<commit_before>/**
 * Implements API class that embeds Jitsi Meet in external applications.
 */
var JitsiMeetExternalAPI = (function () {
    /**
     * The minimum width for the Jitsi Meet frame
     * @type {number}
     */
    var MIN_WIDTH = 400;

    /**
     * The minimum height for the Jitsi Meet frame
     * @type {number}
     */
    var MIN_HEIGHT = 300;

    var scriptSource = (function (scripts) {
        var scripts = document.getElementsByTagName('script'),
            script = scripts[scripts.length - 1];

        if (script.getAttribute.length !== undefined) {
            return script.src
        }

        return script.getAttribute('src', -1)
    }());

    /**
     * Constructs new API instance. Creates iframe element that loads
     * Jitsi Meet.
     * @param domain the domain name of the server that hosts the conference
     * @param room_name the name of the room to join
     * @param width width of the iframe
     * @param height height of the iframe
     * @param parent_node the node that will contain the iframe
     * @constructor
     */
    function JitsiMeetExternalAPI(domain, room_name, width, height, parent_node, is_audio_only) {
        this.parentNode = null;
        if (parent_node) {
            this.parentNode = parent_node;
        }
        else {
            var scriptTag = document.scripts[document.scripts.length - 1];
            this.parentNode = scriptTag.parentNode;
        }

        this.iframeHolder =
            this.parentNode.appendChild(document.createElement("div"));
        this.iframeHolder.id = "jitsiConference" + JitsiMeetExternalAPI.id;

        if (!is_audio_only) {
            if (width < MIN_WIDTH)
                width = MIN_WIDTH;
            if (height < MIN_HEIGHT)
                height = MIN_HEIGHT;
            this.iframeHolder.style.width = "100%";// width + "px";
            this.iframeHolder.style.height = "100%";//height + "px";
        }
        this.frameName = "jitsiConferenceFrame" + JitsiMeetExternalAPI.id;
        this.url = scriptSource.substring(0, scriptSource.lastIndexOf("/")) + "/index.html?node="; // get index.html from same location as this script
        if (room_name)
            this.url += room_name;
        this.url += "#external";
        JitsiMeetExternalAPI.id++;

        this.frame = document.createElement("iframe");
        this.frame.src = this.url;
        this.frame.name = this.frameName;
        this.frame.id = this.frameName;
        if (!is_audio_only) {
            this.frame.width = "100%";
            this.frame.height = "100%";
            this.frame.frameBorder = "0";
            this.frame.setAttribute("allowFullScreen", "true");
        }
        this.frame = this.iframeHolder.appendChild(this.frame);

        this.frameLoaded = false;
        this.initialCommands = [];
        this.eventHandlers = {};
        this.initListeners();
    }

    /**
     * Last id of api object
     * @type {number}
     */
    JitsiMeetExternalAPI.id = 0;

    /**
     * Sends the passed object to Jitsi Meet
     * @param object the object to be sent
     */
    JitsiMeetExternalAPI.prototype.sendMessage = function (object) {
        if (this.frameLoaded) {
            this.frame.contentWindow.postMessage(
                JSON.stringify(object), this.frame.src);
        }
        else {
            this.initialCommands.push(object);
        }

    };

    /**
     * Executes command. The available commands are:
     * displayName - sets the display name of the local participant to the value
     * passed in the arguments array.
     * muteAudio - mutes / unmutes audio with no arguments
     * muteVideo - mutes / unmutes video with no arguments
     * filmStrip - hides / shows the film strip with no arguments
     * If the command doesn't require any arguments the parameter should be set
     * to empty array or it may be omitted.
     * @param name the name of the command
     * @param arguments array of arguments
     */
    JitsiMeetExternalAPI.prototype.executeCommand = function (name,
                                                             argumentsList) {
        var argumentsArray = argumentsList;
        if (!argumentsArray)
            argumentsArray = [];
        var object = { type: "command", action: "execute" };
        object[name] = argumentsArray;
        this.sendMessage(object);
    };

    /**
     * Executes commands. The available commands are:
     * displayName - sets the display name of the local participant to the value
     * passed in the arguments array.
     * muteAudio - mutes / unmutes audio with no arguments
     * muteVideo - mutes / unmutes video with no arguments
     * filmStrip - hides / shows the film strip with no arguments
     * @param object the object with commands to be executed. The keys of the
     * object are the commands that will be executed and the values are the
     * arguments for the command.
     */
    JitsiMeetExternalAPI.prototype.executeCommands = function (object) {
        object.type = "command";
        object.action = "execute";
        this.sendMessage(object);
    };

    /**
     * Adds event listeners to Meet Jitsi. The object key should be the name of the
     * event and value - the listener.
     * Currently we support the following
     * events:
     * incomingMessage - receives event notifications about incoming
     * messages. The listener will receive object with the following structure:
     * {{
     *  "from": from,//JID of the user that sent the message
     *  "nick": nick,//the nickname of the user that sent the message
     *  "message": txt//the text of the message
     * }}
     * outgoingMessage - receives event notifications about outgoing
     * messages. The listener will receive object with the following structure:
     * {{
     *  "message": txt//the text of the message
     * }}
     * displayNameChanged - receives event notifications about display name
     * change. The listener will receive object with the following structure:
     * {{
     * jid: jid,//the JID of the participant that changed his display name
     * displayname: displayName //the new display name
     * }}
     * participantJoined - receives event notifications about new participant.
     * The listener will receive object with the following structure:
     * {{
     * jid: jid //the jid of the participant
     * }}
     * participantLeft - receives event notifications about participant that left room.
     * The listener will receive object with the following structure:
     * {{
     * jid: jid //the jid of the participant
     * }}
     * @param object
     */
    JitsiMeetExternalAPI.prototype.addEventListeners
        = function (object) {

            var message = { type: "event", action: "add", events: [] };
            for (var i in object) {
                message.events.push(i);
                this.eventHandlers[i] = object[i];
            }
            this.sendMessage(message);
        };

    /**
     * Adds event listeners to Meet Jitsi. Currently we support the following
     * events:
     * incomingMessage - receives event notifications about incoming
     * messages. The listener will receive object with the following structure:
     * {{
     *  "from": from,//JID of the user that sent the message
     *  "nick": nick,//the nickname of the user that sent the message
     *  "message": txt//the text of the message
     * }}
     * outgoingMessage - receives event notifications about outgoing
     * messages. The listener will receive object with the following structure:
     * {{
     *  "message": txt//the text of the message
     * }}
     * displayNameChanged - receives event notifications about display name
     * change. The listener will receive object with the following structure:
     * {{
     * jid: jid,//the JID of the participant that changed his display name
     * displayname: displayName //the new display name
     * }}
     * participantJoined - receives event notifications about new participant.
     * The listener will receive object with the following structure:
     * {{
     * jid: jid //the jid of the participant
     * }}
     * participantLeft - receives event notifications about participant that left room.
     * The listener will receive object with the following structure:
     * {{
     * jid: jid //the jid of the participant
     * }}
     * @param event the name of the event
     * @param listener the listener
     */
    JitsiMeetExternalAPI.prototype.addEventListener
        = function (event, listener) {

            var message = { type: "event", action: "add", events: [event] };
            this.eventHandlers[event] = listener;
            this.sendMessage(message);
        };

    /**
     * Removes event listener.
     * @param event the name of the event.
     */
    JitsiMeetExternalAPI.prototype.removeEventListener
        = function (event) {
            if (!this.eventHandlers[event]) {
                console.error("The event " + event + " is not registered.");
                return;
            }
            var message = { type: "event", action: "remove", events: [event] };
            delete this.eventHandlers[event];
            this.sendMessage(message);
        };

    /**
     * Removes event listeners.
     * @param events array with the names of the events.
     */
    JitsiMeetExternalAPI.prototype.removeEventListeners
        = function (events) {
            var eventsArray = [];
            for (var i = 0; i < events.length; i++) {
                var event = events[i];
                if (!this.eventHandlers[event]) {
                    console.error("The event " + event + " is not registered.");
                    continue;
                }
                delete this.eventHandlers[event];
                eventsArray.push(event);
            }

            if (eventsArray.length > 0) {
                this.sendMessage(
                    { type: "event", action: "remove", events: eventsArray });
            }

        };

    /**
     * Processes message events sent from Jitsi Meet
     * @param event the event
     */
    JitsiMeetExternalAPI.prototype.processMessage = function (event) {
        var message;
        try {
            message = JSON.parse(event.data);
        } catch (e) { }

        if (!message.type) {
            console.error("Message without type is received.");
            return;
        }
        switch (message.type) {
            case "system":
                if (message.loaded) {
                    this.onFrameLoaded();
                }
                break;
            case "event":
                if (message.action != "result" ||
                    !message.event || !this.eventHandlers[message.event]) {
                    console.warn("The received event cannot be parsed.");
                    return;
                }
                this.eventHandlers[message.event](message.result);
                break;
            default:
                console.error("Unknown message type.");
                return;
        }

    };

    /**
     * That method is called when the Jitsi Meet is loaded. Executes saved
     * commands that are send before the frame was loaded.
     */
    JitsiMeetExternalAPI.prototype.onFrameLoaded = function () {
        this.frameLoaded = true;
        for (var i = 0; i < this.initialCommands.length; i++) {
            this.sendMessage(this.initialCommands[i]);
        }
        this.initialCommands = null;
    };

    /**
     * Setups the listener for message events from Jitsi Meet.
     */
    JitsiMeetExternalAPI.prototype.initListeners = function () {
        var self = this;
        this.eventListener = function (event) {
            self.processMessage(event);
        };
        if (window.addEventListener) {
            window.addEventListener('message',
                this.eventListener, false);
        }
        else {
            window.attachEvent('onmessage', this.eventListener);
        }
    };

    /**
     * Removes the listeners and removes the Jitsi Meet frame.
     */
    JitsiMeetExternalAPI.prototype.dispose = function () {
        if (window.removeEventListener) {
            window.removeEventListener('message',
                this.eventListener, false);
        }
        else {
            window.detachEvent('onmessage',
                this.eventListener);
        }
        var frame = document.getElementById(this.frameName);
        if (frame)
            frame.src = 'about:blank';
        var self = this;
        window.setTimeout(function () {
            self.iframeHolder.removeChild(self.frame);
            self.iframeHolder.parentNode.removeChild(self.iframeHolder);
        }, 10);
    };

    return JitsiMeetExternalAPI;

})();<commit_msg>minor defensive code added when disposing<commit_after>/**
 * Implements API class that embeds Jitsi Meet in external applications.
 */
var JitsiMeetExternalAPI = (function () {
    /**
     * The minimum width for the Jitsi Meet frame
     * @type {number}
     */
    var MIN_WIDTH = 400;

    /**
     * The minimum height for the Jitsi Meet frame
     * @type {number}
     */
    var MIN_HEIGHT = 300;

    var scriptSource = (function (scripts) {
        var scripts = document.getElementsByTagName('script'),
            script = scripts[scripts.length - 1];

        if (script.getAttribute.length !== undefined) {
            return script.src
        }

        return script.getAttribute('src', -1)
    }());

    /**
     * Constructs new API instance. Creates iframe element that loads
     * Jitsi Meet.
     * @param domain the domain name of the server that hosts the conference
     * @param room_name the name of the room to join
     * @param width width of the iframe
     * @param height height of the iframe
     * @param parent_node the node that will contain the iframe
     * @constructor
     */
    function JitsiMeetExternalAPI(domain, room_name, width, height, parent_node, is_audio_only) {
        this.parentNode = null;
        if (parent_node) {
            this.parentNode = parent_node;
        }
        else {
            var scriptTag = document.scripts[document.scripts.length - 1];
            this.parentNode = scriptTag.parentNode;
        }

        this.iframeHolder =
            this.parentNode.appendChild(document.createElement("div"));
        this.iframeHolder.id = "jitsiConference" + JitsiMeetExternalAPI.id;

        if (!is_audio_only) {
            if (width < MIN_WIDTH)
                width = MIN_WIDTH;
            if (height < MIN_HEIGHT)
                height = MIN_HEIGHT;
            this.iframeHolder.style.width = "100%";// width + "px";
            this.iframeHolder.style.height = "100%";//height + "px";
        }
        this.frameName = "jitsiConferenceFrame" + JitsiMeetExternalAPI.id;
        this.url = scriptSource.substring(0, scriptSource.lastIndexOf("/")) + "/index.html?node="; // get index.html from same location as this script
        if (room_name)
            this.url += room_name;
        this.url += "#external";
        JitsiMeetExternalAPI.id++;

        this.frame = document.createElement("iframe");
        this.frame.src = this.url;
        this.frame.name = this.frameName;
        this.frame.id = this.frameName;
        if (!is_audio_only) {
            this.frame.width = "100%";
            this.frame.height = "100%";
            this.frame.frameBorder = "0";
            this.frame.setAttribute("allowFullScreen", "true");
        }
        this.frame = this.iframeHolder.appendChild(this.frame);

        this.frameLoaded = false;
        this.initialCommands = [];
        this.eventHandlers = {};
        this.initListeners();
    }

    /**
     * Last id of api object
     * @type {number}
     */
    JitsiMeetExternalAPI.id = 0;

    /**
     * Sends the passed object to Jitsi Meet
     * @param object the object to be sent
     */
    JitsiMeetExternalAPI.prototype.sendMessage = function (object) {
        if (this.frameLoaded && this.frame && this.frame.contentWindow) {
            this.frame.contentWindow.postMessage(
                JSON.stringify(object), this.frame.src);
        }
        else if (this.initialCommands) {
            this.initialCommands.push(object);
        }

    };

    /**
     * Executes command. The available commands are:
     * displayName - sets the display name of the local participant to the value
     * passed in the arguments array.
     * muteAudio - mutes / unmutes audio with no arguments
     * muteVideo - mutes / unmutes video with no arguments
     * filmStrip - hides / shows the film strip with no arguments
     * If the command doesn't require any arguments the parameter should be set
     * to empty array or it may be omitted.
     * @param name the name of the command
     * @param arguments array of arguments
     */
    JitsiMeetExternalAPI.prototype.executeCommand = function (name,
                                                             argumentsList) {
        var argumentsArray = argumentsList;
        if (!argumentsArray)
            argumentsArray = [];
        var object = { type: "command", action: "execute" };
        object[name] = argumentsArray;
        this.sendMessage(object);
    };

    /**
     * Executes commands. The available commands are:
     * displayName - sets the display name of the local participant to the value
     * passed in the arguments array.
     * muteAudio - mutes / unmutes audio with no arguments
     * muteVideo - mutes / unmutes video with no arguments
     * filmStrip - hides / shows the film strip with no arguments
     * @param object the object with commands to be executed. The keys of the
     * object are the commands that will be executed and the values are the
     * arguments for the command.
     */
    JitsiMeetExternalAPI.prototype.executeCommands = function (object) {
        object.type = "command";
        object.action = "execute";
        this.sendMessage(object);
    };

    /**
     * Adds event listeners to Meet Jitsi. The object key should be the name of the
     * event and value - the listener.
     * Currently we support the following
     * events:
     * incomingMessage - receives event notifications about incoming
     * messages. The listener will receive object with the following structure:
     * {{
     *  "from": from,//JID of the user that sent the message
     *  "nick": nick,//the nickname of the user that sent the message
     *  "message": txt//the text of the message
     * }}
     * outgoingMessage - receives event notifications about outgoing
     * messages. The listener will receive object with the following structure:
     * {{
     *  "message": txt//the text of the message
     * }}
     * displayNameChanged - receives event notifications about display name
     * change. The listener will receive object with the following structure:
     * {{
     * jid: jid,//the JID of the participant that changed his display name
     * displayname: displayName //the new display name
     * }}
     * participantJoined - receives event notifications about new participant.
     * The listener will receive object with the following structure:
     * {{
     * jid: jid //the jid of the participant
     * }}
     * participantLeft - receives event notifications about participant that left room.
     * The listener will receive object with the following structure:
     * {{
     * jid: jid //the jid of the participant
     * }}
     * @param object
     */
    JitsiMeetExternalAPI.prototype.addEventListeners
        = function (object) {

            var message = { type: "event", action: "add", events: [] };
            for (var i in object) {
                message.events.push(i);
                this.eventHandlers[i] = object[i];
            }
            this.sendMessage(message);
        };

    /**
     * Adds event listeners to Meet Jitsi. Currently we support the following
     * events:
     * incomingMessage - receives event notifications about incoming
     * messages. The listener will receive object with the following structure:
     * {{
     *  "from": from,//JID of the user that sent the message
     *  "nick": nick,//the nickname of the user that sent the message
     *  "message": txt//the text of the message
     * }}
     * outgoingMessage - receives event notifications about outgoing
     * messages. The listener will receive object with the following structure:
     * {{
     *  "message": txt//the text of the message
     * }}
     * displayNameChanged - receives event notifications about display name
     * change. The listener will receive object with the following structure:
     * {{
     * jid: jid,//the JID of the participant that changed his display name
     * displayname: displayName //the new display name
     * }}
     * participantJoined - receives event notifications about new participant.
     * The listener will receive object with the following structure:
     * {{
     * jid: jid //the jid of the participant
     * }}
     * participantLeft - receives event notifications about participant that left room.
     * The listener will receive object with the following structure:
     * {{
     * jid: jid //the jid of the participant
     * }}
     * @param event the name of the event
     * @param listener the listener
     */
    JitsiMeetExternalAPI.prototype.addEventListener
        = function (event, listener) {

            var message = { type: "event", action: "add", events: [event] };
            this.eventHandlers[event] = listener;
            this.sendMessage(message);
        };

    /**
     * Removes event listener.
     * @param event the name of the event.
     */
    JitsiMeetExternalAPI.prototype.removeEventListener
        = function (event) {
            if (!this.eventHandlers[event]) {
                console.error("The event " + event + " is not registered.");
                return;
            }
            var message = { type: "event", action: "remove", events: [event] };
            delete this.eventHandlers[event];
            this.sendMessage(message);
        };

    /**
     * Removes event listeners.
     * @param events array with the names of the events.
     */
    JitsiMeetExternalAPI.prototype.removeEventListeners
        = function (events) {
            var eventsArray = [];
            for (var i = 0; i < events.length; i++) {
                var event = events[i];
                if (!this.eventHandlers[event]) {
                    console.error("The event " + event + " is not registered.");
                    continue;
                }
                delete this.eventHandlers[event];
                eventsArray.push(event);
            }

            if (eventsArray.length > 0) {
                this.sendMessage(
                    { type: "event", action: "remove", events: eventsArray });
            }

        };

    /**
     * Processes message events sent from Jitsi Meet
     * @param event the event
     */
    JitsiMeetExternalAPI.prototype.processMessage = function (event) {
        var message;
        try {
            message = JSON.parse(event.data);
        } catch (e) { }

        if (!message.type) {
            console.error("Message without type is received.");
            return;
        }
        switch (message.type) {
            case "system":
                if (message.loaded) {
                    this.onFrameLoaded();
                }
                break;
            case "event":
                if (message.action != "result" ||
                    !message.event || !this.eventHandlers[message.event]) {
                    console.warn("The received event cannot be parsed.");
                    return;
                }
                this.eventHandlers[message.event](message.result);
                break;
            default:
                console.error("Unknown message type.");
                return;
        }

    };

    /**
     * That method is called when the Jitsi Meet is loaded. Executes saved
     * commands that are send before the frame was loaded.
     */
    JitsiMeetExternalAPI.prototype.onFrameLoaded = function () {
        this.frameLoaded = true;
        for (var i = 0; i < this.initialCommands.length; i++) {
            this.sendMessage(this.initialCommands[i]);
        }
        this.initialCommands = null;
    };

    /**
     * Setups the listener for message events from Jitsi Meet.
     */
    JitsiMeetExternalAPI.prototype.initListeners = function () {
        var self = this;
        this.eventListener = function (event) {
            self.processMessage(event);
        };
        if (window.addEventListener) {
            window.addEventListener('message',
                this.eventListener, false);
        }
        else {
            window.attachEvent('onmessage', this.eventListener);
        }
    };

    /**
     * Removes the listeners and removes the Jitsi Meet frame.
     */
    JitsiMeetExternalAPI.prototype.dispose = function () {
        if (window.removeEventListener) {
            window.removeEventListener('message', this.eventListener, false);
        }
        else {
            window.detachEvent('onmessage', this.eventListener);
        }
        var frame = document.getElementById(this.frameName);
        if (frame) frame.src = 'about:blank';
        var self = this;
        window.setTimeout(function () {
            if (self.frame.parentNode) self.iframeHolder.removeChild(self.frame);
            if (self.iframeHolder.parentNode) self.iframeHolder.parentNode.removeChild(self.iframeHolder);
        }, 10);
    };

    return JitsiMeetExternalAPI;

})();<|endoftext|>


<commit_before>(function(Two) {

  var Arrow = Two.Arrow = function() {

  };

})();
<commit_msg>Delete arrow.js<commit_after><|endoftext|>


<commit_before>import getLocation from '../../utils/getLocation.js';
import error from '../../utils/error.js';
import Node from '../Node.js';
import callHasEffects from './shared/callHasEffects.js';

export default class CallExpression extends Node {
	bind ( scope ) {
		if ( this.callee.type === 'Identifier' ) {
			const declaration = scope.findDeclaration( this.callee.name );

			if ( declaration.isNamespace ) {
				error({
					message: `Cannot call a namespace ('${this.callee.name}')`,
					file: this.module.id,
					pos: this.start,
					loc: getLocation( this.module.code, this.start )
				});
			}

			if ( this.callee.name === 'eval' && declaration.isGlobal ) {
				this.module.bundle.onwarn( `Use of \`eval\` (in ${this.module.id}) is strongly discouraged, as it poses security risks and may cause issues with minification. See https://github.com/rollup/rollup/wiki/Troubleshooting#avoiding-eval for more details` );
			}
		}

		super.bind( scope );
	}

	hasEffects ( scope ) {
		return callHasEffects( scope, this.callee );
	}

	initialise ( scope ) {
		this.module.bundle.dependentExpressions.push( this );
		super.initialise( scope );
	}

	isUsedByBundle () {
		return this.hasEffects( this.findScope() );
	}
}
<commit_msg>Bundle execution is only dependent on program level call expressions<commit_after>import getLocation from '../../utils/getLocation.js';
import error from '../../utils/error.js';
import Node from '../Node.js';
import callHasEffects from './shared/callHasEffects.js';

export default class CallExpression extends Node {
	bind ( scope ) {
		if ( this.callee.type === 'Identifier' ) {
			const declaration = scope.findDeclaration( this.callee.name );

			if ( declaration.isNamespace ) {
				error({
					message: `Cannot call a namespace ('${this.callee.name}')`,
					file: this.module.id,
					pos: this.start,
					loc: getLocation( this.module.code, this.start )
				});
			}

			if ( this.callee.name === 'eval' && declaration.isGlobal ) {
				this.module.bundle.onwarn( `Use of \`eval\` (in ${this.module.id}) is strongly discouraged, as it poses security risks and may cause issues with minification. See https://github.com/rollup/rollup/wiki/Troubleshooting#avoiding-eval for more details` );
			}
		}

		super.bind( scope );
	}

	hasEffects ( scope ) {
		return callHasEffects( scope, this.callee );
	}

	initialise ( scope ) {
		if ( isProgramLevel( this ) ) {
			this.module.bundle.dependentExpressions.push( this );
		}
		super.initialise( scope );
	}

	isUsedByBundle () {
		return this.hasEffects( this.findScope() );
	}
}

function isProgramLevel ( node ) {
	do {
		if ( node.type === 'Program' ) {
			return true;
		}
		node = node.parent;
	} while ( node && !/Function/.test( node.type ) );

	return false;
}
<|endoftext|>


<commit_before>require('lazy-ass');
var check = require('check-more-types');
var calculate = require('./calculate-transform');

function has4floats(arr) {
  return check.array(arr) &&
    arr.length === 4 &&
    arr.every(check.number);
}

function m4(t) {
  return check.array(t) &&
    t.length === 4 &&
    t.every(has4floats);
}

check.mixin(m4);

function identity(t) {
  return check.m4 &&
    t[0][0] === 1 &&
    t[1][1] === 1 &&
    t[2][2] === 1 &&
    t[3][3] === 1;
}

check.mixin(identity);

function numberEqual(a, b) {
  return Math.abs(a - b) < 1e-6;
}

check.mixin(numberEqual);

describe('calculate transform', function () {
  it('is a function', function () {
    la(check.fn(calculate));
  });

  it('calculates identity', function () {
    var corners = [{
      x: 0,
      y: 0,
    }, {
      x: 100,
      y: 0
    }, {
      x: 100,
      y: 50
    }, {
      x: 0,
      y: 50
    }];
    var transform = calculate(corners, corners);
    la(check.fn(transform), 'expected transform function');
    var tr = transform.H;
    la(check.m4(tr), 'found transform matrix', tr);
    la(check.identity(tr), 'transform should be identity', tr);

    corners.forEach(function (corner) {
      var transformed = transform(corner.x, corner.y);
      la(check.numberEqual(transformed.x, corner.x));
      la(check.numberEqual(transformed.y, corner.y));
    });
  });

  it('calculates identity 2', function () {
    var corners = [{
      x: 0,
      y: 0,
    }, {
      x: 100,
      y: 40
    }, {
      x: 100,
      y: 50
    }, {
      x: 0,
      y: 50
    }];
    var transform = calculate(corners, corners);
    la(check.fn(transform), 'found transform fn', transform);

    corners.forEach(function (corner) {
      var transformed = transform(corner.x, corner.y);
      la(check.numberEqual(transformed.x, corner.x));
      la(check.numberEqual(transformed.y, corner.y));
    });
  });

  it('calculates scale', function () {
    var from = [{
      x: 0,
      y: 0,
    }, {
      x: 100,
      y: 0
    }, {
      x: 100,
      y: 50
    }, {
      x: 0,
      y: 50
    }];

    var to = [{
      x: 0,
      y: 0,
    }, {
      x: 200,
      y: 0
    }, {
      x: 200,
      y: 50
    }, {
      x: 0,
      y: 50
    }];

    var transform = calculate(from, to);
    la(check.fn(transform), 'found transform fn', transform);

    from.forEach(function (corner, k) {
      var transformed = transform(corner.x, corner.y);
      var toCorner = to[k];
      la(check.numberEqual(transformed.x, toCorner.x));
      la(check.numberEqual(transformed.y, toCorner.y));
    });
  });

  it('calculates test image', function () {
    // test poster used in README
    // https://github.com/bahmutov/fix-perspective-element
    var from = [{
      x: 470,
      y: 175,
    }, {
      x: 555,
      y: 130
    }, {
      x: 560,
      y: 345
    }, {
      x: 473,
      y: 345
    }];

    var to = [{
      x: 0,
      y: 0,
    }, {
      x: 100,
      y: 0
    }, {
      x: 100,
      y: 200
    }, {
      x: 0,
      y: 200
    }];

    var transform = calculate(from, to);
    la(check.fn(transform), 'found transform fn', transform);

    from.forEach(function (corner, k) {
      var transformed = transform(corner.x, corner.y);
      var toCorner = to[k];
      la(check.numberEqual(transformed.x, toCorner.x));
      la(check.numberEqual(transformed.y, toCorner.y));
    });

    console.log(transform.H);
  });
});
<commit_msg>using top left corner as origin<commit_after>require('lazy-ass');
var check = require('check-more-types');
var calculate = require('./calculate-transform');

function has4floats(arr) {
  return check.array(arr) &&
    arr.length === 4 &&
    arr.every(check.number);
}

function m4(t) {
  return check.array(t) &&
    t.length === 4 &&
    t.every(has4floats);
}

check.mixin(m4);

function identity(t) {
  return check.m4 &&
    t[0][0] === 1 &&
    t[1][1] === 1 &&
    t[2][2] === 1 &&
    t[3][3] === 1;
}

check.mixin(identity);

function numberEqual(a, b) {
  return Math.abs(a - b) < 1e-6;
}

check.mixin(numberEqual);

describe('calculate transform', function () {
  it('is a function', function () {
    la(check.fn(calculate));
  });

  it('calculates identity', function () {
    var corners = [{
      x: 0,
      y: 0,
    }, {
      x: 100,
      y: 0
    }, {
      x: 100,
      y: 50
    }, {
      x: 0,
      y: 50
    }];
    var transform = calculate(corners, corners);
    la(check.fn(transform), 'expected transform function');
    var tr = transform.H;
    la(check.m4(tr), 'found transform matrix', tr);
    la(check.identity(tr), 'transform should be identity', tr);

    corners.forEach(function (corner) {
      var transformed = transform(corner.x, corner.y);
      la(check.numberEqual(transformed.x, corner.x));
      la(check.numberEqual(transformed.y, corner.y));
    });
  });

  it('calculates identity 2', function () {
    var corners = [{
      x: 0,
      y: 0,
    }, {
      x: 100,
      y: 40
    }, {
      x: 100,
      y: 50
    }, {
      x: 0,
      y: 50
    }];
    var transform = calculate(corners, corners);
    la(check.fn(transform), 'found transform fn', transform);

    corners.forEach(function (corner) {
      var transformed = transform(corner.x, corner.y);
      la(check.numberEqual(transformed.x, corner.x));
      la(check.numberEqual(transformed.y, corner.y));
    });
  });

  it('calculates scale', function () {
    var from = [{
      x: 0,
      y: 0,
    }, {
      x: 100,
      y: 0
    }, {
      x: 100,
      y: 50
    }, {
      x: 0,
      y: 50
    }];

    var to = [{
      x: 0,
      y: 0,
    }, {
      x: 200,
      y: 0
    }, {
      x: 200,
      y: 50
    }, {
      x: 0,
      y: 50
    }];

    var transform = calculate(from, to);
    la(check.fn(transform), 'found transform fn', transform);

    from.forEach(function (corner, k) {
      var transformed = transform(corner.x, corner.y);
      var toCorner = to[k];
      la(check.numberEqual(transformed.x, toCorner.x));
      la(check.numberEqual(transformed.y, toCorner.y));
    });
  });

  it('calculates test image', function () {
    // test poster used in README
    // https://github.com/bahmutov/fix-perspective-element
    var from = [{
      x: 470,
      y: 175,
    }, {
      x: 555,
      y: 130
    }, {
      x: 560,
      y: 345
    }, {
      x: 473,
      y: 345
    }];

    var to = [{
      x: 0,
      y: 0,
    }, {
      x: 100,
      y: 0
    }, {
      x: 100,
      y: 200
    }, {
      x: 0,
      y: 200
    }];

    var topLeft = from[0];
    var fromTopLeft = from.map(function (p) {
      return {
        x: p.x - topLeft.x,
        y: p.y - topLeft.y,
      };
    });

    var transform = calculate(fromTopLeft, to);
    la(check.fn(transform), 'found transform fn', transform);
    console.log(transform.H);

    from.forEach(function (corner, k) {
      var toTopLeft = {
        x: corner.x - topLeft.x,
        y: corner.y - topLeft.y
      };
      var transformed = transform(toTopLeft.x, toTopLeft.y);
      var toCorner = to[k];
      la(check.numberEqual(transformed.x, toCorner.x));
      la(check.numberEqual(transformed.y, toCorner.y));
    });

  });
});
<|endoftext|>


<commit_before>/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2018 Photon Storm Ltd.
 * @license      {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
 */

var Camera = require('./Camera');
var Class = require('../../utils/Class');
var GetFastValue = require('../../utils/object/GetFastValue');
var PluginManager = require('../../boot/PluginManager');
var RectangleContains = require('../../geom/rectangle/Contains');

/**
 * @classdesc
 * [description]
 *
 * @class CameraManager
 * @memberOf Phaser.Cameras.Scene2D
 * @constructor
 * @since 3.0.0
 *
 * @param {Phaser.Scene} scene - The Scene that owns the Camera Manager plugin.
 */
var CameraManager = new Class({

    initialize:

    function CameraManager (scene)
    {
        /**
         * The Scene that owns the Camera Manager plugin.
         *
         * @name Phaser.Cameras.Scene2D.CameraManager#scene
         * @type {Phaser.Scene}
         * @since 3.0.0
         */
        this.scene = scene;

        /**
         * A reference to the Scene.Systems handler for the Scene that owns the Camera Manager.
         *
         * @name Phaser.Cameras.Scene2D.CameraManager#systems
         * @type {Phaser.Scenes.Systems}
         * @since 3.0.0
         */
        this.systems = scene.sys;

        if (!scene.sys.settings.isBooted)
        {
            scene.sys.events.once('boot', this.boot, this);
        }

        /**
         * The current Camera ID.
         *
         * @name Phaser.Cameras.Scene2D.CameraManager#currentCameraId
         * @type {number}
         * @default 1
         * @readOnly
         * @since 3.0.0
         */
        this.currentCameraId = 1;

        /**
         * An Array of the Camera objects being managed by this Camera Manager.
         *
         * @name Phaser.Cameras.Scene2D.CameraManager#cameras
         * @type {Phaser.Cameras.Scene2D.Camera[]}
         * @since 3.0.0
         */
        this.cameras = [];

        /**
         * A pool of Camera objects available to be used by the Camera Manager.
         *
         * @name Phaser.Cameras.Scene2D.CameraManager#cameraPool
         * @type {Phaser.Cameras.Scene2D.Camera[]}
         * @since 3.0.0
         */
        this.cameraPool = [];

        if (scene.sys.settings.cameras)
        {
            //  We have cameras to create
            this.fromJSON(scene.sys.settings.cameras);
        }
        else
        {
            //  Make one
            this.add();
        }

        /**
         * The default Camera in the Camera Manager.
         *
         * @name Phaser.Cameras.Scene2D.CameraManager#main
         * @type {Phaser.Cameras.Scene2D.Camera}
         * @since 3.0.0
         */
        this.main = this.cameras[0];

        /**
         * This scale affects all cameras. It's used by Scale Manager.
         *
         * @name Phaser.Cameras.Scene2D.CameraManager#baseScale
         * @type {number}
         * @since 3.0.0
         */
        this.baseScale = 1.0;
    },

    /**
     * Called when the Camera Manager boots.
     * Starts the event listeners running.
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#boot
     * @since 3.0.0
     */
    boot: function ()
    {
        var eventEmitter = this.systems.events;

        eventEmitter.on('update', this.update, this);
        eventEmitter.on('shutdown', this.shutdown, this);
        eventEmitter.on('destroy', this.destroy, this);
    },

    /**
     * [description]
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#add
     * @since 3.0.0
     *
     * @param {number} [x=0] - [description]
     * @param {number} [y=0] - [description]
     * @param {number} [width] - [description]
     * @param {number} [height] - [description]
     * @param {boolean} [makeMain=false] - [description]
     * @param {string} [name=''] - [description]
     *
     * @return {Phaser.Cameras.Scene2D.Camera} [description]
     */
    add: function (x, y, width, height, makeMain, name)
    {
        if (x === undefined) { x = 0; }
        if (y === undefined) { y = 0; }
        if (width === undefined) { width = this.scene.sys.game.config.width; }
        if (height === undefined) { height = this.scene.sys.game.config.height; }
        if (makeMain === undefined) { makeMain = false; }
        if (name === undefined) { name = ''; }

        var camera = null;

        if (this.cameraPool.length > 0)
        {
            camera = this.cameraPool.pop();

            camera.setViewport(x, y, width, height);
        }
        else
        {
            camera = new Camera(x, y, width, height);
        }

        camera.setName(name);
        camera.setScene(this.scene);

        this.cameras.push(camera);

        if (makeMain)
        {
            this.main = camera;
        }

        camera._id = this.currentCameraId;

        this.currentCameraId = this.currentCameraId << 1;

        return camera;
    },

    /**
     * [description]
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#addExisting
     * @since 3.0.0
     *
     * @param {Phaser.Cameras.Scene2D.Camera} camera - [description]
     *
     * @return {Phaser.Cameras.Scene2D.Camera} [description]
     */
    addExisting: function (camera)
    {
        var index = this.cameras.indexOf(camera);
        var poolIndex = this.cameraPool.indexOf(camera);

        if (index < 0 && poolIndex >= 0)
        {
            this.cameras.push(camera);
            this.cameraPool.slice(poolIndex, 1);
            return camera;
        }

        return null;
    },

    /*
    {
        cameras: [
            {
                name: string
                x: int
                y: int
                width: int
                height: int
                zoom: float
                rotation: float
                roundPixels: bool
                scrollX: float
                scrollY: float
                backgroundColor: string
                bounds: {
                    x: int
                    y: int
                    width: int
                    height: int
                }
            }
        ]
    }
    */

    /**
     * [description]
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#fromJSON
     * @since 3.0.0
     *
     * @param {[type]} config - [description]
     *
     * @return {[type]} [description]
     */
    fromJSON: function (config)
    {
        if (!Array.isArray(config))
        {
            config = [ config ];
        }

        var gameWidth = this.scene.sys.game.config.width;
        var gameHeight = this.scene.sys.game.config.height;

        for (var i = 0; i < config.length; i++)
        {
            var cameraConfig = config[i];

            var x = GetFastValue(cameraConfig, 'x', 0);
            var y = GetFastValue(cameraConfig, 'y', 0);
            var width = GetFastValue(cameraConfig, 'width', gameWidth);
            var height = GetFastValue(cameraConfig, 'height', gameHeight);

            var camera = this.add(x, y, width, height);

            //  Direct properties
            camera.name = GetFastValue(cameraConfig, 'name', '');
            camera.zoom = GetFastValue(cameraConfig, 'zoom', 1);
            camera.rotation = GetFastValue(cameraConfig, 'rotation', 0);
            camera.scrollX = GetFastValue(cameraConfig, 'scrollX', 0);
            camera.scrollY = GetFastValue(cameraConfig, 'scrollY', 0);
            camera.roundPixels = GetFastValue(cameraConfig, 'roundPixels', false);

            // Background Color

            var backgroundColor = GetFastValue(cameraConfig, 'backgroundColor', false);

            if (backgroundColor)
            {
                camera.setBackgroundColor(backgroundColor);
            }

            //  Bounds

            var boundsConfig = GetFastValue(cameraConfig, 'bounds', null);

            if (boundsConfig)
            {
                var bx = GetFastValue(boundsConfig, 'x', 0);
                var by = GetFastValue(boundsConfig, 'y', 0);
                var bwidth = GetFastValue(boundsConfig, 'width', gameWidth);
                var bheight = GetFastValue(boundsConfig, 'height', gameHeight);

                camera.setBounds(bx, by, bwidth, bheight);
            }
        }

        return this;
    },

    /**
     * [description]
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#getCamera
     * @since 3.0.0
     *
     * @param {string} name - [description]
     *
     * @return {Phaser.Cameras.Scene2D.Camera} [description]
     */
    getCamera: function (name)
    {
        this.cameras.forEach(function (camera)
        {
            if (camera.name === name)
            {
                return camera;
            }
        });

        return null;
    },

    /**
     * [description]
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#getCameraBelowPointer
     * @since 3.0.0
     *
     * @param {[type]} pointer - [description]
     *
     * @return {Phaser.Cameras.Scene2D.Camera} [description]
     */
    getCameraBelowPointer: function (pointer)
    {
        var cameras = this.cameras;

        //  Start from the most recently added camera (the 'top' camera)
        for (var i = cameras.length - 1; i >= 0; i--)
        {
            var camera = cameras[i];

            if (camera.inputEnabled && RectangleContains(camera, pointer.x, pointer.y))
            {
                return camera;
            }
        }
    },

    /**
     * [description]
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#remove
     * @since 3.0.0
     *
     * @param {Phaser.Cameras.Scene2D.Camera} camera - [description]
     */
    remove: function (camera)
    {
        var cameraIndex = this.cameras.indexOf(camera);

        if (cameraIndex >= 0 && this.cameras.length > 1)
        {
            this.cameraPool.push(this.cameras[cameraIndex]);
            this.cameras.splice(cameraIndex, 1);

            if (this.main === camera)
            {
                this.main = this.cameras[0];
            }
        }
    },

    /**
     * [description]
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#render
     * @since 3.0.0
     *
     * @param {[type]} renderer - [description]
     * @param {[type]} children - [description]
     * @param {[type]} interpolation - [description]
     */
    render: function (renderer, children, interpolation)
    {
        var cameras = this.cameras;
        var baseScale = this.baseScale;

        for (var i = 0, l = cameras.length; i < l; ++i)
        {
            var camera = cameras[i];

            camera.preRender(baseScale, renderer.config.resolution);

            renderer.render(this.scene, children, interpolation, camera);
        }
    },

    /**
     * [description]
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#resetAll
     * @since 3.0.0
     *
     * @return {Phaser.Cameras.Scene2D.Camera} [description]
     */
    resetAll: function ()
    {
        while (this.cameras.length > 0)
        {
            this.cameraPool.push(this.cameras.pop());
        }

        this.main = this.add();

        return this.main;
    },

    /**
     * [description]
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#update
     * @since 3.0.0
     *
     * @param {number} timestep - [description]
     * @param {number} delta - [description]
     */
    update: function (timestep, delta)
    {
        for (var i = 0, l = this.cameras.length; i < l; ++i)
        {
            this.cameras[i].update(timestep, delta);
        }
    },

    /**
     * [description]
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#shutdown
     * @since 3.0.0
     */
    shutdown: function ()
    {
        //  TODO
    },

    /**
     * [description]
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#destroy
     * @since 3.0.0
     */
    destroy: function ()
    {
        this.main = undefined;

        for (var i = 0; i < this.cameras.length; i++)
        {
            this.cameras[i].destroy();
        }

        for (i = 0; i < this.cameraPool.length; i++)
        {
            this.cameraPool[i].destroy();
        }

        this.cameras = [];
        this.cameraPool = [];
        this.scene = undefined;
    }

});

PluginManager.register('CameraManager', CameraManager, 'cameras');

module.exports = CameraManager;
<commit_msg>Added CameraManager.resize method.<commit_after>/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2018 Photon Storm Ltd.
 * @license      {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
 */

var Camera = require('./Camera');
var Class = require('../../utils/Class');
var GetFastValue = require('../../utils/object/GetFastValue');
var PluginManager = require('../../boot/PluginManager');
var RectangleContains = require('../../geom/rectangle/Contains');

/**
 * @classdesc
 * [description]
 *
 * @class CameraManager
 * @memberOf Phaser.Cameras.Scene2D
 * @constructor
 * @since 3.0.0
 *
 * @param {Phaser.Scene} scene - The Scene that owns the Camera Manager plugin.
 */
var CameraManager = new Class({

    initialize:

    function CameraManager (scene)
    {
        /**
         * The Scene that owns the Camera Manager plugin.
         *
         * @name Phaser.Cameras.Scene2D.CameraManager#scene
         * @type {Phaser.Scene}
         * @since 3.0.0
         */
        this.scene = scene;

        /**
         * A reference to the Scene.Systems handler for the Scene that owns the Camera Manager.
         *
         * @name Phaser.Cameras.Scene2D.CameraManager#systems
         * @type {Phaser.Scenes.Systems}
         * @since 3.0.0
         */
        this.systems = scene.sys;

        if (!scene.sys.settings.isBooted)
        {
            scene.sys.events.once('boot', this.boot, this);
        }

        /**
         * The current Camera ID.
         *
         * @name Phaser.Cameras.Scene2D.CameraManager#currentCameraId
         * @type {number}
         * @default 1
         * @readOnly
         * @since 3.0.0
         */
        this.currentCameraId = 1;

        /**
         * An Array of the Camera objects being managed by this Camera Manager.
         *
         * @name Phaser.Cameras.Scene2D.CameraManager#cameras
         * @type {Phaser.Cameras.Scene2D.Camera[]}
         * @since 3.0.0
         */
        this.cameras = [];

        /**
         * A pool of Camera objects available to be used by the Camera Manager.
         *
         * @name Phaser.Cameras.Scene2D.CameraManager#cameraPool
         * @type {Phaser.Cameras.Scene2D.Camera[]}
         * @since 3.0.0
         */
        this.cameraPool = [];

        if (scene.sys.settings.cameras)
        {
            //  We have cameras to create
            this.fromJSON(scene.sys.settings.cameras);
        }
        else
        {
            //  Make one
            this.add();
        }

        /**
         * The default Camera in the Camera Manager.
         *
         * @name Phaser.Cameras.Scene2D.CameraManager#main
         * @type {Phaser.Cameras.Scene2D.Camera}
         * @since 3.0.0
         */
        this.main = this.cameras[0];

        /**
         * This scale affects all cameras. It's used by Scale Manager.
         *
         * @name Phaser.Cameras.Scene2D.CameraManager#baseScale
         * @type {number}
         * @since 3.0.0
         */
        this.baseScale = 1.0;
    },

    /**
     * Called when the Camera Manager boots.
     * Starts the event listeners running.
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#boot
     * @since 3.0.0
     */
    boot: function ()
    {
        var eventEmitter = this.systems.events;

        eventEmitter.on('update', this.update, this);
        eventEmitter.on('shutdown', this.shutdown, this);
        eventEmitter.on('destroy', this.destroy, this);
    },

    /**
     * [description]
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#add
     * @since 3.0.0
     *
     * @param {number} [x=0] - [description]
     * @param {number} [y=0] - [description]
     * @param {number} [width] - [description]
     * @param {number} [height] - [description]
     * @param {boolean} [makeMain=false] - [description]
     * @param {string} [name=''] - [description]
     *
     * @return {Phaser.Cameras.Scene2D.Camera} [description]
     */
    add: function (x, y, width, height, makeMain, name)
    {
        if (x === undefined) { x = 0; }
        if (y === undefined) { y = 0; }
        if (width === undefined) { width = this.scene.sys.game.config.width; }
        if (height === undefined) { height = this.scene.sys.game.config.height; }
        if (makeMain === undefined) { makeMain = false; }
        if (name === undefined) { name = ''; }

        var camera = null;

        if (this.cameraPool.length > 0)
        {
            camera = this.cameraPool.pop();

            camera.setViewport(x, y, width, height);
        }
        else
        {
            camera = new Camera(x, y, width, height);
        }

        camera.setName(name);
        camera.setScene(this.scene);

        this.cameras.push(camera);

        if (makeMain)
        {
            this.main = camera;
        }

        camera._id = this.currentCameraId;

        this.currentCameraId = this.currentCameraId << 1;

        return camera;
    },

    /**
     * [description]
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#addExisting
     * @since 3.0.0
     *
     * @param {Phaser.Cameras.Scene2D.Camera} camera - [description]
     *
     * @return {Phaser.Cameras.Scene2D.Camera} [description]
     */
    addExisting: function (camera)
    {
        var index = this.cameras.indexOf(camera);
        var poolIndex = this.cameraPool.indexOf(camera);

        if (index < 0 && poolIndex >= 0)
        {
            this.cameras.push(camera);
            this.cameraPool.slice(poolIndex, 1);
            return camera;
        }

        return null;
    },

    /*
    {
        cameras: [
            {
                name: string
                x: int
                y: int
                width: int
                height: int
                zoom: float
                rotation: float
                roundPixels: bool
                scrollX: float
                scrollY: float
                backgroundColor: string
                bounds: {
                    x: int
                    y: int
                    width: int
                    height: int
                }
            }
        ]
    }
    */

    /**
     * [description]
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#fromJSON
     * @since 3.0.0
     *
     * @param {[type]} config - [description]
     *
     * @return {[type]} [description]
     */
    fromJSON: function (config)
    {
        if (!Array.isArray(config))
        {
            config = [ config ];
        }

        var gameWidth = this.scene.sys.game.config.width;
        var gameHeight = this.scene.sys.game.config.height;

        for (var i = 0; i < config.length; i++)
        {
            var cameraConfig = config[i];

            var x = GetFastValue(cameraConfig, 'x', 0);
            var y = GetFastValue(cameraConfig, 'y', 0);
            var width = GetFastValue(cameraConfig, 'width', gameWidth);
            var height = GetFastValue(cameraConfig, 'height', gameHeight);

            var camera = this.add(x, y, width, height);

            //  Direct properties
            camera.name = GetFastValue(cameraConfig, 'name', '');
            camera.zoom = GetFastValue(cameraConfig, 'zoom', 1);
            camera.rotation = GetFastValue(cameraConfig, 'rotation', 0);
            camera.scrollX = GetFastValue(cameraConfig, 'scrollX', 0);
            camera.scrollY = GetFastValue(cameraConfig, 'scrollY', 0);
            camera.roundPixels = GetFastValue(cameraConfig, 'roundPixels', false);

            // Background Color

            var backgroundColor = GetFastValue(cameraConfig, 'backgroundColor', false);

            if (backgroundColor)
            {
                camera.setBackgroundColor(backgroundColor);
            }

            //  Bounds

            var boundsConfig = GetFastValue(cameraConfig, 'bounds', null);

            if (boundsConfig)
            {
                var bx = GetFastValue(boundsConfig, 'x', 0);
                var by = GetFastValue(boundsConfig, 'y', 0);
                var bwidth = GetFastValue(boundsConfig, 'width', gameWidth);
                var bheight = GetFastValue(boundsConfig, 'height', gameHeight);

                camera.setBounds(bx, by, bwidth, bheight);
            }
        }

        return this;
    },

    /**
     * [description]
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#getCamera
     * @since 3.0.0
     *
     * @param {string} name - [description]
     *
     * @return {Phaser.Cameras.Scene2D.Camera} [description]
     */
    getCamera: function (name)
    {
        this.cameras.forEach(function (camera)
        {
            if (camera.name === name)
            {
                return camera;
            }
        });

        return null;
    },

    /**
     * [description]
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#getCameraBelowPointer
     * @since 3.0.0
     *
     * @param {[type]} pointer - [description]
     *
     * @return {Phaser.Cameras.Scene2D.Camera} [description]
     */
    getCameraBelowPointer: function (pointer)
    {
        var cameras = this.cameras;

        //  Start from the most recently added camera (the 'top' camera)
        for (var i = cameras.length - 1; i >= 0; i--)
        {
            var camera = cameras[i];

            if (camera.inputEnabled && RectangleContains(camera, pointer.x, pointer.y))
            {
                return camera;
            }
        }
    },

    /**
     * [description]
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#remove
     * @since 3.0.0
     *
     * @param {Phaser.Cameras.Scene2D.Camera} camera - [description]
     */
    remove: function (camera)
    {
        var cameraIndex = this.cameras.indexOf(camera);

        if (cameraIndex >= 0 && this.cameras.length > 1)
        {
            this.cameraPool.push(this.cameras[cameraIndex]);
            this.cameras.splice(cameraIndex, 1);

            if (this.main === camera)
            {
                this.main = this.cameras[0];
            }
        }
    },

    /**
     * [description]
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#render
     * @since 3.0.0
     *
     * @param {[type]} renderer - [description]
     * @param {[type]} children - [description]
     * @param {[type]} interpolation - [description]
     */
    render: function (renderer, children, interpolation)
    {
        var cameras = this.cameras;
        var baseScale = this.baseScale;

        for (var i = 0, l = cameras.length; i < l; ++i)
        {
            var camera = cameras[i];

            camera.preRender(baseScale, renderer.config.resolution);

            renderer.render(this.scene, children, interpolation, camera);
        }
    },

    /**
     * [description]
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#resetAll
     * @since 3.0.0
     *
     * @return {Phaser.Cameras.Scene2D.Camera} [description]
     */
    resetAll: function ()
    {
        while (this.cameras.length > 0)
        {
            this.cameraPool.push(this.cameras.pop());
        }

        this.main = this.add();

        return this.main;
    },

    /**
     * [description]
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#update
     * @since 3.0.0
     *
     * @param {number} timestep - [description]
     * @param {number} delta - [description]
     */
    update: function (timestep, delta)
    {
        for (var i = 0, l = this.cameras.length; i < l; ++i)
        {
            this.cameras[i].update(timestep, delta);
        }
    },

    /**
     * Resizes all cameras to the given dimensions.
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#resize
     * @since 3.2.0
     *
     * @param {number} width - The new width of the camera.
     * @param {number} height - The new height of the camera.
     */
    resize: function (width, height)
    {
        for (var i = 0, l = this.cameras.length; i < l; ++i)
        {
            this.cameras[i].setSize(width, height);
        }
    },

    /**
     * [description]
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#shutdown
     * @since 3.0.0
     */
    shutdown: function ()
    {
        //  TODO
    },

    /**
     * [description]
     *
     * @method Phaser.Cameras.Scene2D.CameraManager#destroy
     * @since 3.0.0
     */
    destroy: function ()
    {
        this.main = undefined;

        for (var i = 0; i < this.cameras.length; i++)
        {
            this.cameras[i].destroy();
        }

        for (i = 0; i < this.cameraPool.length; i++)
        {
            this.cameraPool[i].destroy();
        }

        this.cameras = [];
        this.cameraPool = [];
        this.scene = undefined;
    }

});

PluginManager.register('CameraManager', CameraManager, 'cameras');

module.exports = CameraManager;
<|endoftext|>


<commit_before>"use strict";

var $ = require('jquery'),
    Marionette = require('../../shim/backbone.marionette'),
    App = require('../app'),
    models = require('./models'),
    coreModels = require('../core/models'),
    coreViews = require('../core/views'),
    chart = require('../core/chart'),
    windowTmpl = require('./templates/window.html'),
    messageTmpl = require('./templates/message.html'),
    detailsTmpl = require('./templates/details.html'),
    tableTmpl = require('./templates/table.html'),
    tableRowTmpl = require('./templates/tableRow.html'),
    tabPanelTmpl = require('./templates/tabPanel.html'),
    tabContentTmpl = require('./templates/tabContent.html'),
    barChartTmpl = require('../core/templates/barChart.html');

var AnalyzeWindow = Marionette.LayoutView.extend({
    id: 'analyze-output-wrapper',
    template: windowTmpl,

    regions: {
        headerRegion: '#analyze-header-region',
        detailsRegion: {
            el: '#analyze-details-region'
        }
    },

    onShow: function() {
        this.showHeaderRegion();
        this.showAnalyzingMessage();

        var self = this;

        if (!this.model.get('result')) {
            var taskHelper = {
                pollSuccess: function() {
                    self.showDetailsRegion();
                },

                pollFailure: function() {
                    self.showErrorMessage();
                },

                startFailure: function() {
                    self.showErrorMessage();
                }
            };
            this.model.start(taskHelper);
        } else {
            this.lock = $.Deferred();
            this.lock.done(function() {
                self.showDetailsRegion();
            });
        }
    },

    showHeaderRegion: function() {
        this.headerRegion.show(new coreViews.AreaOfInterestView({
            App: App,
            model: new coreModels.AreaOfInterestModel({
                shape: this.model.get('area_of_interest'),
                can_go_back: true,
                next_label: 'Model',
                url: 'model'
            })
        }));
    },

    showAnalyzingMessage: function() {
        var messageModel = new models.AnalyzeMessageModel();
        messageModel.setAnalyzing();
        this.detailsRegion.show(new MessageView({
            model: messageModel
        }));
    },

    showErrorMessage: function() {
        var messageModel = new models.AnalyzeMessageModel();
        messageModel.setError();
        this.detailsRegion.show(new MessageView({
            model: messageModel
        }));
    },

    showDetailsRegion: function() {
        var results = JSON.parse(this.model.get('result'));

        if (!this.isDestroyed) {
            this.detailsRegion.show(new DetailsView({
                collection: new models.LayerCollection(results)
            }));
        }
    },

    transitionInCss: {
        height: '0%'
    },

    animateIn: function() {
        var self = this;

        this.$el.animate({ height: '55%' }, 200, function() {
            self.trigger('animateIn');
            App.map.set('halfSize', true);
        });
        if (this.lock !== undefined) {
            this.lock.resolve();
        }
    },

    animateOut: function() {
        var self = this;

        App.map.set('halfSize', false);
        this.$el.animate({ height: '0%' }, 200, function() {
            self.trigger('animateOut');
        });
    }
});

var MessageView = Marionette.ItemView.extend({
    template: messageTmpl,
    className: 'analyze-message-region'
});

var DetailsView = Marionette.LayoutView.extend({
    template: detailsTmpl,
    regions: {
        panelsRegion: '.tab-panels-region',
        contentRegion: '.tab-contents-region'
    },

    onShow: function() {
        this.panelsRegion.show(new TabPanelsView({
            collection: this.collection
        }));

        this.contentRegion.show(new TabContentsView({
            collection: this.collection
        }));
    }
});

var TabPanelView = Marionette.ItemView.extend({
    tagName: 'li',
    template: tabPanelTmpl,
    attributes: {
        role: 'presentation'
    }
});

var TabPanelsView = Marionette.CollectionView.extend({
    tagName: 'ul',
    className: 'nav nav-tabs',
    attributes: {
        role: 'tablist'
    },
    childView: TabPanelView,

    events: {
        'shown.bs.tab li a ': 'triggerBarChartRefresh'
    },

    triggerBarChartRefresh: function() {
        $('#analyze-output-wrapper .bar-chart').trigger('bar-chart:refresh');
    },

    onRender: function() {
        this.$el.find('li:first').addClass('active');
    }
});

var TabContentView = Marionette.LayoutView.extend({
    className: 'tab-pane',
    id: function() {
        return this.model.get('name');
    },
    template: tabContentTmpl,
    attributes: {
        role: 'tabpanel'
    },
    regions: {
        tableRegion: '.analyze-table-region',
        chartRegion: '.analyze-chart-region'
    },
    onShow: function() {
        var dataCollection = new coreModels.DataCollection(
            this.model.get('categories')
        );

        this.tableRegion.show(new TableView({
            collection: dataCollection
        }));

        this.chartRegion.show(new ChartView({
            model: this.model,
            collection: dataCollection
        }));
    }
});

var TabContentsView = Marionette.CollectionView.extend({
    className: 'tab-content',
    childView: TabContentView,
    onRender: function() {
        this.$el.find('.tab-pane:first').addClass('active');
    }
});

var TableRowView = Marionette.ItemView.extend({
    tagName: 'tr',
    template: tableRowTmpl
});

var TableView = Marionette.CompositeView.extend({
    childView: TableRowView,
    childViewContainer: 'tbody',
    template: tableTmpl
});

var ChartView = Marionette.ItemView.extend({
    template: barChartTmpl,
    id: function() {
        return 'chart-' + this.model.get('name');
    },
    className: 'chart-container',

    onAttach: function() {
        this.addChart();
    },

    addChart: function() {
        var selector = '#' + this.id() + ' .bar-chart',
            chartData = this.collection.map(function(model) {
                return model.attributes;
            }),
            chartOptions = {
                isPercentage: true,
                depAxisLabel: 'Coverage'
            },
            depVars = ['coverage'],
            indVar = 'type';

        if (this.model.get('name') === 'land') {
            chartOptions.useHorizBars = true;
        }
        chart.makeBarChart(selector, chartData, indVar, depVars, chartOptions);
    }
});

module.exports = {
    AnalyzeWindow: AnalyzeWindow,
    DetailsView: DetailsView
};
<commit_msg>Refresh chart on animateIn<commit_after>"use strict";

var $ = require('jquery'),
    Marionette = require('../../shim/backbone.marionette'),
    App = require('../app'),
    models = require('./models'),
    coreModels = require('../core/models'),
    coreViews = require('../core/views'),
    chart = require('../core/chart'),
    windowTmpl = require('./templates/window.html'),
    messageTmpl = require('./templates/message.html'),
    detailsTmpl = require('./templates/details.html'),
    tableTmpl = require('./templates/table.html'),
    tableRowTmpl = require('./templates/tableRow.html'),
    tabPanelTmpl = require('./templates/tabPanel.html'),
    tabContentTmpl = require('./templates/tabContent.html'),
    barChartTmpl = require('../core/templates/barChart.html');

var AnalyzeWindow = Marionette.LayoutView.extend({
    id: 'analyze-output-wrapper',
    template: windowTmpl,

    regions: {
        headerRegion: '#analyze-header-region',
        detailsRegion: {
            el: '#analyze-details-region'
        }
    },

    initialize: function() {
        this.listenTo(this, 'animateIn', function() {
            $('#analyze-output-wrapper .bar-chart').trigger('bar-chart:refresh');
        });
    },

    onShow: function() {
        this.showHeaderRegion();
        this.showAnalyzingMessage();

        var self = this;

        if (!this.model.get('result')) {
            var taskHelper = {
                pollSuccess: function() {
                    self.showDetailsRegion();
                },

                pollFailure: function() {
                    self.showErrorMessage();
                },

                startFailure: function() {
                    self.showErrorMessage();
                }
            };
            this.model.start(taskHelper);
        } else {
            this.lock = $.Deferred();
            this.lock.done(function() {
                self.showDetailsRegion();
            });
        }
    },

    showHeaderRegion: function() {
        this.headerRegion.show(new coreViews.AreaOfInterestView({
            App: App,
            model: new coreModels.AreaOfInterestModel({
                shape: this.model.get('area_of_interest'),
                can_go_back: true,
                next_label: 'Model',
                url: 'model'
            })
        }));
    },

    showAnalyzingMessage: function() {
        var messageModel = new models.AnalyzeMessageModel();
        messageModel.setAnalyzing();
        this.detailsRegion.show(new MessageView({
            model: messageModel
        }));
    },

    showErrorMessage: function() {
        var messageModel = new models.AnalyzeMessageModel();
        messageModel.setError();
        this.detailsRegion.show(new MessageView({
            model: messageModel
        }));
    },

    showDetailsRegion: function() {
        var results = JSON.parse(this.model.get('result'));

        if (!this.isDestroyed) {
            this.detailsRegion.show(new DetailsView({
                collection: new models.LayerCollection(results)
            }));
        }
    },

    transitionInCss: {
        height: '0%'
    },

    animateIn: function() {
        var self = this;

        this.$el.animate({ height: '55%' }, 200, function() {
            self.trigger('animateIn');
            App.map.set('halfSize', true);
        });
        if (this.lock !== undefined) {
            this.lock.resolve();
        }
    },

    animateOut: function() {
        var self = this;

        App.map.set('halfSize', false);
        this.$el.animate({ height: '0%' }, 200, function() {
            self.trigger('animateOut');
        });
    }
});

var MessageView = Marionette.ItemView.extend({
    template: messageTmpl,
    className: 'analyze-message-region'
});

var DetailsView = Marionette.LayoutView.extend({
    template: detailsTmpl,
    regions: {
        panelsRegion: '.tab-panels-region',
        contentRegion: '.tab-contents-region'
    },

    onShow: function() {
        this.panelsRegion.show(new TabPanelsView({
            collection: this.collection
        }));

        this.contentRegion.show(new TabContentsView({
            collection: this.collection
        }));
    }
});

var TabPanelView = Marionette.ItemView.extend({
    tagName: 'li',
    template: tabPanelTmpl,
    attributes: {
        role: 'presentation'
    }
});

var TabPanelsView = Marionette.CollectionView.extend({
    tagName: 'ul',
    className: 'nav nav-tabs',
    attributes: {
        role: 'tablist'
    },
    childView: TabPanelView,

    events: {
        'shown.bs.tab li a ': 'triggerBarChartRefresh'
    },

    triggerBarChartRefresh: function() {
        $('#analyze-output-wrapper .bar-chart').trigger('bar-chart:refresh');
    },

    onRender: function() {
        this.$el.find('li:first').addClass('active');
    }
});

var TabContentView = Marionette.LayoutView.extend({
    className: 'tab-pane',
    id: function() {
        return this.model.get('name');
    },
    template: tabContentTmpl,
    attributes: {
        role: 'tabpanel'
    },
    regions: {
        tableRegion: '.analyze-table-region',
        chartRegion: '.analyze-chart-region'
    },
    onShow: function() {
        var dataCollection = new coreModels.DataCollection(
            this.model.get('categories')
        );

        this.tableRegion.show(new TableView({
            collection: dataCollection
        }));

        this.chartRegion.show(new ChartView({
            model: this.model,
            collection: dataCollection
        }));
    }
});

var TabContentsView = Marionette.CollectionView.extend({
    className: 'tab-content',
    childView: TabContentView,
    onRender: function() {
        this.$el.find('.tab-pane:first').addClass('active');
    }
});

var TableRowView = Marionette.ItemView.extend({
    tagName: 'tr',
    template: tableRowTmpl
});

var TableView = Marionette.CompositeView.extend({
    childView: TableRowView,
    childViewContainer: 'tbody',
    template: tableTmpl
});

var ChartView = Marionette.ItemView.extend({
    template: barChartTmpl,
    id: function() {
        return 'chart-' + this.model.get('name');
    },
    className: 'chart-container',

    onAttach: function() {
        this.addChart();
    },

    addChart: function() {
        var selector = '#' + this.id() + ' .bar-chart',
            chartData = this.collection.map(function(model) {
                return model.attributes;
            }),
            chartOptions = {
                isPercentage: true,
                depAxisLabel: 'Coverage'
            },
            depVars = ['coverage'],
            indVar = 'type';

        if (this.model.get('name') === 'land') {
            chartOptions.useHorizBars = true;
        }
        chart.makeBarChart(selector, chartData, indVar, depVars, chartOptions);
    }
});

module.exports = {
    AnalyzeWindow: AnalyzeWindow,
    DetailsView: DetailsView
};
<|endoftext|>


<commit_before>'use strict';

Namespace('Enpowi').

    Class('directives', {
        Static: {
            defaultModuleElement: null,
            queue: [],
            data: {},
            app: null,
	        bindingDirectives: [],
	        setBinding: function(directive) {
		        this.bindingDirectives.push(directive);
	        },
	        doneBinding: function(directive) {
		        this.bindingDirectives.splice(this.bindingDirectives.indexOf(directive), 1);

		        if (this.bindingDirectives.length < 1) {
			        Enpowi.App.pub('directive.ready');
		        }
	        },
            setup: function () {
                var me = this,
	                app = Enpowi.app,
	                emptyEl = function(el) {
		                while (el.firstChild) el.removeChild(el.lastChild);
	                };

                me.app = app;

                Vue.directive('module', {
                    bind: function () {
                        var el = this.el,
	                        directive = this;
	                    me.setBinding(directive);
                        switch (el.nodeName) {
                            case 'FORM':
                                Enpowi.forms.strategy(el, this.vm, this);
	                            me.doneBinding(directive);
                        }
                    }
                });

                Vue.directive('module-item', {
                    deep: true,
                    bind: function () {
                        var me = this,
	                        el = this.el,
                            parent = null,
                            listenFn = function () {
                                var form;

                                if (parent === null) {
                                    parent = el.parentNode;

                                    while (parent !== null && parent.nodeName !== 'FORM') {
                                        parent = parent.parentNode;

                                    }

                                    if (parent === null) return;
                                }

                                form = parent;

                                Enpowi.utilities.trigger(form, 'submit');
                            };

                        el.addEventListener('change', listenFn);
                        el.addEventListener('keyup', listenFn);
                        el.addEventListener('click', listenFn);
                    }
                });

                Vue.directive('header', {
                    bind: function () {
                        var el = this.el,
	                        directive = this;

	                    me.setBinding(directive);

	                    emptyEl(el);

                        app.loadModule(Enpowi.session.theme + '/header', function (html) {
                            el.appendChild(html);
	                        me.doneBinding(directive);
                        });
                    }
                });

                Vue.directive('navigation', {
                    bind: function () {
                        var el = this.el,
	                        directive = this;

	                    me.setBinding(directive);

	                    emptyEl(el);

                        app.loadModule(Enpowi.session.theme + '/navigation', function (html) {
                            el.appendChild(html);
	                        me.doneBinding(directive);
                        });
                    }
                });

                Vue.directive('article', {
                    bind: function () {
	                    var el = this.el;
                        me.defaultModuleElement = el;
                    }
                });

                Vue.directive('side', {
                    bind: function () {
	                    var directive = this,
		                    el = this.el;

	                    me.setBinding(directive);

	                    emptyEl(el);

	                    app.loadModule(Enpowi.session.theme + '/' + this.expression, function (html) {
		                    el.appendChild(html);
		                    me.doneBinding(directive);
	                    });
                    }
                });

                Vue.directive('footer', {
                    bind: function () {
	                    var directive = this;
	                    me.setBinding(directive);
                        var el = this.el;

	                    emptyEl(el);

                        app.loadModule(Enpowi.session.theme + '/footer', function (html) {
                            el.appendChild(html);
	                        me.doneBinding(directive);
                        });
                    }
                });

                Vue.directive('source-edit', {
                    bind: function() {
                        var directive = this,
	                        el = this.el,
                            form = el.form,
                            styleUrls = ["vendor/codemirror/lib/codemirror.css"],
                            scriptUrls = ["vendor/codemirror/lib/codemirror.js"],
                            mode = '';

	                    me.setBinding(directive);

                        switch (this.expression.toLowerCase()) {
                            case "wikilingo":
                                styleUrls.push("vendor/codemirror.wikilingo/wikiLingo.css");
                                scriptUrls.push("vendor/codemirror.wikilingo/wikiLingo.js");
                                mode = 'text/wikiLingo';
                                break;
                            default:

                        }

                        el.parentNode.appendChild(app.loadStyles(styleUrls));
                        app.loadScripts(scriptUrls, function() {
                            var cm = CodeMirror.fromTextArea(el, {
                                mode: mode
                            });

                            cm.on('change', function() {
                                el.value = cm.getValue();
                            });

                            form.addEventListener('submit', function() {
                                el.value = cm.getValue();
                            });

	                        me.doneBinding(directive);
                        });
                    }
                });

	            Vue.directive('frame', {
		            bind: function() {
			            var me = this,
				            el = this.el;

			            this.vm.reload = function() {
				            me.update(me.urlRaw);
			            };

			            $(el).bind('reload', this.vm.reload);
		            },
		            update: function (urlRaw) {
			            this.urlRaw = urlRaw;
			            var el = this.el,
				            url;

			            if (el.style.display === 'none') return;
			            if (this.expression === null) return;
			            if (urlRaw === null) return;

			            if (el.hasAttribute('static')) {
                            emptyEl(el);
				            app.load(Enpowi.utilities.url(urlRaw), function (html) {
					            $(el).append(html);
				            });
				            this.expression = '';
			            } else {
				            app.load(url = Enpowi.utilities.url(urlRaw), function (html) {
                                emptyEl(el);
					            el.appendChild(app.process(html, url.m, url.c));
				            });
			            }
		            }
	            });

                Vue.directive('pager', {
                    update: function(value) {
                        value.pages = value.pages || 0;

                        if (value.pages <= 1) return;

                        var page = value.page || 1,
                            pages = value.pages,
                            size = value.size || 5,
                            url = value.url,
                            i = Math.floor(Math.max(1, page - size)),
                            max = Math.ceil(Math.min(pages, page + size)),
                            pageBeforeAnchor = '',
                            pageAfterAnchor = '',
                            pageAnchors = '',
                            el = this.el;

                        if (el.nodeName !== 'NAV') {
                            console.log('Warning: pager should be used with nav elements');
                        }

                        for (; i <= max; i++) {
                            pageAnchors += '
' + i + '
';
                        }

                        if (page > 1) {
                            pageBeforeAnchor = '
\
                                \
                                    «\
                                \

';
                        } else {
                            pageBeforeAnchor = '
\
                                \
                                    «\
                                \

';
                        }

                        if (page < pages) {
                            pageAfterAnchor = '
\
                                \
                                    »\
                                \

';
                        } else {
                            pageAfterAnchor = '
\
                                \
                                    »\
                                \

';
                        }

                        el.innerHTML = '
\
                            ' + pageBeforeAnchor + pageAnchors + pageAfterAnchor + '\

';
                    }
                });

                Vue.directive('find', {
                    update: function (value) {
                        if (this.el.vFindActive) return;

                        var el = this.el,
                            find = Enpowi.utilities.url(value.find),
                            init = function() {
                                el.setAttribute('autocomplete', 'off');
                                $(el).typeahead({
                                    ajax: {
                                        url: find,
                                        triggerLength: 1
                                    },
                                    item: '

',
                                    onSelect: function(selection) {
                                    	if (value.url) {
                                        	app.go(value.url + selection.value);
                                    	} else {
                                    		el.value = selection.value;
                                    		Enpowi.utilities.trigger(el, 'change');
                                    		Enpowi.utilities.trigger(el, 'input');
                                    	}
                                    }
                                });

                                el.vFindActive = true;
                            };

                        if (el.nodeName !== 'INPUT') {
                            console.log('Warning: find should be used with input elements');
                        }

                        if ($.fn.typeahead === undefined) {
                            app.loadScript('vendor/bs-typeahead/js/bootstrap-typeahead.js', init);
                        } else {
                            init();
                        }
                    }
                });
            }
        }
    });
<commit_msg>Fixed a bug where v-find and listen were not working well together<commit_after>'use strict';

Namespace('Enpowi').

    Class('directives', {
        Static: {
            defaultModuleElement: null,
            queue: [],
            data: {},
            app: null,
	        bindingDirectives: [],
	        setBinding: function(directive) {
		        this.bindingDirectives.push(directive);
	        },
	        doneBinding: function(directive) {
		        this.bindingDirectives.splice(this.bindingDirectives.indexOf(directive), 1);

		        if (this.bindingDirectives.length < 1) {
			        Enpowi.App.pub('directive.ready');
		        }
	        },
            setup: function () {
                var me = this,
	                app = Enpowi.app,
	                emptyEl = function(el) {
		                while (el.firstChild) el.removeChild(el.lastChild);
	                };

                me.app = app;

                Vue.directive('module', {
                    bind: function () {
                        var el = this.el,
	                        directive = this;
	                    me.setBinding(directive);
                        switch (el.nodeName) {
                            case 'FORM':
                                Enpowi.forms.strategy(el, this.vm, this);
	                            me.doneBinding(directive);
                        }
                    }
                });

                Vue.directive('module-item', {
                    deep: true,
                    bind: function () {
                        var me = this,
	                        el = this.el,
                            parent = null,
                            listenFn = function () {
                                var form;

                                if (parent === null) {
                                    parent = el.parentNode;

                                    while (parent !== null && parent.nodeName !== 'FORM') {
                                        parent = parent.parentNode;

                                    }

                                    if (parent === null) return;
                                }

                                form = parent;
                                /*
                                 this timeout is here so that all event handlers that happen after this event get to
                                 finish before the form has submit triggered
                                */
                                setTimeout(function() {
                                  Enpowi.utilities.trigger(form, 'submit');
                                }, 0);
                            };

                        el.addEventListener('change', listenFn);
                        el.addEventListener('keyup', listenFn);
                        el.addEventListener('click', listenFn);
                    }
                });

                Vue.directive('header', {
                    bind: function () {
                        var el = this.el,
	                        directive = this;

	                    me.setBinding(directive);

	                    emptyEl(el);

                        app.loadModule(Enpowi.session.theme + '/header', function (html) {
                            el.appendChild(html);
	                        me.doneBinding(directive);
                        });
                    }
                });

                Vue.directive('navigation', {
                    bind: function () {
                        var el = this.el,
	                        directive = this;

	                    me.setBinding(directive);

	                    emptyEl(el);

                        app.loadModule(Enpowi.session.theme + '/navigation', function (html) {
                            el.appendChild(html);
	                        me.doneBinding(directive);
                        });
                    }
                });

                Vue.directive('article', {
                    bind: function () {
	                    var el = this.el;
                        me.defaultModuleElement = el;
                    }
                });

                Vue.directive('side', {
                    bind: function () {
	                    var directive = this,
		                    el = this.el;

	                    me.setBinding(directive);

	                    emptyEl(el);

	                    app.loadModule(Enpowi.session.theme + '/' + this.expression, function (html) {
		                    el.appendChild(html);
		                    me.doneBinding(directive);
	                    });
                    }
                });

                Vue.directive('footer', {
                    bind: function () {
	                    var directive = this;
	                    me.setBinding(directive);
                        var el = this.el;

	                    emptyEl(el);

                        app.loadModule(Enpowi.session.theme + '/footer', function (html) {
                            el.appendChild(html);
	                        me.doneBinding(directive);
                        });
                    }
                });

                Vue.directive('source-edit', {
                    bind: function() {
                        var directive = this,
	                        el = this.el,
                            form = el.form,
                            styleUrls = ["vendor/codemirror/lib/codemirror.css"],
                            scriptUrls = ["vendor/codemirror/lib/codemirror.js"],
                            mode = '';

	                    me.setBinding(directive);

                        switch (this.expression.toLowerCase()) {
                            case "wikilingo":
                                styleUrls.push("vendor/codemirror.wikilingo/wikiLingo.css");
                                scriptUrls.push("vendor/codemirror.wikilingo/wikiLingo.js");
                                mode = 'text/wikiLingo';
                                break;
                            default:

                        }

                        el.parentNode.appendChild(app.loadStyles(styleUrls));
                        app.loadScripts(scriptUrls, function() {
                            var cm = CodeMirror.fromTextArea(el, {
                                mode: mode
                            });

                            cm.on('change', function() {
                                el.value = cm.getValue();
                            });

                            form.addEventListener('submit', function() {
                                el.value = cm.getValue();
                            });

	                        me.doneBinding(directive);
                        });
                    }
                });

	            Vue.directive('frame', {
		            bind: function() {
			            var me = this,
				            el = this.el;

			            this.vm.reload = function() {
				            me.update(me.urlRaw);
			            };

			            $(el).bind('reload', this.vm.reload);
		            },
		            update: function (urlRaw) {
			            this.urlRaw = urlRaw;
			            var el = this.el,
				            url;

			            if (el.style.display === 'none') return;
			            if (this.expression === null) return;
			            if (urlRaw === null) return;

			            if (el.hasAttribute('static')) {
                            emptyEl(el);
				            app.load(Enpowi.utilities.url(urlRaw), function (html) {
					            $(el).append(html);
				            });
				            this.expression = '';
			            } else {
				            app.load(url = Enpowi.utilities.url(urlRaw), function (html) {
                                emptyEl(el);
					            el.appendChild(app.process(html, url.m, url.c));
				            });
			            }
		            }
	            });

                Vue.directive('pager', {
                    update: function(value) {
                        value.pages = value.pages || 0;

                        if (value.pages <= 1) return;

                        var page = value.page || 1,
                            pages = value.pages,
                            size = value.size || 5,
                            url = value.url,
                            i = Math.floor(Math.max(1, page - size)),
                            max = Math.ceil(Math.min(pages, page + size)),
                            pageBeforeAnchor = '',
                            pageAfterAnchor = '',
                            pageAnchors = '',
                            el = this.el;

                        if (el.nodeName !== 'NAV') {
                            console.log('Warning: pager should be used with nav elements');
                        }

                        for (; i <= max; i++) {
                            pageAnchors += '
' + i + '
';
                        }

                        if (page > 1) {
                            pageBeforeAnchor = '
\
                                \
                                    «\
                                \

';
                        } else {
                            pageBeforeAnchor = '
\
                                \
                                    «\
                                \

';
                        }

                        if (page < pages) {
                            pageAfterAnchor = '
\
                                \
                                    »\
                                \

';
                        } else {
                            pageAfterAnchor = '
\
                                \
                                    »\
                                \

';
                        }

                        el.innerHTML = '
\
                            ' + pageBeforeAnchor + pageAnchors + pageAfterAnchor + '\

';
                    }
                });

                Vue.directive('find', {
                    update: function (value) {
                        if (this.el.vFindActive) return;

                        var el = this.el,
                            find = Enpowi.utilities.url(value.find),
                            init = function() {
                                el.setAttribute('autocomplete', 'off');
                                $(el).typeahead({
                                    ajax: {
                                        url: find,
                                        triggerLength: 1
                                    },
                                    item: '

',
                                    onSelect: function(selection) {
                                    	if (value.url) {
                                        	app.go(value.url + selection.value);
                                    	} else {
                                    		el.value = selection.value;
                                    		Enpowi.utilities.trigger(el, 'change');
                                    		Enpowi.utilities.trigger(el, 'input');
                                    	}
                                    }
                                });

                                el.vFindActive = true;
                            };

                        if (el.nodeName !== 'INPUT') {
                            console.log('Warning: find should be used with input elements');
                        }

                        if ($.fn.typeahead === undefined) {
                            app.loadScript('vendor/bs-typeahead/js/bootstrap-typeahead.js', init);
                        } else {
                            init();
                        }
                    }
                });
            }
        }
    });
<|endoftext|>