<commit_before>
	// Fire 'end' when a mouse or pen leaves the document.
	function documentLeave ( event, data ) {
		if ( event.type === "mouseout" && event.target.nodeName === "HTML" && event.relatedTarget === null ){
			eventEnd (event, data);
		}
	}

	// Handle movement on document for handle and range drag.
	function eventMove ( event, data ) {

		// Fix #498
		// Check value of .buttons in 'start' to work around a bug in IE10 mobile (data.buttonsProperty).
		// https://connect.microsoft.com/IE/feedback/details/927005/mobile-ie10-windows-phone-buttons-property-of-pointermove-event-always-zero
		// IE9 has .buttons and .which zero on mousemove.
		// Firefox breaks the spec MDN defines.
		if ( navigator.appVersion.indexOf("MSIE 9") === -1 && event.buttons === 0 && data.buttonsProperty !== 0 ) {
			return eventEnd(event, data);
		}

		var proposal = ((event.calcPoint - data.startCalcPoint) * 100) / data.baseSize;
		var proposals = data.locations.map(function(a){ return a + proposal; });
		var movingUpward = event.calcPoint > scope_PreviousCalcPoint;
		var handleNumbers = data.handleNumbers.slice();
		var state = true;

		// Check to see which handle is 'leading'.
		// If that one can't move the second can't either.
		if ( movingUpward ) {
			handleNumbers.reverse();
		}

		// Stop if one of the handles can't move.
		handleNumbers.forEach(function(handleNumber) {
			if ( state ) {
				state = setHandle(handleNumber, proposals[handleNumber], APPLY_MARGIN, APPLY_LIMIT, LOOK_FORWARD);
			}
		});

		// Only fire event if something changed
		if ( state ) {

			// Only consider the pointer movement if it resulted in a change
			scope_PreviousCalcPoint = event.calcPoint;

			handleNumbers.forEach(function(handleNumber){
				fireEvent('update', handleNumber);
				fireEvent('slide', handleNumber);
			});
		}
	}

	// Unbind move events on document, call callbacks.
	function eventEnd ( event, data ) {

		// The handle is no longer active, so remove the class.
		var active = scope_Base.querySelector( '.' + options.cssClasses.active );

		if ( active !== null ) {
			removeClass(active, options.cssClasses.active);
		}

		// Remove cursor styles and text-selection events bound to the body.
		if ( event.cursor ) {
			document.body.style.cursor = '';
			document.body.removeEventListener('selectstart', document.body.noUiListener);
		}

		// Unbind the move and end events, which are added on 'start'.
		document.documentElement.noUiListeners.forEach(function( c ) {
			document.documentElement.removeEventListener(c[0], c[1]);
		});

		// Remove dragging class.
		removeClass(scope_Target, options.cssClasses.drag);

		data.handleNumbers.forEach(function(handleNumber){
			fireEvent('set', handleNumber);
			fireEvent('change', handleNumber);
			fireEvent('end', handleNumber);
			setZindex();
		});
	}

	// Bind move events on document.
	function eventStart ( event, data ) {

		// Mark the handle as 'active' so it can be styled.
		if ( data.handleNumbers[0].length === 1 ) {

			var handle = scope_Handles[data.handleNumbers[0]];

			// Ignore 'disabled' handles
			if ( handle.hasAttribute('disabled') ) {
				return false;
			}

			addClass(handle.children[0], options.cssClasses.active);
		}

		// Fix #551, where a handle gets selected instead of dragged.
		event.preventDefault();

		// A drag should never propagate up to the 'tap' event.
		event.stopPropagation();

		scope_PreviousCalcPoint = event.calcPoint;

		// Attach the move and end events.
		var moveEvent = attachEvent(actions.move, document.documentElement, eventMove, {
			startCalcPoint: event.calcPoint,
			baseSize: baseSize(),
			pageOffset: event.pageOffset,
			handleNumbers: data.handleNumbers,
			buttonsProperty: event.buttons,
			locations: scope_Locations.slice()
		});

		var endEvent = attachEvent(actions.end, document.documentElement, eventEnd, {
			handleNumbers: data.handleNumbers
		});

		var outEvent = attachEvent("mouseout", document.documentElement, documentLeave, {
			handleNumbers: data.handleNumbers
		});

		document.documentElement.noUiListeners = moveEvent.concat(endEvent, outEvent);

		// Text selection isn't an issue on touch devices,
		// so adding cursor styles can be skipped.
		if ( event.cursor ) {

			// Prevent the 'I' cursor and extend the range-drag cursor.
			document.body.style.cursor = getComputedStyle(event.target).cursor;

			// Mark the target with a dragging state.
			if ( scope_Handles.length > 1 ) {
				addClass(scope_Target, options.cssClasses.drag);
			}

			var f = function(){
				return false;
			};

			document.body.noUiListener = f;

			// Prevent text selection when dragging the handles.
			document.body.addEventListener('selectstart', f, false);
		}

		data.handleNumbers.forEach(function(handleNumber){
			fireEvent('start', handleNumber);
		});
	}

	// Move closest handle to tapped location.
	function eventTap ( event ) {

		var calcPoint = event.calcPoint;
		var handleNumber = false;
		var minDistance = Number.MAX_VALUE;

		// The tap event shouldn't propagate up and cause 'edge' to run.
		event.stopPropagation();

		// Iterate all handles.
		scope_Handles.forEach(function(handle, index){

			// Disabled handles are ignored
			if ( handle.hasAttribute('disabled') ) {
				return;
			}

			var current = offset(handle)[options.style];
			var candidateMinDistance = Math.abs(current - calcPoint);

			// If the candidate is better, then update the minimum distance as well as the handle number.
			if ( candidateMinDistance < minDistance ) {
				minDistance = candidateMinDistance;
				handleNumber = index;
			}

			// If they are the same, the relative positions of handle and location decide whether there is an update.
			// This is needed for totally overlapping handles.
			else if ( candidateMinDistance == minDistance ) {

				// If this handle is before the calcPoint, then this handle should be selected.
				if ( current < calcPoint ) {
					handleNumber = index;
				}
			}

			// Else, that is the candidate is worse, then the candidates will be getting even worse from there on.
			else {
				return;
			}
		});

		// Tackle the case that all handles are 'disabled'.
		if ( handleNumber === false ) {
			return false;
		}

		calcPoint -= offset(scope_Base)[ options.style ];

		// Calculate the new position.
		var to = (calcPoint * 100) / baseSize();

		// Flag the slider as it is now in a transitional state.
		// Transition takes a configurable amount of ms (default 300). Re-enable the slider after that.
		if ( !options.events.snap ) {
			addClassFor(scope_Target, options.cssClasses.tap, options.animationDuration);
		}

		// Find the closest handle and calculate the tapped point.
		// The set handle to the new position.
		setHandle(handleNumber, to, APPLY_MARGIN, APPLY_MARGIN, LOOK_FORWARD);

		fireEvent('slide', handleNumber, true);
		fireEvent('set', handleNumber, true);
		fireEvent('change', handleNumber, true);

		if ( options.events.snap ) {
			eventStart(event, { handleNumbers: [handleNumber] });
		}
	}

	// Fires a 'hover' event for a hovered mouse/pen position.
	function eventHover ( event ) {

		var location = event.calcPoint - offset(scope_Base)[ options.style ];
		var to = scope_Spectrum.getStep(( location * 100 ) / baseSize());
		var value = scope_Spectrum.fromStepping( to );

		Object.keys(scope_Events).forEach(function( targetEvent ) {
			if ( 'hover' === targetEvent.split('.')[0] ) {
				scope_Events[targetEvent].forEach(function( callback ) {
					callback.call( scope_Self, value );
				});
			}
		});
	}

	// Attach events to several slider parts.
	function bindSliderEvents ( behaviour ) {

		// Attach the standard drag event to the handles.
		if ( !behaviour.fixed ) {

			scope_Handles.forEach(function( handle, index ){

				// These events are only bound to the visual handle
				// element, not the 'real' origin element.
				attachEvent ( actions.start, handle.children[0], eventStart, {
					handleNumbers: [index]
				});
			});
		}

		// Attach the tap event to the slider base.
		if ( behaviour.tap ) {
			attachEvent (actions.start, scope_Base, eventTap, {});
		}

		// Fire hover events
		if ( behaviour.hover ) {
			attachEvent (actions.move, scope_Base, eventHover, { hover: true });
		}

		// Make the range draggable.
		if ( behaviour.drag ){

			scope_Connects.forEach(function( connect, index ){

				if ( connect === false || index === 0 || index === scope_Connects.length - 1 ) {
					return;
				}

				var handleBefore = scope_Handles[index - 1];
				var handleAfter = scope_Handles[index];
				var eventHolders = [connect];

				addClass(connect, options.cssClasses.draggable);

				// When the range is fixed, the entire range can
				// be dragged by the handles. The handle in the first
				// origin will propagate the start event upward,
				// but it needs to be bound manually on the other.
				if ( behaviour.fixed ) {
					eventHolders.push(handleBefore.children[0]);
					eventHolders.push(handleAfter.children[0]);
				}

				eventHolders.forEach(function( eventHolder ) {
					attachEvent ( actions.start, eventHolder, eventStart, {
						handles: [handleBefore, handleAfter],
						handleNumbers: [index - 1, index]
					});
				});
			});
		}
	}
<commit_msg>decouple events from pointer<commit_after>
	// Fire 'end' when a mouse or pen leaves the document.
	function documentLeave ( event, data ) {
		if ( event.type === "mouseout" && event.target.nodeName === "HTML" && event.relatedTarget === null ){
			eventEnd (event, data);
		}
	}

	// Handle movement on document for handle and range drag.
	function eventMove ( event, data ) {

		// Fix #498
		// Check value of .buttons in 'start' to work around a bug in IE10 mobile (data.buttonsProperty).
		// https://connect.microsoft.com/IE/feedback/details/927005/mobile-ie10-windows-phone-buttons-property-of-pointermove-event-always-zero
		// IE9 has .buttons and .which zero on mousemove.
		// Firefox breaks the spec MDN defines.
		if ( navigator.appVersion.indexOf("MSIE 9") === -1 && event.buttons === 0 && data.buttonsProperty !== 0 ) {
			return eventEnd(event, data);
		}

		var proposal = ((event.calcPoint - data.startCalcPoint) * 100) / data.baseSize;
		var proposals = data.locations.map(function(a){ return a + proposal; });
		var movingUpward = event.calcPoint > scope_PreviousCalcPoint;
		var handleNumbers = data.handleNumbers.slice();
		var state = true;
		var firedOnce = false;

		// Check to see which handle is 'leading'.
		// If that one can't move the second can't either.
		if ( movingUpward ) {
			handleNumbers.reverse();
		}

		// Stop if one of the handles can't move.
		handleNumbers.forEach(function(handleNumber) {
			if ( state ) {
				state = setHandle(handleNumber, proposals[handleNumber], APPLY_MARGIN, APPLY_LIMIT, LOOK_FORWARD);
				firedOnce = firedOnce || state;
			}
		});

		// Only consider the pointer movement if it resulted in a change
		if ( state ) {
			scope_PreviousCalcPoint = event.calcPoint;
		}

		// Only fire event if something changed
		if ( firedOnce ) {

			handleNumbers.forEach(function(handleNumber){
				fireEvent('update', handleNumber);
				fireEvent('slide', handleNumber);
			});
		}
	}

	// Unbind move events on document, call callbacks.
	function eventEnd ( event, data ) {

		// The handle is no longer active, so remove the class.
		var active = scope_Base.querySelector( '.' + options.cssClasses.active );

		if ( active !== null ) {
			removeClass(active, options.cssClasses.active);
		}

		// Remove cursor styles and text-selection events bound to the body.
		if ( event.cursor ) {
			document.body.style.cursor = '';
			document.body.removeEventListener('selectstart', document.body.noUiListener);
		}

		// Unbind the move and end events, which are added on 'start'.
		document.documentElement.noUiListeners.forEach(function( c ) {
			document.documentElement.removeEventListener(c[0], c[1]);
		});

		// Remove dragging class.
		removeClass(scope_Target, options.cssClasses.drag);

		data.handleNumbers.forEach(function(handleNumber){
			fireEvent('set', handleNumber);
			fireEvent('change', handleNumber);
			fireEvent('end', handleNumber);
			setZindex();
		});
	}

	// Bind move events on document.
	function eventStart ( event, data ) {

		// Mark the handle as 'active' so it can be styled.
		if ( data.handleNumbers[0].length === 1 ) {

			var handle = scope_Handles[data.handleNumbers[0]];

			// Ignore 'disabled' handles
			if ( handle.hasAttribute('disabled') ) {
				return false;
			}

			addClass(handle.children[0], options.cssClasses.active);
		}

		// Fix #551, where a handle gets selected instead of dragged.
		event.preventDefault();

		// A drag should never propagate up to the 'tap' event.
		event.stopPropagation();

		scope_PreviousCalcPoint = event.calcPoint;

		// Attach the move and end events.
		var moveEvent = attachEvent(actions.move, document.documentElement, eventMove, {
			startCalcPoint: event.calcPoint,
			baseSize: baseSize(),
			pageOffset: event.pageOffset,
			handleNumbers: data.handleNumbers,
			buttonsProperty: event.buttons,
			locations: scope_Locations.slice()
		});

		var endEvent = attachEvent(actions.end, document.documentElement, eventEnd, {
			handleNumbers: data.handleNumbers
		});

		var outEvent = attachEvent("mouseout", document.documentElement, documentLeave, {
			handleNumbers: data.handleNumbers
		});

		document.documentElement.noUiListeners = moveEvent.concat(endEvent, outEvent);

		// Text selection isn't an issue on touch devices,
		// so adding cursor styles can be skipped.
		if ( event.cursor ) {

			// Prevent the 'I' cursor and extend the range-drag cursor.
			document.body.style.cursor = getComputedStyle(event.target).cursor;

			// Mark the target with a dragging state.
			if ( scope_Handles.length > 1 ) {
				addClass(scope_Target, options.cssClasses.drag);
			}

			var f = function(){
				return false;
			};

			document.body.noUiListener = f;

			// Prevent text selection when dragging the handles.
			document.body.addEventListener('selectstart', f, false);
		}

		data.handleNumbers.forEach(function(handleNumber){
			fireEvent('start', handleNumber);
		});
	}

	// Move closest handle to tapped location.
	function eventTap ( event ) {

		var calcPoint = event.calcPoint;
		var handleNumber = false;
		var minDistance = Number.MAX_VALUE;

		// The tap event shouldn't propagate up and cause 'edge' to run.
		event.stopPropagation();

		// Iterate all handles.
		scope_Handles.forEach(function(handle, index){

			// Disabled handles are ignored
			if ( handle.hasAttribute('disabled') ) {
				return;
			}

			var current = offset(handle)[options.style];
			var candidateMinDistance = Math.abs(current - calcPoint);

			// If the candidate is better, then update the minimum distance as well as the handle number.
			if ( candidateMinDistance < minDistance ) {
				minDistance = candidateMinDistance;
				handleNumber = index;
			}

			// If they are the same, the relative positions of handle and location decide whether there is an update.
			// This is needed for totally overlapping handles.
			else if ( candidateMinDistance == minDistance ) {

				// If this handle is before the calcPoint, then this handle should be selected.
				if ( current < calcPoint ) {
					handleNumber = index;
				}
			}

			// Else, that is the candidate is worse, then the candidates will be getting even worse from there on.
			else {
				return;
			}
		});

		// Tackle the case that all handles are 'disabled'.
		if ( handleNumber === false ) {
			return false;
		}

		calcPoint -= offset(scope_Base)[ options.style ];

		// Calculate the new position.
		var to = (calcPoint * 100) / baseSize();

		// Flag the slider as it is now in a transitional state.
		// Transition takes a configurable amount of ms (default 300). Re-enable the slider after that.
		if ( !options.events.snap ) {
			addClassFor(scope_Target, options.cssClasses.tap, options.animationDuration);
		}

		// Find the closest handle and calculate the tapped point.
		// The set handle to the new position.
		setHandle(handleNumber, to, APPLY_MARGIN, APPLY_MARGIN, LOOK_FORWARD);

		fireEvent('slide', handleNumber, true);
		fireEvent('set', handleNumber, true);
		fireEvent('change', handleNumber, true);

		if ( options.events.snap ) {
			eventStart(event, { handleNumbers: [handleNumber] });
		}
	}

	// Fires a 'hover' event for a hovered mouse/pen position.
	function eventHover ( event ) {

		var location = event.calcPoint - offset(scope_Base)[ options.style ];
		var to = scope_Spectrum.getStep(( location * 100 ) / baseSize());
		var value = scope_Spectrum.fromStepping( to );

		Object.keys(scope_Events).forEach(function( targetEvent ) {
			if ( 'hover' === targetEvent.split('.')[0] ) {
				scope_Events[targetEvent].forEach(function( callback ) {
					callback.call( scope_Self, value );
				});
			}
		});
	}

	// Attach events to several slider parts.
	function bindSliderEvents ( behaviour ) {

		// Attach the standard drag event to the handles.
		if ( !behaviour.fixed ) {

			scope_Handles.forEach(function( handle, index ){

				// These events are only bound to the visual handle
				// element, not the 'real' origin element.
				attachEvent ( actions.start, handle.children[0], eventStart, {
					handleNumbers: [index]
				});
			});
		}

		// Attach the tap event to the slider base.
		if ( behaviour.tap ) {
			attachEvent (actions.start, scope_Base, eventTap, {});
		}

		// Fire hover events
		if ( behaviour.hover ) {
			attachEvent (actions.move, scope_Base, eventHover, { hover: true });
		}

		// Make the range draggable.
		if ( behaviour.drag ){

			scope_Connects.forEach(function( connect, index ){

				if ( connect === false || index === 0 || index === scope_Connects.length - 1 ) {
					return;
				}

				var handleBefore = scope_Handles[index - 1];
				var handleAfter = scope_Handles[index];
				var eventHolders = [connect];

				addClass(connect, options.cssClasses.draggable);

				// When the range is fixed, the entire range can
				// be dragged by the handles. The handle in the first
				// origin will propagate the start event upward,
				// but it needs to be bound manually on the other.
				if ( behaviour.fixed ) {
					eventHolders.push(handleBefore.children[0]);
					eventHolders.push(handleAfter.children[0]);
				}

				eventHolders.forEach(function( eventHolder ) {
					attachEvent ( actions.start, eventHolder, eventStart, {
						handles: [handleBefore, handleAfter],
						handleNumbers: [index - 1, index]
					});
				});
			});
		}
	}
<|endoftext|>


<commit_before>
const __cellSize = 50;

class SudokuScreen {

    /**
     * Sudoku instance
     */
    constructor(eventManager, properties) {

        this.grid = [];
        this.rawGrid = [];
        this.tempGrid = [];
        this.solutions = [];
        this.numbersBtn = [];
        this.currentNumber = null;
        this.properties = properties;
        this.eventManager = eventManager;

        this.globalTimer = new CanvasTimer(200,15,100,40,"Timer:",this.properties.globalTimer);
        this.blockUpTimer = new CanvasTimer(350,15,100,40,"PC Turn:",this.properties.blockUpTimer);
        this.blockUpPowerDuration = new CanvasTimer(500,15,100,40,"PC Power:",0);

        this.generateGrid();
        this.generateNumbers();
        // Here we copy the SudokuNumber matrix (already scrambled) to an integer matrix
        this.generateRawGrid();

        // As this is a relatively expensive function, we'll only run it if we're going to check for errors (easy mode)
        if(this.properties['errores'])
            this.findSolutions();
            console.log(this.solutions);

        this.globalTimer.initTimer();
        this.blockUpTimer.initTimer();

        this.setEvents();
    }

    setEvents() {

        this.globalTimer.eventTimer = () => {
            this.globalTimer.stopTimer();
            this.blockUpTimer.stopTimer();
            this.blockUpPowerDuration.stopTimer();

            this.eventManager.fire('showFinal', 'FAIL');
        };

        this.blockUpTimer.eventTimer = () => {
            this.blockUpPowerDuration.current = this.properties.blockUpPowerDuration;
            this.blockUpPowerDuration.initTimer();
        };

        this.blockUpPowerDuration.eventTimer = () => {
            this.blockUpPowerDuration.stopTimer();
        };
    }

    generateGrid() {

        for (let r = 0; r < 9; ++r) {

            let row = [];

            for (let c = 0; c < 9; ++c) {

                let sudokuNumber = new SudokuNumber(177+c*50,72+r*50,48,48, Math.floor((r*3+r/3+c)%9+1));

                sudokuNumber.onMouseUp = () => {
                    sudokuNumber.setValue(this.currentNumber.text);
                }

                row.push(sudokuNumber);
            }

            this.grid.push(row);
        }

        // Simplest way ^^
        for (let i = 1; i < 7; ++i) {
            this.swapNumbers(i, Math.floor(Math.random()*9+1));
            this.swapRows(Math.floor(Math.random()*3),Math.floor(Math.random()*3),Math.floor(Math.random()*3));
            this.swapGroups(Math.floor(Math.random()*3),Math.floor(Math.random()*3,Math.random()*3));
            this.rotateChess();
        }

        let current = 0;
        let max = 81/this.properties.clues;
        let block = Math.floor(Math.random() * Math.round(max));

        while (current < 81) {

            if (current >= Math.round(max)) {
                block = Math.floor(Math.random() * (Math.round(max+81/this.properties.clues) - Math.round(max)) + Math.round(max));
                max += 81/this.properties.clues;
            }

            let sudokuNumber = this.grid[Math.floor(current/9)][current%9];

            if (current == block)
                sudokuNumber.block = true;
            else
                sudokuNumber.value = 0;

            ++current;

        }
        console.log("YAY");
    }

    swapNumbers(from, to) {

        this.grid.forEach(r => {
            r.forEach(e => {
                if (e.value == from)
                    e.value = to;
                else if (e.value == to)
                    e.value = from;
            });
        });

    }

    swapRows(group, from, to) {

        from = this.grid[group*3+from];
        to = this.grid[group*3+to];

        for (let i = 0; i < 9; ++i) {
            let tmpVal = from[i].value;
            from[i].value = to[i].value;
            to[i].value = tmpVal;
        }
    }

    swapGroups(from, to) {

        from = from*3;
        to = to*3;

        for (let i = 0; i < 3; ++i) {
            for (let j = 0; j < 9; ++j) {
                let tmpVal = this.grid[from+i][j].value;
                this.grid[from+i][j].value = this.grid[to+i][j].value;
                this.grid[to+i][j].value = tmpVal;
            }
        }
    }

    rotateChess() {

        for (let r = 0; r < 9; ++r) {
            for (let c = r+1; c < 9; ++c) {
                let tmpVal = this.grid[r][c].value;
                this.grid[r][c].value = this.grid[c][r].value;
                this.grid[c][r].value = tmpVal;

            }
        }

        for (let r = 0; r < 9; ++r) {
            for (let c = 0; c < 4; ++c) {
                let tmpVal = this.grid[r][c].value;
                this.grid[r][c].value = this.grid[r][8-c].value;
                this.grid[r][8-c].value = tmpVal;

            }
        }
    }

    generateNumbers() {

        for (let i = 0; i < 9; ++i) {

            let numberBtn = new CanvasButton(181+i*50,540,40,40,i+1, "Tahoma 15px");

            numberBtn.onMouseUp = () => {

                this.currentNumber.setProperties({backgroundColor:'#FFFFFF'});
                numberBtn.setProperties({backgroundColor:'#DDDDDD'});

                this.currentNumber = numberBtn;
            };

            this.numbersBtn.push(numberBtn);
        }

        this.currentNumber = this.numbersBtn[0];
        this.currentNumber.setProperties({backgroundColor:'#DDDDDD'});
    }

    draw(canvasCtx) {

        // Draw grey lines (all grid)
        canvasCtx.fillStyle = "#AAAAAA";
        canvasCtx.beginPath();

        for (let i = 0; i < 10; ++i) {

            canvasCtx.rect(175+__cellSize*i,70,2,this.grid.length*50);
            canvasCtx.rect(175,70+__cellSize*i,this.grid.length*50,2);
        }

        // End draw
        canvasCtx.fill();

        // Draw black lines (group lines)
        canvasCtx.fillStyle = "#000000";
        canvasCtx.beginPath();

        // We add "+2" on the length of the lines beucase the anchor is 2
        for (let i = 0; i < 10; i+=3) {

            // Horizontal
            canvasCtx.rect(175+__cellSize*i,70,2,this.grid.length*50+2);

            // Vertical
            canvasCtx.rect(175,70+__cellSize*i,this.grid.length*50+2,2);
        }

        // End draw
        canvasCtx.fill();

        this.grid.forEach(r => r.forEach(e => e.draw(canvasCtx)));

        this.numbersBtn.forEach(e => e.draw(canvasCtx));

        this.globalTimer.draw(canvasCtx);
        this.blockUpTimer.draw(canvasCtx);
        this.blockUpPowerDuration.draw(canvasCtx);
    }

    handleEvent(event, x, y) {

        this.numbersBtn.forEach(e => e.handleEvent(event, x, y));

        this.grid.forEach(r => r.forEach(e => e.handleEvent(event, x, y)));
    }

    generateRawGrid(){
        for(let row of this.grid){
            let rawRow = [];

            for(let number of row)
                rawRow.push(number.getValue());

            this.rawGrid.push(rawRow);
        }
    }

    findSolutions(){
        for(let fila of this.rawGrid)
            this.tempGrid.push(fila.slice(0));

        let emptyCell = this.findEmptyCell();

        if(!emptyCell){
            return;
        }

        let leadRow = emptyCell[0];
        let leadCol = emptyCell[1];

        for(let i = 1; i <= 9; ++i)
            if(this.isValid(leadRow, leadCol, i)){
                this.tempGrid[leadRow][leadCol] = i;
                this.findSolution();
                this.tempGrid[leadRow][leadCol] = 0;
            }
    }

    findSolution(){
        let emptyCell = this.findEmptyCell();

        // If there's no empty cells, the board is solved, and it's then copied to our solutions list
        if(!emptyCell){
            let solvedGrid = []

            for(let fila of this.tempGrid)
                solvedGrid.push(fila.slice(0));

            this.solutions.push(solvedGrid);
            return true;
        }

        let row = emptyCell[0];
        let col = emptyCell[1];

        for(let i = 1; i <= 9; ++i){
            if(this.isValid(row, col, i))
            {
                this.tempGrid[row][col] = i;

                if(this.findSolution())
                    return true;

                this.tempGrid[row][col] = 0;
            }
        }

        return false;
    }

    // Three simple validation checks: Row, Column, and 3x3 board the number belongs to
    isValid(row, col, number){
        let length = this.tempGrid.length;
        let width = Math.sqrt(length);

        for(let i = 0; i < length; ++i)
            if(i !== col && this.tempGrid[row][i] === number)
                return false;

        for(let i = 0; i < length; ++i)
            if(i !== row && this.tempGrid[i][col] === number)
                return false;

        let limit_x = width * Math.floor(row / width);
        let limit_y = width * Math.floor(col / width);

        for(let i = 0; i < width; ++i)
            for(let j = 0; j < width; ++j){
                if(limit_x + i == row && limit_y + j == col)
                    continue;

                if(this.tempGrid[limit_x + i][limit_y + j] === number)
                    return false;
            }

        return true;
    }

    findEmptyCell(){
        let length = this.tempGrid.length;

        for(let i = 0; i < length; ++i)
            for(let j = 0; j < length; ++j)
                if(this.tempGrid[i][j] === 0)
                    return [i, j];

        return false;
    }
}<commit_msg>Check for errors pt. 2<commit_after>
const __cellSize = 50;

class SudokuScreen {

    /**
     * Sudoku instance
     */
    constructor(eventManager, properties) {

        this.grid = [];
        this.rawGrid = [];
        this.tempGrid = [];
        this.solutions = [];
        this.numbersBtn = [];
        this.currentNumber = null;
        this.properties = properties;
        this.eventManager = eventManager;

        this.globalTimer = new CanvasTimer(200,15,100,40,"Timer:",this.properties.globalTimer);
        this.blockUpTimer = new CanvasTimer(350,15,100,40,"PC Turn:",this.properties.blockUpTimer);
        this.blockUpPowerDuration = new CanvasTimer(500,15,100,40,"PC Power:",0);

        this.generateGrid();
        this.generateNumbers();
        // Here we copy the SudokuNumber matrix (already scrambled) to an integer matrix
        this.generateRawGrid();

        // As this is a relatively expensive function, we'll only run it if we're going to check for errors (easy mode)
        if(this.properties['errores'])
            this.findSolutions();
            console.log(this.solutions);

        this.globalTimer.initTimer();
        this.blockUpTimer.initTimer();

        this.setEvents();
    }

    setEvents() {

        this.globalTimer.eventTimer = () => {
            this.globalTimer.stopTimer();
            this.blockUpTimer.stopTimer();
            this.blockUpPowerDuration.stopTimer();

            this.eventManager.fire('showFinal', 'FAIL');
        };

        this.blockUpTimer.eventTimer = () => {
            this.blockUpPowerDuration.current = this.properties.blockUpPowerDuration;
            this.blockUpPowerDuration.initTimer();
        };

        this.blockUpPowerDuration.eventTimer = () => {
            this.blockUpPowerDuration.stopTimer();
        };
    }

    generateGrid() {

        for (let r = 0; r < 9; ++r) {

            let row = [];

            for (let c = 0; c < 9; ++c) {

                let sudokuNumber = new SudokuNumber(177+c*50,72+r*50,48,48, Math.floor((r*3+r/3+c)%9+1));

                sudokuNumber.onMouseUp = () => {
                    sudokuNumber.setValue(this.currentNumber.text);
                    this.highlightInvalidCells((sudokuNumber.y - 72) / 50, (sudokuNumber.x - 177) / 50, sudokuNumber.getValue());
                }

                row.push(sudokuNumber);
            }

            this.grid.push(row);
        }

        // Simplest way ^^
        for (let i = 1; i < 7; ++i) {
            this.swapNumbers(i, Math.floor(Math.random()*9+1));
            this.swapRows(Math.floor(Math.random()*3),Math.floor(Math.random()*3),Math.floor(Math.random()*3));
            this.swapGroups(Math.floor(Math.random()*3),Math.floor(Math.random()*3,Math.random()*3));
            this.rotateChess();
        }

        let current = 0;
        let max = 81/this.properties.clues;
        let block = Math.floor(Math.random() * Math.round(max));

        while (current < 81) {

            if (current >= Math.round(max)) {
                block = Math.floor(Math.random() * (Math.round(max+81/this.properties.clues) - Math.round(max)) + Math.round(max));
                max += 81/this.properties.clues;
            }

            let sudokuNumber = this.grid[Math.floor(current/9)][current%9];

            if (current == block)
                sudokuNumber.block = true;
            else
                sudokuNumber.value = 0;

            ++current;

        }
        console.log("YAY");
    }

    swapNumbers(from, to) {

        this.grid.forEach(r => {
            r.forEach(e => {
                if (e.value == from)
                    e.value = to;
                else if (e.value == to)
                    e.value = from;
            });
        });

    }

    swapRows(group, from, to) {

        from = this.grid[group*3+from];
        to = this.grid[group*3+to];

        for (let i = 0; i < 9; ++i) {
            let tmpVal = from[i].value;
            from[i].value = to[i].value;
            to[i].value = tmpVal;
        }
    }

    swapGroups(from, to) {

        from = from*3;
        to = to*3;

        for (let i = 0; i < 3; ++i) {
            for (let j = 0; j < 9; ++j) {
                let tmpVal = this.grid[from+i][j].value;
                this.grid[from+i][j].value = this.grid[to+i][j].value;
                this.grid[to+i][j].value = tmpVal;
            }
        }
    }

    rotateChess() {

        for (let r = 0; r < 9; ++r) {
            for (let c = r+1; c < 9; ++c) {
                let tmpVal = this.grid[r][c].value;
                this.grid[r][c].value = this.grid[c][r].value;
                this.grid[c][r].value = tmpVal;

            }
        }

        for (let r = 0; r < 9; ++r) {
            for (let c = 0; c < 4; ++c) {
                let tmpVal = this.grid[r][c].value;
                this.grid[r][c].value = this.grid[r][8-c].value;
                this.grid[r][8-c].value = tmpVal;

            }
        }
    }

    generateNumbers() {

        for (let i = 0; i < 9; ++i) {

            let numberBtn = new CanvasButton(181+i*50,540,40,40,i+1, "Tahoma 15px");

            numberBtn.onMouseUp = () => {

                this.currentNumber.setProperties({backgroundColor:'#FFFFFF'});
                numberBtn.setProperties({backgroundColor:'#DDDDDD'});

                this.currentNumber = numberBtn;
            };

            this.numbersBtn.push(numberBtn);
        }

        this.currentNumber = this.numbersBtn[0];
        this.currentNumber.setProperties({backgroundColor:'#DDDDDD'});
    }

    draw(canvasCtx) {

        // Draw grey lines (all grid)
        canvasCtx.fillStyle = "#AAAAAA";
        canvasCtx.beginPath();

        for (let i = 0; i < 10; ++i) {

            canvasCtx.rect(175+__cellSize*i,70,2,this.grid.length*50);
            canvasCtx.rect(175,70+__cellSize*i,this.grid.length*50,2);
        }

        // End draw
        canvasCtx.fill();

        // Draw black lines (group lines)
        canvasCtx.fillStyle = "#000000";
        canvasCtx.beginPath();

        // We add "+2" on the length of the lines beucase the anchor is 2
        for (let i = 0; i < 10; i+=3) {

            // Horizontal
            canvasCtx.rect(175+__cellSize*i,70,2,this.grid.length*50+2);

            // Vertical
            canvasCtx.rect(175,70+__cellSize*i,this.grid.length*50+2,2);
        }

        // End draw
        canvasCtx.fill();

        this.grid.forEach(r => r.forEach(e => e.draw(canvasCtx)));

        this.numbersBtn.forEach(e => e.draw(canvasCtx));

        this.globalTimer.draw(canvasCtx);
        this.blockUpTimer.draw(canvasCtx);
        this.blockUpPowerDuration.draw(canvasCtx);
    }

    handleEvent(event, x, y) {

        this.numbersBtn.forEach(e => e.handleEvent(event, x, y));

        this.grid.forEach(r => r.forEach(e => e.handleEvent(event, x, y)));
    }

    generateRawGrid(){
        for(let row of this.grid){
            let rawRow = [];

            for(let number of row)
                rawRow.push(number.getValue());

            this.rawGrid.push(rawRow);
        }
    }

    findSolutions(){
        for(let fila of this.rawGrid)
            this.tempGrid.push(fila.slice(0));

        let emptyCell = this.findEmptyCell();

        if(!emptyCell){
            return;
        }

        let leadRow = emptyCell[0];
        let leadCol = emptyCell[1];

        for(let i = 1; i <= 9; ++i)
            if(this.isValid(leadRow, leadCol, i)){
                this.tempGrid[leadRow][leadCol] = i;
                this.findSolution();
                this.tempGrid[leadRow][leadCol] = 0;
            }
    }

    findSolution(){
        let emptyCell = this.findEmptyCell();

        // If there's no empty cells, the board is solved, and it's then copied to our solutions list
        if(!emptyCell){
            let solvedGrid = []

            for(let fila of this.tempGrid)
                solvedGrid.push(fila.slice(0));

            this.solutions.push(solvedGrid);
            return true;
        }

        let row = emptyCell[0];
        let col = emptyCell[1];

        for(let i = 1; i <= 9; ++i){
            if(this.isValid(row, col, i))
            {
                this.tempGrid[row][col] = i;

                if(this.findSolution())
                    return true;

                this.tempGrid[row][col] = 0;
            }
        }

        return false;
    }

    // Three simple validation checks: Row, Column, and 3x3 board the number belongs to
    isValid(row, col, number){
        let length = this.tempGrid.length;
        let width = Math.sqrt(length);

        for(let i = 0; i < length; ++i)
            if(i !== col && this.tempGrid[row][i] === number)
                return false;

        for(let i = 0; i < length; ++i)
            if(i !== row && this.tempGrid[i][col] === number)
                return false;

        let limit_x = width * Math.floor(row / width);
        let limit_y = width * Math.floor(col / width);

        for(let i = 0; i < width; ++i)
            for(let j = 0; j < width; ++j){
                if(limit_x + i == row && limit_y + j == col)
                    continue;

                if(this.tempGrid[limit_x + i][limit_y + j] === number)
                    return false;
            }

        return true;
    }

    findEmptyCell(){
        let length = this.tempGrid.length;

        for(let i = 0; i < length; ++i)
            for(let j = 0; j < length; ++j)
                if(this.tempGrid[i][j] === 0)
                    return [i, j];

        return false;
    }

    highlightInvalidCells(row, col, number){
        let length = this.tempGrid.length;
        let width = Math.sqrt(length);

        for(let i = 0; i < length; ++i)
            if(i !== col && this.tempGrid[row][i] === number)
                this.grid[row][i].isCollision = this.grid[row][i].getValue() == 0 ? false : true;
            else
                this.grid[row][i].isCollision = false;

        for(let i = 0; i < length; ++i)
            if(i !== row && this.tempGrid[i][col] === number)
                this.grid[i][col].isCollision = this.grid[i][col].getValue() == 0 ? false : true;
            else
                this.grid[i][col].isCollision = false;

        let limit_x = width * Math.floor(row / width);
        let limit_y = width * Math.floor(col / width);

        for(let i = 0; i < width; ++i)
            for(let j = 0; j < width; ++j){
                if(limit_x + i == row && limit_y + j == col)
                    continue;

                if(this.tempGrid[limit_x + i][limit_y + j] === number)
                    this.grid[limit_x + i][limit_y + j].isCollision = this.grid[limit_x + i][limit_y + j].getValue() == 0 ? false : true;
                else
                    this.grid[limit_x + i][limit_y + j].isCollision = false;
            }
    }
}
<|endoftext|>


<commit_before>/*jslint nomen: true */
/*globals define: true */

define([
  'jquery',
  'lib/lodash',
  'backbone',
  'lib/tinypubsub',

  // LocalData
  'settings',
  'api',

  // Models
  'models/surveys',
  'models/forms',

  // Views
  'views/preview',

  // Templates
  'text!templates/surveys/design.html',
  'text!templates/surveys/design-copy-survey.html',

  // Misc
  'misc/exampleform',
  'misc/blankform'
],

function($, _, Backbone, events, settings, api, SurveyModels, FormModels, PreviewView,
    designTemplate, copySurveyTemplate, exampleForm, blankForm) {
  'use strict';

  var DesignViews = {};

  DesignViews.DesignView = Backbone.View.extend({
    template: _.template(designTemplate),
    copySurveyTemplate: _.template(copySurveyTemplate),

    events: {
      'click .preview': 'preview',
      'click .use-survey': 'useSurvey',
      'click .use-blank-survey': 'useBlankSurvey',
      'click .copy-form': 'copyForm'
    },

    initialize: function(options) {
      _.bindAll(this,
        'update',
        'render',
        'useSurvey',
        'useBlankSurvey',
        'copyForm',
        'saveFormToSurvey',
        'showSurveys',
        'preview'
      );

      this.surveys = new SurveyModels.Collection();
      this.surveys.bind('reset', this.showSurveys);
      this.surveys.fetch({
        reset: true
      });

      this.el = options.el;
      this.$el = $(options.el);

      this.model.on('change', this.render, this);
    },

    update: function() {
      console.log("Update design view called, but not operational");
      // this.render();
    },

    // List surveys the user owns
    showSurveys: function(event) {
      var context = {
        surveys: this.surveys.toJSON()
      };
      this.$el.find('.copy-survey').html(this.copySurveyTemplate(context));
    },

    // Start copying a form from an existing survey
    copyForm: function(event) {
      event.preventDefault();
      var $target = $(event.target);
      var $select = $target.parent().find('select');
      var surveyId = $select.val();
      this.getForm(surveyId);

      // Mark the button disabled so users know we're waiting
      $target.attr('disabled', 'disabled');
      $target.addClass('disabled');
    },

    // Get the forms of an existing survey
    getForm: function(surveyId) {
      var form = new FormModels.Collection({
        surveyId: surveyId
      });
      form.on('reset', this.saveFormToSurvey);
    },

    // Save an existing survey form to this new survey
    saveFormToSurvey: function(formCollection) {
      var formToSave = formCollection.toJSON()[0];
      delete formToSave.created;
      delete formToSave.id;
      api.createForm(formToSave, $.proxy(function() {
        this.trigger('formAdded');
      },this));
    },

    useSurvey: function(event) {
      console.log("Using the survey");
      api.createForm(exampleForm, $.proxy(function() {
        this.trigger('formAdded');
      },this));
    },

    useBlankSurvey: function(event) {
      console.log("Using the survey");
      api.createForm(blankForm, $.proxy(function() {
        this.trigger('formAdded');
      },this));
    },

    render: function() {
      console.log("Rendering design view", this.surveys);
      $("#preview-view-container").empty();

      // Set the context & render the page
      var context = {
        'survey': this.model.toJSON()
      };
      this.$el.html(this.template(context));
    },

    preview: function(event) {
      console.log("Survey preview clicked");
      event.preventDefault();

      this.previewView = new PreviewView({
        el: '#preview-view-container',
        popup: true,
        forms: [exampleForm]
      });
    }

  });

  return DesignViews;
});
<commit_msg>Remove errant log<commit_after>/*jslint nomen: true */
/*globals define: true */

define([
  'jquery',
  'lib/lodash',
  'backbone',
  'lib/tinypubsub',

  // LocalData
  'settings',
  'api',

  // Models
  'models/surveys',
  'models/forms',

  // Views
  'views/preview',

  // Templates
  'text!templates/surveys/design.html',
  'text!templates/surveys/design-copy-survey.html',

  // Misc
  'misc/exampleform',
  'misc/blankform'
],

function($, _, Backbone, events, settings, api, SurveyModels, FormModels, PreviewView,
    designTemplate, copySurveyTemplate, exampleForm, blankForm) {
  'use strict';

  var DesignViews = {};

  DesignViews.DesignView = Backbone.View.extend({
    template: _.template(designTemplate),
    copySurveyTemplate: _.template(copySurveyTemplate),

    events: {
      'click .preview': 'preview',
      'click .use-survey': 'useSurvey',
      'click .use-blank-survey': 'useBlankSurvey',
      'click .copy-form': 'copyForm'
    },

    initialize: function(options) {
      _.bindAll(this,
        'update',
        'render',
        'useSurvey',
        'useBlankSurvey',
        'copyForm',
        'saveFormToSurvey',
        'showSurveys',
        'preview'
      );

      this.surveys = new SurveyModels.Collection();
      this.surveys.bind('reset', this.showSurveys);
      this.surveys.fetch({
        reset: true
      });

      this.el = options.el;
      this.$el = $(options.el);

      this.model.on('change', this.render, this);
    },

    update: function() {
      console.log("Update design view called, but not operational");
      // this.render();
    },

    // List surveys the user owns
    showSurveys: function(event) {
      var context = {
        surveys: this.surveys.toJSON()
      };
      this.$el.find('.copy-survey').html(this.copySurveyTemplate(context));
    },

    // Start copying a form from an existing survey
    copyForm: function(event) {
      event.preventDefault();
      var $target = $(event.target);
      var $select = $target.parent().find('select');
      var surveyId = $select.val();
      this.getForm(surveyId);

      // Mark the button disabled so users know we're waiting
      $target.attr('disabled', 'disabled');
      $target.addClass('disabled');
    },

    // Get the forms of an existing survey
    getForm: function(surveyId) {
      var form = new FormModels.Collection({
        surveyId: surveyId
      });
      form.on('reset', this.saveFormToSurvey);
    },

    // Save an existing survey form to this new survey
    saveFormToSurvey: function(formCollection) {
      var formToSave = formCollection.toJSON()[0];
      delete formToSave.created;
      delete formToSave.id;
      api.createForm(formToSave, $.proxy(function() {
        this.trigger('formAdded');
      },this));
    },

    useSurvey: function(event) {
      console.log("Using the survey");
      api.createForm(exampleForm, $.proxy(function() {
        this.trigger('formAdded');
      },this));
    },

    useBlankSurvey: function(event) {
      console.log("Using the survey");
      api.createForm(blankForm, $.proxy(function() {
        this.trigger('formAdded');
      },this));
    },

    render: function() {
      console.log("Rendering design view");
      $("#preview-view-container").empty();

      // Set the context & render the page
      var context = {
        'survey': this.model.toJSON()
      };
      this.$el.html(this.template(context));
    },

    preview: function(event) {
      console.log("Survey preview clicked");
      event.preventDefault();

      this.previewView = new PreviewView({
        el: '#preview-view-container',
        popup: true,
        forms: [exampleForm]
      });
    }

  });

  return DesignViews;
});
<|endoftext|>


<commit_before>const test = require('ava');
const spies = require('chai-spies');
const chai = require('chai');
const expect = chai.expect;
chai.use(spies);
const TestUtils = require('fountain-generator').TestUtils;

let context;

test.before(() => {
  context = TestUtils.mock('ci');
  context.templatePath = path => path;
  context.destinationPath = path => path;
  context.fs = {
    copy: () => {}
  };
  require('../../generators/ci/index');
});

test('Call this.fs.copy twice for Jenkins', () => {
  context.options.ci = ['jenkins'];
  const spy1 = chai.spy.on(context.fs, 'copy');
  TestUtils.call(context, 'writing');
  expect(spy1).to.have.been.called.twice();
});

test('Call this.fs.copy twice for CircleCi', () => {
  context.options.ci = ['circleci'];
  const spy1 = chai.spy.on(context.fs, 'copy');
  TestUtils.call(context, 'writing');
  expect(spy1).to.have.been.called.once();
});

test('Call this.fs.copy twice for Travis', () => {
  context.options.ci = ['travis'];
  const spy1 = chai.spy.on(context.fs, 'copy');
  TestUtils.call(context, 'writing');
  expect(spy1).to.have.been.called.once();
});

test('Call this.fs.copy twice for Wercker', () => {
  context.options.ci = ['wercker'];
  const spy1 = chai.spy.on(context.fs, 'copy');
  TestUtils.call(context, 'writing');
  expect(spy1).to.have.been.called.once();
});
<commit_msg>Fix typos<commit_after>const test = require('ava');
const spies = require('chai-spies');
const chai = require('chai');
const expect = chai.expect;
chai.use(spies);
const TestUtils = require('fountain-generator').TestUtils;

let context;

test.before(() => {
  context = TestUtils.mock('ci');
  context.templatePath = path => path;
  context.destinationPath = path => path;
  context.fs = {
    copy: () => {}
  };
  require('../../generators/ci/index');
});

test('Call this.fs.copy twice for Jenkins', () => {
  context.options.ci = ['jenkins'];
  const spy1 = chai.spy.on(context.fs, 'copy');
  TestUtils.call(context, 'writing');
  expect(spy1).to.have.been.called.twice();
});

test('Call this.fs.copy once for CircleCi', () => {
  context.options.ci = ['circleci'];
  const spy1 = chai.spy.on(context.fs, 'copy');
  TestUtils.call(context, 'writing');
  expect(spy1).to.have.been.called.once();
});

test('Call this.fs.copy once for Travis', () => {
  context.options.ci = ['travis'];
  const spy1 = chai.spy.on(context.fs, 'copy');
  TestUtils.call(context, 'writing');
  expect(spy1).to.have.been.called.once();
});

test('Call this.fs.copy once for Wercker', () => {
  context.options.ci = ['wercker'];
  const spy1 = chai.spy.on(context.fs, 'copy');
  TestUtils.call(context, 'writing');
  expect(spy1).to.have.been.called.once();
});
<|endoftext|>


<commit_before>/*
 * Rapid Interface Builder (RIB) - A simple WYSIWYG HTML5 app creator
 * Copyright (c) 2011-2012, Intel Corporation.
 *
 * This program is licensed under the terms and conditions of the
 * Apache License, version 2.0.  The full text of the Apache License is at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 */
"use strict";

// Layout view widget

(function($, undefined) {

    $.widget('rib.layoutView', $.rib.baseView, {

        options: {
            iframe: null,
            contentDocument: null,
            customHeaders: {
                'meta': [
                    '<meta http-equiv="cache-control" content="no-cache">'
                ],
                'script': [
                    '',
                    ''
                ],
                'link': [
                    '<link href="src/css/composer.css" rel="stylesheet">'
                ]
            }
        },

        _create: function() {
            var o = this.options,
                e = this.element;

            // Chain up to base class _create()
            $.rib.baseView.prototype._create.call(this);

            this.options.iframe = this.element.find('iframe');
            if (!this.options.iframe.length) {
                this.options.iframe = $('');
            }

            this.options.iframe.addClass(this.widgetName)
                .addClass('flex1')
                .appendTo(this.element);

            this.options.contentDocument =
                $(this.options.iframe[0].contentDocument);

            $(window).resize(this, function(event) {
                var el = event.data.element,
                    doc = event.data.options.contentDocument,
                    iframe = event.data.options.iframe;

                // Nothing to resize if iframe contents has not been loaded
                if (!event || !event.data || !event.data.loaded) return;

                // Force resize of the stage when containing window resizes
                el.height(el.parent().height());
                el.find('div').height(el.parent().height());
                // Force resize of the iframe when containing window resizes
                iframe.height(doc.height());
                iframe.css('min-height',
                            el.height() - 2
                            - parseFloat(iframe.css('margin-top'))
                            - parseFloat(iframe.css('margin-bottom'))
                            - parseFloat(iframe.css('padding-bottom'))
                            - parseFloat(iframe.css('padding-bottom')));
            });

            return this;
        },

        _setOption: function(key, value) {
            // Chain up to base class _setOptions()
            // FIXME: In jquery UI 1.9 and above, instead use
            //    this._super('_setOption', key, value)
            $.rib.baseView.prototype._setOption.apply(this, arguments);

            switch (key) {
                // Should this REALLY be done here, or plugin registration in
                // the "host"... using the functions mapped in widget options?
                case 'model':
                    this._createDocument();
                    this.options.iframe.load(this, this._iframeLoaded);
                    break;
                default:
                    break;
            }
        },

        refresh: function(event, widget) {
            var name, visible;
            widget = widget || event && event.data || this;
            name = (event)?(event.name)?event.name:event.type:'';
            visible = widget.element.data('visible');

            if (!widget.loaded) {
                return;
            }

            if (event && (name !== 'designReset') && !visible) {
                return;
            }

            if ((!event) || (name === 'load' ||
                             name === 'designReset' ||
                             name === 'modelUpdated')) {
                widget._serializeADMDesignToDOM();
/* FIXME: Calling serializeADMSubtreeToDom is not actually forcing the
          the DOM to update, but it should work...

                switch (event.type) {
                    case 'nodeAdded':
                    case 'nodeRemoved':
                        serializeADMSubtreeToDOM(event.parent, null,
                                                 widget._renderer);
                        break;
                    case 'nodeMoved':
                        serializeADMSubtreeToDOM(event.oldParent, null,
                                                 widget._renderer);
                        serializeADMSubtreeToDOM(event.newParent, null,
                                                 widget._renderer);
                        break;
                    case 'propertyChanged':
                        serializeADMSubtreeToDOM(event.node, null,
                                                 widget._renderer);
                        break;
                    default:
                        console.warn(widget.widgetName,
                                     ':: Unexpected modelUpdate type:',
                                     event.type);
                        return;
                        break;
                }
*/
            } else {
                console.warn(widget.widgetName,':: Unexpected event:',name);
                return;
            }

            if (widget.options.contentDocument.length) {
                widget.options.contentDocument[0].defaultView
                    .postMessage('reload', '*');
            } else {
                console.error(widget.widgetName, ':: Missing contentDocument');
            }
        },

        // Private functions
        _iframeLoaded: function(event) {
            event.data.loaded = true;
            event.data.refresh(null, event.data);
            $.rib.enableKeys(event.data.options.contentDocument);
            event.data.options.contentDocument[0].contentEditable = false;
        },

        _createPrimaryTools: function() {
            var doc, html, classes, selector,
                commands = ['undo', 'redo', 'cut', 'copy', 'paste'];

            classes = "buttonStyle primary-tools ui-state-default";
            html = $('
').addClass('hbox').hide();
            $.each(commands, function (index, command) {
                $('<button/>').addClass(classes)
                    .attr('id', 'btn' + command)
                    .appendTo(html);
            });

            doc = $((this.element)[0].ownerDocument);
            selector = "button.primary-tools"
            doc.delegate(selector, "click", jQuery.proxy(function (event) {
                var model = this.options.model;
                if (model) {
                    switch (event.currentTarget.id) {
                    case "btnundo":  model.undo();  break;
                    case "btnredo":  model.redo();  break;
                    case "btncut":   model.cut();   break;
                    case "btncopy":  model.copy();  break;
                    case "btnpaste": model.paste(); break;
                    default:
                        console.warn("Unhandled click on primary tool");
                        break;
                    }
                }
                else {
                    console.warn("No model while attempting undo");
                }
            }, this));

            return html;
        },

        _createSecondaryTools: function() {
            return $(null);
        },

        _selectionChangedHandler: function(event, widget) {
            var uid;
            widget = widget || this;

            // Always un-style currently selected nodes
            widget.options.contentDocument.find('.ui-selected')
                .removeClass('ui-selected');

            // Only apply selection style changes on valid nodes
            if (!event || (!event.uid && !event.node)) {
                return;
            }

            // Normally, ADM node id is provided in event.uid
            uid = event && event.uid;
            // Fallback is to try event.node.getUid()
            uid = uid || (event.node)?event.node.getUid():null;

            if (uid) {
                widget.options.contentDocument
                    .find('.adm-node[data-uid=\''+uid+'\']')
                    .not('[data-role=\'page\']')
                    .addClass('ui-selected').first().each(function() {
                        // Scroll selected node into view
                        setTimeout($.proxy(function() {
                            this.scrollIntoViewIfNeeded()
                        }, this), 100);
                    });
            }
        },

        _activePageChangedHandler: function(event, widget) {
            var win,
                newPage = event && event.page,
                id = newPage && newPage.getProperty('id');

            widget = widget || this;

            // Only change if new page is valid
            if (!newPage) {
                return;
            }

            win = widget.options.contentDocument[0].defaultView;

            if (win && win.$ && win.$.mobile) {
                if (win.$.mobile.activePage &&
                    win.$.mobile.activePage.attr('id') !== id) {
                    win.$.mobile.changePage("#"+id, {transition: "none"});
                }
            }
        },

        _modelUpdatedHandler: function(event, widget) {
            var win, aPage, pageNode;

            widget = widget || this;

            widget.refresh(event, widget);

            win = widget.options.contentDocument[0].defaultView;
            if (win && win.$ && win.$.mobile) {
                aPage = widget.options.model.getActivePage();
                if (aPage) {
                    pageNode = win.$('#' + aPage.getProperty('id'));
                    if (pageNode.length) {
                        win.$.mobile.changePage(pageNode);
                    } else {
                        // TODO: this is okay when last page is deleted, so
                        //       maybe this warning can be removed
                        console.warn("No such page found: ",
                                     aPage.getProperty('id'));
                    }
                } else {
                    win.$.mobile.initializePage();
                }
            } else {
                console.error(widget.widgetName, ':: Missing contentDocument');
            }
        },

        _createDocument: function() {
            var contents, doc;

            if (!this.designRoot) return;

            doc = this.options.contentDocument[0];
            doc.open();
            contents = this._serializeFramework(this.designRoot);
            doc.writeln(contents);
            doc.close();
        },

        _serializeFramework: function() {
            var start, end, ret, headers;

            headers = this._getCustomHeaders();

            start = '<!DOCTYPE html>\n <title>Page Title</title>\n';
            end = "\n\n\n";

            if (headers && headers.length > 0) {
                ret = start + headers.join('\n') + end;
            } else {
                ret = start + end;
            }

            return ret;
        },

        // Great assumptions are being made here that the incoming default
        // headers are already "sorted" and in the order in which they should
        // be inserted into the  node of the document being created...
        _getCustomHeaders: function() {
            var dh = $.rib.getDefaultHeaders(),   // default headers
                ch = this.options.customHeaders, // our custom headers
                m, s;

            $.each(dh, function(idx) {
                if (/<meta/.test(this)) {
                    m = idx+1; // Insert our Meta after LAST one
                    return;
                }
                if (/',
                    ''
                ],
                'link': [
                    '<link href="src/css/composer.css" rel="stylesheet">'
                ]
            }
        },

        _create: function() {
            var o = this.options,
                e = this.element;

            // Chain up to base class _create()
            $.rib.baseView.prototype._create.call(this);

            this.options.iframe = this.element.find('iframe');
            if (!this.options.iframe.length) {
                this.options.iframe = $('');
            }

            this.options.iframe.addClass(this.widgetName)
                .addClass('flex1')
                .appendTo(this.element);

            this.options.contentDocument =
                $(this.options.iframe[0].contentDocument);

            $(window).resize(this, function(event) {
                var el = event.data.element,
                    doc = event.data.options.contentDocument,
                    iframe = event.data.options.iframe;

                // Nothing to resize if iframe contents has not been loaded
                if (!event || !event.data || !event.data.loaded) return;

                // Force resize of the stage when containing window resizes
                el.height(el.parent().height());
                el.find('div').height(el.parent().height());
                // Force resize of the iframe when containing window resizes
                iframe.height(doc.height());
                iframe.css('min-height',
                            el.height() - 2
                            - parseFloat(iframe.css('margin-top'))
                            - parseFloat(iframe.css('margin-bottom'))
                            - parseFloat(iframe.css('padding-bottom'))
                            - parseFloat(iframe.css('padding-bottom')));
            });

            return this;
        },

        _setOption: function(key, value) {
            // Chain up to base class _setOptions()
            // FIXME: In jquery UI 1.9 and above, instead use
            //    this._super('_setOption', key, value)
            $.rib.baseView.prototype._setOption.apply(this, arguments);

            switch (key) {
                // Should this REALLY be done here, or plugin registration in
                // the "host"... using the functions mapped in widget options?
                case 'model':
                    this._createDocument();
                    this.options.iframe.load(this, this._iframeLoaded);
                    break;
                default:
                    break;
            }
        },

        refresh: function(event, widget) {
            var name, visible;
            widget = widget || event && event.data || this;
            name = (event)?(event.name)?event.name:event.type:'';
            visible = widget.element.data('visible');

            if (!widget.loaded) {
                return;
            }

            if (event && (name !== 'designReset') && !visible) {
                return;
            }

            if ((!event) || (name === 'load' ||
                             name === 'designReset' ||
                             name === 'modelUpdated')) {
                widget._serializeADMDesignToDOM();
/* FIXME: Calling serializeADMSubtreeToDom is not actually forcing the
          the DOM to update, but it should work...

                switch (event.type) {
                    case 'nodeAdded':
                    case 'nodeRemoved':
                        serializeADMSubtreeToDOM(event.parent, null,
                                                 widget._renderer);
                        break;
                    case 'nodeMoved':
                        serializeADMSubtreeToDOM(event.oldParent, null,
                                                 widget._renderer);
                        serializeADMSubtreeToDOM(event.newParent, null,
                                                 widget._renderer);
                        break;
                    case 'propertyChanged':
                        serializeADMSubtreeToDOM(event.node, null,
                                                 widget._renderer);
                        break;
                    default:
                        console.warn(widget.widgetName,
                                     ':: Unexpected modelUpdate type:',
                                     event.type);
                        return;
                        break;
                }
*/
            } else {
                console.warn(widget.widgetName,':: Unexpected event:',name);
                return;
            }

            if (widget.options.contentDocument.length) {
                widget.options.contentDocument[0].defaultView
                    .postMessage('reload', '*');
            } else {
                console.error(widget.widgetName, ':: Missing contentDocument');
            }
        },

        // Private functions
        _iframeLoaded: function(event) {
            event.data.loaded = true;
            event.data.refresh(null, event.data);
            $.rib.enableKeys(event.data.options.contentDocument);
            event.data.options.contentDocument[0].contentEditable = false;
        },

        _createPrimaryTools: function() {
            var doc, html, classes, selector,
                commands = ['undo', 'redo', 'cut', 'copy', 'paste'];

            classes = "buttonStyle primary-tools ui-state-default";
            html = $('
').addClass('hbox').hide();
            $.each(commands, function (index, command) {
                $('<button/>').addClass(classes)
                    .attr('id', 'btn' + command)
                    .appendTo(html);
            });

            doc = $((this.element)[0].ownerDocument);
            selector = "button.primary-tools"
            doc.delegate(selector, "click", jQuery.proxy(function (event) {
                var model = this.options.model;
                if (model) {
                    switch (event.currentTarget.id) {
                    case "btnundo":  model.undo();  break;
                    case "btnredo":  model.redo();  break;
                    case "btncut":   model.cut();   break;
                    case "btncopy":  model.copy();  break;
                    case "btnpaste": model.paste(); break;
                    default:
                        console.warn("Unhandled click on primary tool");
                        break;
                    }
                }
                else {
                    console.warn("No model while attempting undo");
                }
            }, this));

            return html;
        },

        _createSecondaryTools: function() {
            return $(null);
        },

        _selectionChangedHandler: function(event, widget) {
            var uid;
            widget = widget || this;

            // Always un-style currently selected nodes
            widget.options.contentDocument.find('.ui-selected')
                .removeClass('ui-selected');

            // Only apply selection style changes on valid nodes
            if (!event || (!event.uid && !event.node)) {
                return;
            }

            // Normally, ADM node id is provided in event.uid
            uid = event && event.uid;
            // Fallback is to try event.node.getUid()
            uid = uid || (event.node)?event.node.getUid():null;

            if (uid) {
                widget.options.contentDocument
                    .find('.adm-node[data-uid=\''+uid+'\']')
                    .not('[data-role=\'page\']')
                    .addClass('ui-selected').first().each(function() {
                        // Scroll selected node into view
                        setTimeout($.proxy(function() {
                            this.scrollIntoViewIfNeeded()
                        }, this), 100);
                    });
            }
        },

        _activePageChangedHandler: function(event, widget) {
            var win,
                newPage = event && event.page,
                id = newPage && newPage.getProperty('id');

            widget = widget || this;

            // Only change if new page is valid
            if (!newPage) {
                return;
            }

            win = widget.options.contentDocument[0].defaultView;

            if (win && win.$ && win.$.mobile) {
                if (win.$.mobile.activePage &&
                    win.$.mobile.activePage.attr('id') !== id) {
                    win.$.mobile.changePage("#"+id, {transition: "none"});
                }
            }
        },

        _modelUpdatedHandler: function(event, widget) {
            var win, aPage, pageNode;

            widget = widget || this;

            widget.refresh(event, widget);

            win = widget.options.contentDocument[0].defaultView;
            if (win && win.$ && win.$.mobile) {
                aPage = widget.options.model.getActivePage();
                if (aPage) {
                    pageNode = win.$('#' + aPage.getProperty('id'));
                    if (pageNode.length) {
                        win.$.mobile.changePage(pageNode);
                    } else {
                        // TODO: this is okay when last page is deleted, so
                        //       maybe this warning can be removed
                        console.warn("No such page found: ",
                                     aPage.getProperty('id'));
                    }
                } else {
                    win.$.mobile.initializePage();
                }
            } else {
                console.error(widget.widgetName, ':: Missing contentDocument');
            }
        },

        _createDocument: function() {
            var contents, doc;

            if (!this.designRoot) return;

            doc = this.options.contentDocument[0];
            doc.open();
            contents = this._serializeFramework(this.designRoot);
            doc.writeln(contents);
            doc.close();
        },

        _serializeFramework: function() {
            var start, end, ret, headers;

            headers = this._getCustomHeaders();

            start = '<!DOCTYPE html>\n <title>Page Title</title>\n';
            end = "\n\n\n";

            if (headers && headers.length > 0) {
                ret = start + headers.join('\n') + end;
            } else {
                ret = start + end;
            }

            return ret;
        },

        // Great assumptions are being made here that the incoming default
        // headers are already "sorted" and in the order in which they should
        // be inserted into the  node of the document being created...
        _getCustomHeaders: function() {
            var dh = $.rib.getDefaultHeaders(),   // default headers
                ch = this.options.customHeaders, // our custom headers
                m, s;

            $.each(dh, function(idx) {
                if (/<meta/.test(this)) {
                    m = idx+1; // Insert our Meta after LAST one
                    return;
                }
                if (/


<commit_before>import MockAdapter from 'axios-mock-adapter';
import assert from 'assert';
import Chai from 'chai';
import Chatwork from '../index';

const chai = Chai.assert;

describe('/contactsのテスト', () => {
  it('GET /contacts', () => {
    const cw = new Chatwork('apiKey');
    const mock = new MockAdapter(cw.instance);

    const mockRes = [
      {
        account_id: 123,
        room_id: 322,
        name: 'John Smith',
        chatwork_id: 'tarochatworkid',
        organization_id: 101,
        organization_name: 'Hello Company',
        department: 'Marketing',
        avatar_image_url: 'https://example.com/abc.png',
      },
    ];

    mock.onGet('/contacts').reply(200, mockRes);

    return cw.listMyContacts().then(res => {
      assert(
        JSON.stringify(res.data) === JSON.stringify(mockRes),
      );
    })
    .catch(err => {
      chai.fail(0, 1, err.message);
    });
  });
});
<commit_msg>Modify expression of mocha in contacts.js<commit_after>import MockAdapter from 'axios-mock-adapter';
import assert from 'assert';
import Chai from 'chai';
import Chatwork from '../index';

const chai = Chai.assert;

describe('/contactsのテスト', () => {
  describe('GET', () => {
    it('/contacts', () => {
      const cw = new Chatwork('apiKey');
      const mock = new MockAdapter(cw.instance);

      const mockRes = [
        {
          account_id: 123,
          room_id: 322,
          name: 'John Smith',
          chatwork_id: 'tarochatworkid',
          organization_id: 101,
          organization_name: 'Hello Company',
          department: 'Marketing',
          avatar_image_url: 'https://example.com/abc.png',
        },
      ];

      mock.onGet('/contacts').reply(200, mockRes);

      return cw.listMyContacts().then(res => {
        assert(
          JSON.stringify(res.data) === JSON.stringify(mockRes),
        );
      })
      .catch(err => {
        chai.fail(0, 1, err.message);
      });
    });
  });
});
<|endoftext|>


<commit_before>var grunt = require('grunt');
var ChromeExtension = require('crx');
var path = require('path');
var fs = require('fs');
var exec = require('child_process').exec;
var extensionConfigs;

exports['crx'] = {
  setUp: function(done) {
    extensionConfigs = {
      "standard": {
        "privateKey": fs.readFileSync("test/data/key.pem"),
        "rootDirectory": "test/data/src/",
        "dest": "test/data/files/test.crx"
      },
      "codebase": {
        "codebase": "http://example.com/files/test-codebase.crx",
        "privateKey": fs.readFileSync("test/data/key.pem"),
        "rootDirectory": "test/data/src/",
        "dest": "test/data/files/test-codebase.crx"
      },
      "exclude": {
        "exclude": [
          "ignore.me",
          "stuff/*"
        ],
        "privateKey": fs.readFileSync("test/data/key.pem"),
        "rootDirectory": "test/data/src/",
        "dest": "test/data/files/test.crx"
      }
    };

    exec('rm -f test/data/files/*', done);
  },
  'helper-crx': {
    'without codebase': function(test){
      var config = extensionConfigs.standard;
      test.expect(4);

      test.doesNotThrow(function(){
        grunt.helper('crx', new ChromeExtension(config), function(){
          test.equal(grunt.file.expandFiles('test/data/files/test.crx').length, 1);
          test.equal(grunt.file.expandFiles('test/data/files/test-codebase.crx').length, 0);
          test.equal(grunt.file.expandFiles('test/data/files/updates.xml').length, 0);

          test.done();
        });
      });
    },
    'with codebase': function(test){
      var config = extensionConfigs.codebase;
      test.expect(3);

      grunt.helper('crx', new ChromeExtension(config), function(){
        test.equal(grunt.file.expandFiles('test/data/files/test.crx').length, 0);
        test.equal(grunt.file.expandFiles('test/data/files/test-codebase.crx').length, 1);
        test.equal(grunt.file.expandFiles('test/data/files/updates.xml').length, 0);

        test.done();
      });
    },
    'excluding files': function(test){
      var config = extensionConfigs.exclude;
      var crx = new ChromeExtension(config);
      test.expect(4);

      grunt.helper('crx', crx, function(){
        //local
        test.equal(grunt.file.expandFiles('test/data/src/stuff/*').length, 1);
        test.equal(grunt.file.expandFiles('test/data/src/*').length, 3);

        //copy
        test.equal(grunt.file.expandFiles(path.join(crx.path + '/stuff/*')).length, 0);
        test.equal(grunt.file.expandFiles(path.join(crx.path + '/*')).length, 2);

        test.done();
      });
    }
  },
  'helper-manifest': {
    'without running crx-helper': function(test){
      var config = extensionConfigs.codebase;
      test.expect(1);

      test.throws(function(){
        grunt.helper('crx-manifest', new ChromeExtension(config));
      });

      test.done();
    },
    'without codebase': function(test){
      var config = extensionConfigs.standard;
      var crx = new ChromeExtension(config);
      test.expect(1);

      grunt.utils.async.series([
        function(done){
          grunt.helper('crx', crx, done);
        },
        function(done){
          test.throws(function(){
            grunt.helper('crx-manifest', crx);
          });

          done();
        }
      ], test.done);
    },
    'with codebase': function(test){
      var config = extensionConfigs.codebase;
      var crx = new ChromeExtension(config);
      test.expect(3);

      grunt.utils.async.series([
        function(done){
          grunt.helper('crx', crx, done);
        },
        function(done){
          grunt.helper('crx-manifest', crx, function(){

            test.equal(grunt.file.expandFiles('test/data/files/test.crx').length, 0);
            test.equal(grunt.file.expandFiles('test/data/files/test-codebase.crx').length, 1);
            test.equal(grunt.file.expandFiles('test/data/files/updates.xml').length, 1);
            done();
          });
        }
      ], test.done);
    }
  }
};
<commit_msg>Destroying properly temporary files<commit_after>var grunt = require('grunt');
var ChromeExtension = require('crx');
var path = require('path');
var fs = require('fs');
var exec = require('child_process').exec;
var extensionConfigs;

exports['crx'] = {
  setUp: function(done) {
    extensionConfigs = {
      "standard": {
        "privateKey": fs.readFileSync("test/data/key.pem"),
        "rootDirectory": "test/data/src/",
        "dest": "test/data/files/test.crx"
      },
      "codebase": {
        "codebase": "http://example.com/files/test-codebase.crx",
        "privateKey": fs.readFileSync("test/data/key.pem"),
        "rootDirectory": "test/data/src/",
        "dest": "test/data/files/test-codebase.crx"
      },
      "exclude": {
        "exclude": [
          "ignore.me",
          "stuff/*"
        ],
        "privateKey": fs.readFileSync("test/data/key.pem"),
        "rootDirectory": "test/data/src/",
        "dest": "test/data/files/test.crx"
      }
    };

    exec('rm -f test/data/files/*', done);
  },
  'helper-crx': {
    'without codebase': function(test){
      var config = extensionConfigs.standard;
      var crx = new ChromeExtension(config);
      test.expect(4);

      test.doesNotThrow(function(){
        grunt.helper('crx', crx, function(){
          test.equal(grunt.file.expandFiles('test/data/files/test.crx').length, 1);
          test.equal(grunt.file.expandFiles('test/data/files/test-codebase.crx').length, 0);
          test.equal(grunt.file.expandFiles('test/data/files/updates.xml').length, 0);

          crx.destroy();
          test.done();
        });
      });
    },
    'with codebase': function(test){
      var config = extensionConfigs.codebase;
      var crx = new ChromeExtension(config);
      test.expect(3);

      grunt.helper('crx', crx, function(){
        test.equal(grunt.file.expandFiles('test/data/files/test.crx').length, 0);
        test.equal(grunt.file.expandFiles('test/data/files/test-codebase.crx').length, 1);
        test.equal(grunt.file.expandFiles('test/data/files/updates.xml').length, 0);

        crx.destroy();
        test.done();
      });
    },
    'excluding files': function(test){
      var config = extensionConfigs.exclude;
      var crx = new ChromeExtension(config);
      test.expect(4);

      grunt.helper('crx', crx, function(){
        //local
        test.equal(grunt.file.expandFiles('test/data/src/stuff/*').length, 1);
        test.equal(grunt.file.expandFiles('test/data/src/*').length, 3);

        //copy
        test.equal(grunt.file.expandFiles(path.join(crx.path + '/stuff/*')).length, 0);
        test.equal(grunt.file.expandFiles(path.join(crx.path + '/*')).length, 2);

        crx.destroy();
        test.done();
      });
    }
  },
  'helper-manifest': {
    'without running crx-helper': function(test){
      var config = extensionConfigs.codebase;
      test.expect(1);

      test.throws(function(){
        grunt.helper('crx-manifest', new ChromeExtension(config));
      });

      test.done();
    },
    'without codebase': function(test){
      var config = extensionConfigs.standard;
      var crx = new ChromeExtension(config);
      test.expect(1);

      grunt.utils.async.series([
        function(done){
          grunt.helper('crx', crx, done);
        },
        function(done){
          test.throws(function(){
            grunt.helper('crx-manifest', crx);
          });

          crx.destroy();
          done();
        }
      ], test.done);
    },
    'with codebase': function(test){
      var config = extensionConfigs.codebase;
      var crx = new ChromeExtension(config);
      test.expect(3);

      grunt.utils.async.series([
        function(done){
          grunt.helper('crx', crx, done);
        },
        function(done){
          grunt.helper('crx-manifest', crx, function(){

            test.equal(grunt.file.expandFiles('test/data/files/test.crx').length, 0);
            test.equal(grunt.file.expandFiles('test/data/files/test-codebase.crx').length, 1);
            test.equal(grunt.file.expandFiles('test/data/files/updates.xml').length, 1);

            crx.destroy();
            done();
          });
        }
      ], test.done);
    }
  }
};
<|endoftext|>


<commit_before>import THREE from 'three';

import ADT from '../../pipeline/adt';
import Chunk from '../../pipeline/adt/chunk';
import DBC from '../../pipeline/dbc';
import M2 from '../../pipeline/m2';
import WDT from '../../pipeline/wdt';
import WMO from '../../pipeline/wmo';

class WorldMap extends THREE.Group {

  static ZEROPOINT = ADT.SIZE * 32;

  static CHUNKS_PER_ROW = 64 * 16;
  static CHUNK_RENDER_RADIUS = 10;

  constructor(data, wdt) {
    super();

    this.data = data;
    this.wdt = wdt;

    this.mapID = this.data.id;
    this.chunkX = null;
    this.chunkY = null;

    this.queuedChunks = new Map();
    this.chunks = new Map();

    this.queuedDoodads = new Map();
    this.doodads = new Map();

    this.queuedWMOs = new Map();
    this.wmos = new Map();
  }

  get internalName() {
    return this.data.internalName;
  }

  render(x, y) {
    const chunkX = Chunk.chunkFor(x);
    const chunkY = Chunk.chunkFor(y);

    if (this.chunkX === chunkX && this.chunkY === chunkY) {
      return;
    }

    this.chunkX = chunkX;
    this.chunkY = chunkY;

    const radius = this.constructor.CHUNK_RENDER_RADIUS;
    const indices = this.chunkIndicesAround(chunkX, chunkY, radius);

    indices.forEach((index) => {
      this.loadChunkByIndex(index);
    });

    this.chunks.forEach((_chunk, index) => {
      if (indices.indexOf(index) === -1) {
        this.unloadChunkByIndex(index);
      }
    });
  }

  chunkIndicesAround(chunkX, chunkY, radius) {
    const perRow = this.constructor.CHUNKS_PER_ROW;

    const base = this.indexFor(chunkX, chunkY);
    const indices = [];

    for (let y = -radius; y <= radius; ++y) {
      for (let x = -radius; x <= radius; ++x) {
        indices.push(base + y * perRow + x);
      }
    }

    return indices;
  }

  loadChunkByIndex(index) {
    if (this.queuedChunks.has(index)) {
      return;
    }

    const perRow = this.constructor.CHUNKS_PER_ROW;
    const chunkX = (index / perRow) | 0;
    const chunkY = index % perRow;

    this.queuedChunks.set(index, Chunk.load(this, chunkX, chunkY).then((chunk) => {
      this.chunks.set(index, chunk);

      chunk.doodadEntries.forEach((entry) => {
        this.loadDoodad(entry);
      });

      chunk.wmoEntries.forEach((entry) => {
        this.loadWMO(entry);
      });

      this.add(chunk);
    }));
  }

  unloadChunkByIndex(index) {
    const chunk = this.chunks.get(index);
    if (!chunk) {
      return;
    }

    // TODO: Unload doodads and WMOs
    this.queuedChunks.delete(index);
    this.chunks.delete(index);
    this.remove(chunk);
  }

  indexFor(chunkX, chunkY) {
    return chunkX * 64 * 16 + chunkY;
  }

  loadDoodad(entry) {
    if (this.queuedDoodads.has(entry.id)) {
      return;
    }

    this.queuedDoodads.set(entry.id, M2.load(entry.filename).then((m2) => {
      m2.position.set(
        -(entry.position.z - this.constructor.ZEROPOINT),
        -(entry.position.x - this.constructor.ZEROPOINT),
        entry.position.y
      );

      m2.rotation.set(
        entry.rotation.x * Math.PI / 180,
        -entry.rotation.z * Math.PI / 180,
        entry.rotation.y * Math.PI / 180
      );

      if (entry.scale !== 1024) {
        const scale = entry.scale / 1024;
        m2.scale.set(scale, scale, scale);
      }

      this.add(m2);

      // TODO: Remove doodad from map on unload
      this.doodads.set(entry.id, m2);

      // Auto-play animation index 0 in doodad, if animations are present
      // TODO: Properly manage doodad animations
      if (m2.animated && m2.animations.length > 0) {
        m2.animations.play(0);
      }
    }));
  }

  loadWMO(entry) {
    if (this.queuedWMOs.has(entry.id)) {
      return;
    }

    this.queuedWMOs.set(entry.id, WMO.load(entry.filename).then((wmo) => {
      wmo.position.set(
        -(entry.position.z - this.constructor.ZEROPOINT),
        -(entry.position.x - this.constructor.ZEROPOINT),
        entry.position.y
      );

      wmo.doodadSet = entry.doodadSet;

      // Provided as (X, Z, -Y)
      wmo.rotation.set(
        entry.rotation.x * Math.PI / 180,
        -entry.rotation.z * Math.PI / 180,
        entry.rotation.y * Math.PI / 180
      );

      this.add(wmo);

      // TODO: Remove WMO from map on unload
      this.wmos.set(entry.id, wmo);
    }));
  }

  animate(delta, camera, cameraRotated) {
    this.animateDoodads(delta, camera, cameraRotated);
  }

  animateDoodads(delta, camera, cameraRotated) {
    this.doodads.forEach((doodad) => {
      if (!doodad.animated) {
        return;
      }

      if (doodad.animations.length > 0) {
        doodad.animations.update(delta);
      }

      if (cameraRotated && doodad.billboards.length > 0) {
        doodad.applyBillboards(camera);
      }

      if (doodad.skeletonHelper) {
        doodad.skeletonHelper.update();
      }
    });
  }

  static load(id) {
    return DBC.load('Map', id).then((data) => {
      const { internalName: name } = data;
      return WDT.load(`World\\Maps\\${name}\\${name}.wdt`).then((wdt) => {
        return new this(data, wdt);
      });
    });
  }

}

export default WorldMap;
<commit_msg>Adjusted WMO and M2 rotations for ADT placements<commit_after>import THREE from 'three';

import ADT from '../../pipeline/adt';
import Chunk from '../../pipeline/adt/chunk';
import DBC from '../../pipeline/dbc';
import M2 from '../../pipeline/m2';
import WDT from '../../pipeline/wdt';
import WMO from '../../pipeline/wmo';

class WorldMap extends THREE.Group {

  static ZEROPOINT = ADT.SIZE * 32;

  static CHUNKS_PER_ROW = 64 * 16;
  static CHUNK_RENDER_RADIUS = 10;

  constructor(data, wdt) {
    super();

    this.data = data;
    this.wdt = wdt;

    this.mapID = this.data.id;
    this.chunkX = null;
    this.chunkY = null;

    this.queuedChunks = new Map();
    this.chunks = new Map();

    this.queuedDoodads = new Map();
    this.doodads = new Map();

    this.queuedWMOs = new Map();
    this.wmos = new Map();
  }

  get internalName() {
    return this.data.internalName;
  }

  render(x, y) {
    const chunkX = Chunk.chunkFor(x);
    const chunkY = Chunk.chunkFor(y);

    if (this.chunkX === chunkX && this.chunkY === chunkY) {
      return;
    }

    this.chunkX = chunkX;
    this.chunkY = chunkY;

    const radius = this.constructor.CHUNK_RENDER_RADIUS;
    const indices = this.chunkIndicesAround(chunkX, chunkY, radius);

    indices.forEach((index) => {
      this.loadChunkByIndex(index);
    });

    this.chunks.forEach((_chunk, index) => {
      if (indices.indexOf(index) === -1) {
        this.unloadChunkByIndex(index);
      }
    });
  }

  chunkIndicesAround(chunkX, chunkY, radius) {
    const perRow = this.constructor.CHUNKS_PER_ROW;

    const base = this.indexFor(chunkX, chunkY);
    const indices = [];

    for (let y = -radius; y <= radius; ++y) {
      for (let x = -radius; x <= radius; ++x) {
        indices.push(base + y * perRow + x);
      }
    }

    return indices;
  }

  loadChunkByIndex(index) {
    if (this.queuedChunks.has(index)) {
      return;
    }

    const perRow = this.constructor.CHUNKS_PER_ROW;
    const chunkX = (index / perRow) | 0;
    const chunkY = index % perRow;

    this.queuedChunks.set(index, Chunk.load(this, chunkX, chunkY).then((chunk) => {
      this.chunks.set(index, chunk);

      chunk.doodadEntries.forEach((entry) => {
        this.loadDoodad(entry);
      });

      chunk.wmoEntries.forEach((entry) => {
        this.loadWMO(entry);
      });

      this.add(chunk);
    }));
  }

  unloadChunkByIndex(index) {
    const chunk = this.chunks.get(index);
    if (!chunk) {
      return;
    }

    // TODO: Unload doodads and WMOs
    this.queuedChunks.delete(index);
    this.chunks.delete(index);
    this.remove(chunk);
  }

  indexFor(chunkX, chunkY) {
    return chunkX * 64 * 16 + chunkY;
  }

  loadDoodad(entry) {
    if (this.queuedDoodads.has(entry.id)) {
      return;
    }

    this.queuedDoodads.set(entry.id, M2.load(entry.filename).then((m2) => {
      m2.position.set(
        -(entry.position.z - this.constructor.ZEROPOINT),
        -(entry.position.x - this.constructor.ZEROPOINT),
        entry.position.y
      );

      m2.rotation.set(
        -entry.rotation.z * Math.PI / 180,
        entry.rotation.x * Math.PI / 180,
        entry.rotation.y * Math.PI / 180
      );

      // Adjust M2 rotation to match Wowser's axes.
      const quat = m2.quaternion;
      quat.set(-quat.x, quat.y, -quat.z, -quat.w);

      if (entry.scale !== 1024) {
        const scale = entry.scale / 1024;
        m2.scale.set(scale, scale, scale);
      }

      this.add(m2);

      // TODO: Remove doodad from map on unload
      this.doodads.set(entry.id, m2);

      // Auto-play animation index 0 in doodad, if animations are present
      // TODO: Properly manage doodad animations
      if (m2.animated && m2.animations.length > 0) {
        m2.animations.play(0);
      }
    }));
  }

  loadWMO(entry) {
    if (this.queuedWMOs.has(entry.id)) {
      return;
    }

    this.queuedWMOs.set(entry.id, WMO.load(entry.filename).then((wmo) => {
      wmo.position.set(
        -(entry.position.z - this.constructor.ZEROPOINT),
        -(entry.position.x - this.constructor.ZEROPOINT),
        entry.position.y
      );

      wmo.doodadSet = entry.doodadSet;

      // Provided as (X, Z, -Y)
      wmo.rotation.set(
        -entry.rotation.z * Math.PI / 180,
        entry.rotation.x * Math.PI / 180,
        entry.rotation.y * Math.PI / 180
      );

      // Adjust WMO rotation to match Wowser's axes.
      const quat = wmo.quaternion;
      quat.set(-quat.x, quat.y, -quat.z, -quat.w);

      this.add(wmo);

      // TODO: Remove WMO from map on unload
      this.wmos.set(entry.id, wmo);
    }));
  }

  animate(delta, camera, cameraRotated) {
    this.animateDoodads(delta, camera, cameraRotated);
  }

  animateDoodads(delta, camera, cameraRotated) {
    this.doodads.forEach((doodad) => {
      if (!doodad.animated) {
        return;
      }

      if (doodad.animations.length > 0) {
        doodad.animations.update(delta);
      }

      if (cameraRotated && doodad.billboards.length > 0) {
        doodad.applyBillboards(camera);
      }

      if (doodad.skeletonHelper) {
        doodad.skeletonHelper.update();
      }
    });
  }

  static load(id) {
    return DBC.load('Map', id).then((data) => {
      const { internalName: name } = data;
      return WDT.load(`World\\Maps\\${name}\\${name}.wdt`).then((wdt) => {
        return new this(data, wdt);
      });
    });
  }

}

export default WorldMap;
<|endoftext|>