<commit_before>(function (window, document) {

// Array Remove - By John Resig (MIT Licensed)
Array.prototype.remove = function(from, to) {
    var rest = this.slice( (to || from) + 1 || this.length );
    this.length = from < 0 ? this.length + from : from;
    return this.push.apply(this, rest);
};

/***
 * Paladin
 *
 * This is where we put all of our goodies. Some are instances, like the subsystems,
 * and others are prototypes to be used and extended.
 */
Paladin = {};
Paladin.component = {};
Paladin.init = function( options ) {
    Paladin.subsystem.init();
    if( options && options.debug )
        Paladin.debug = console.log;
    else
        Paladin.debug = function () {};
};
Paladin.run = function() {
    Paladin.tasker.run();
};
window.Paladin = Paladin;

/***
 * Tasker
 *
 * Provides a mechanism for scheduling callbacks to run each frame.
 */
function Tasker() {

    this.CONT = 0;
    this.DONE = 1;
    this.AGAIN = 2;

    var nextId = 0,
        tasksById = {},
        tasksByName = {},
        terminate = false,
        that = this;

    this.run = function() {
        for( var id in tasksById ) {
            var task = tasksById[id];
            task.time = Date.now();
            if( task.DONE === task._callback( task ) ) {
                that.remove( task );
            }
        }

        if( !terminate ) {
            setTimeout( that.run, 0 );
        }
    };

    this.terminate = function() {
        terminate = true;
    };

    this.add = function( options ) {
        var id = nextId ++;
        var task = {
            _callback: options.callback || function () {},
            _id: id,
            name: options.name || undefined,
            time: Date.now(),
            CONT: 0,
            DONE: 1,
            AGAIN: 2
        };

        tasksById[id] = task;
        if( task.name )
            tasksByName[task.name] = task;
        return task;
    };

    this.remove = function( task ) {
        if( task._id in tasksById ) {
            delete tasksById[task._id];
        }
        if ( task.name && task.name in tasksByName ) {
            delete tasksByName[task.name];
        }
    };

};

/***
 * Loader
 *
 * Provide resource loaders for game assets like models, textures and sounds.
 */
function Loader() {

    var that = this,
        graphics = undefined,
        physics = undefined,
        sound = undefined;

};

/***
 * MouseWatcher
 *
 * Caches the current mouse position and provides access to the coordinates.
 */
function MouseWatcher() {

    var mousePosition = {
            x: undefined,
            y: undefined
        },
        that = this;

    this.getX = function() {
        return mousePosition['x'];
    };

    this.getY = function() {
        return mousePosition['y'];
    };

    this._mouseMove = function( event ) {
        mousePosition['x'] = event.pageX;
        mousePosition['y'] = event.pageY;
    };

    window.addEventListener( 'mousemove', this._mouseMove, true );
    window.addEventListener( 'mouseover', this._mouseMove, true );
};

/***
 * Messenger
 *
 * Provide a mechanism for game entities to listen for events and to send
 * events. An event is an arbitrary string. Some Javascript events are
 * handled here and converted to game engine events so that entities can
 * listen for them.
 */
function Messenger() {

    var callbacks = {},
        that = this;

    this.listen = function( options ) {
        var id = options.entity.getId();
        if( !callbacks.hasOwnProperty( options.event ) )
            callbacks[options.event] = {};
        callbacks[options.event][id] = {
            callback: options.callback.bind( options.entity ),
            parameters: options.parameters,
            persistent: options.persistent
        };
    };

    this.ignore = function( options ) {
        if( callbacks.hasOwnProperty( options.event ) ) {
            if( callbacks[options.event].hasOwnProperty( options.entity.getId() ) )
                delete callbacks[options.event][options.entity.getId()];
            if( 0 == Object.keys( callbacks[options.event] ).length )
                delete callbacks[options.event];
        }
    };

    this.ignoreAll = function( options ) {

    };

    this.send = function( options ) {
        if( callbacks.hasOwnProperty( options.event ) ) {
            listeners = callbacks[options.event];
            for( var id in listeners ) {
                var callback = listeners[id].callback,
                    parameters = listeners[id].parameters,
                    persistent = listeners[id].persistent;

                callback( parameters.concat( options.parameters ) );
                if( !persistent )
                    delete callbacks[options.event][id];
            }
        }
    };

    this._keyDown = function( event ) {
        that.send( {
            event: that._convertKeyEvent( event, 'down' ),
            parameters: []
        } );
    };

    this._keyUp = function( event ) {
        that.send( {
            event: that._convertKeyEvent( event, 'up' ),
            parameters: []
        } );
    };

    this._mouseButtonDown = function( event ) {
        that.send( {
            event: that._convertMouseButtonEvent( event, 'down' ),
            parameters: []
        } );
    };

    this._mouseButtonUp = function( event ) {
        that.send( {
            event: that._convertMouseButtonEvent( event, 'up' ),
            parameters: []
        } );
    };

    this._mouseWheelScroll = function( event ) {
        that.send( {
            event: that._convertMouseWheelEvent( event ),
            parameters: []
        } );
    };

    this._convertKeyEvent = function( event, mode ) {
        var code = event.keyCode;

        var components = [];
        if( event.shiftKey || code == 16 )
            components.push( 'shift' );
        if( event.ctrlKey || code == 17 )
            components.push( 'control' );
        if( event.altKey || code == 18 )
            components.push( 'alt' );
        if( event.metaKey || code == 0 )
            components.push( 'meta' );

        if( code == 0 || (code >= 16 && code <= 18) || code == 224 ) {
            // These are modifier keys, do nothing.
        }
        else if( code == 27 )
            components.push( 'escape' );
        else if( code == 37 )
            components.push( 'larrow' );
        else if( code == 38 )
            components.push( 'uarrow' );
        else if( code == 39 )
            components.push( 'rarrow' );
        else if( code == 40 )
            components.push( 'darrow' );
        else if( (code >= 48 && code <= 90) )
            components.push( String.fromCharCode( code ).toLocaleLowerCase() );
        else
            components.push( '<' + code + '>' );

        components.push( mode );

        result = components.join( '-' );
        return result;
    };

    this._convertMouseButtonEvent = function( event, mode ) {
        var code = event.button;

        var components = [];
        if( event.shiftKey )
            components.push( 'shift' );
        if( event.ctrlKey )
            components.push( 'control' );
        if( event.altKey )
            components.push( 'alt' );
        if( event.metaKey )
            components.push( 'meta' );

        if( code == 0 )
            components.push( 'mouse1' );
        else if( code == 2 )
            components.push( 'mouse2' );
        else if( code == 1 )
            components.push( 'mouse3' );
        else
            components.push( '<' + code + '>' );

        components.push( mode );

        result = components.join( '-' );
        return result;
    };

    this._convertMouseWheelEvent = function( event ) {
        var code = event.detail;

        var components = [];
        if( event.shiftKey )
            components.push( 'shift' );
        if( event.ctrlKey )
            components.push( 'control' );
        if( event.altKey )
            components.push( 'alt' );
        if( event.metaKey )
            components.push( 'meta' );

        if( code < 0 )
            components.push( 'wheel-up' );
        else if( code > 0 )
            components.push( 'wheel-down' );

        result = components.join( '-' );
        return result;
    };

    window.addEventListener( 'keydown', this._keyDown, true );
    window.addEventListener( 'keyup', this._keyUp, true );
    window.addEventListener( 'mousedown', this._mouseButtonDown, true );
    window.addEventListener( 'mouseup', this._mouseButtonUp, true );
    window.addEventListener( 'DOMMouseScroll', this._mouseWheelScroll, true );
};

/***
 * Entity
 *
 * An entity is a basic game object. It is a container object for components. Each
 * entity has a unique identifier.
 */
var nextEntityId = 0;   // FIXME(alan.kligman@gmail.com): This is a hack.
function Entity() {

    var id = nextEntityId ++,
        componentsByType = {},
        children = [],
        that = this;

    this.getId = function() {
        return id;
    };

    this.listen = function( options ) {
        Paladin.messenger.listen( {
            entity: that,
            event: options.event,
            callback: options.callback,
            parameters: options.parameters || [],
            persistent: options.persistent || true
        } );
    };

    this.ignore = function( options ) {
        Paladin.messenger.ignore( {
            entity: that,
            event: options.event
        } );
    };

    this.send = function( options ) {
        Paladin.messenger.send( {
            event: options.event,
            parameters: options.parameters || []
        } );
    };

    this.getChildren = function () {
        return children;
    };

    this.addComponent = function( component ) {
        var componentType = component.getType();
        /* Note(alan.kligman@gmail.com):
         * We don't allow more than one component of the same type. This
         * might change in the future, so this step could be removed.
         */
        var previousComponents = this.getComponents( componentType );
        for( var i = 0; i < previousComponents.length; ++ i )
            this.removeComponent( previousComponents[i] );

        Paladin.debug( "Entity.addComponent(): ", component );
        componentsByType[componentType] = component;
        component.onAdd( this );

        return previousComponents;
    };

    this.removeComponent = function( component ) {
        var componentType = component.getType();
        if ( componentsByType[componentType] ) {
            for( var i = 0; i < componentsByType[componentType].length; ++ i ) {
                if( componentsByType[componentType][i] == component ) {
                    Paladin.debug( "Entity.removeComponent(): ", component );
                    componentsByType[componentType][i].onRemove( this );
                    componentsByType[componentType].remove( i );
                    break;
                }
            }

            // If we removed the spatial component, re-parent all children to this
            // entity so they can properly associate with an ancestor spatial component.
            if( 'spatial' == componentType ) {
                for( var child in this.children )
                    child.setParent( this );
            }
        }
    };

    this.getComponents = function( componentType ) {
        if( componentType && componentsByType.hasOwnProperty( componentType ) )
            return componentsByType[componentType];
        else {
            var components = [];
            for( var i = 0; i < componentsByType.length; ++ i ) {
                components.concat( componentsByType[i] );
            }
            return components;
        }
    };

    this.hasComponent = function( componentType ) {
        return componentType && componentsByType.hasOwnProperty( componentType );
    };

    this.addChild = function( child ) {
        children.push( child );
    };

    this.setParent = function( parent ) {
      parent.addChild( this );
      this.parentNode = parent;

      // Find the nearest ancestral spatial component. It might be in the parent.
      var nearestAncestor;
      for( nearestAncestor = parent; parent != null; nearestAncestor = nearestAncestor.parent ) {
          if( nearestAncestor.hasComponent( 'spatial' ) )
              break;
      }

      if( !this.hasComponent( 'spatial' ) ) {
          // Re-parent any components that rely on the spatial component.
          var components = this.getComponents();
          for( var component in components ) {
              if( 'spatial' in component.requires )
                  component.setParent( nearestAncestor.getComponent( 'spatial' ) );
          }
      } else {
          // Re-parent this entity's spatial component.
          this.getComponents( 'spatial' ).setParent( nearestAncestor );
      }
    };

};

/***
 * Component (prototype interface)
 *
 * A component is a basic unit of game functionality. Components are narrow in scope and are composed
 * together by entities to form game objects.
 */
function Component( options ) {
    this.type = options.type || undefined;
    this.subtype = options.subtype || [];
    this.requires = options.requires || [];
    this.parent = null;
};
Component.prototype.getType = function() {
    return this.type;
};
Component.prototype.getSubtype = function() {
    return this.subtype;
};

function SpatialComponent( position, rotation ) {
    this.position = position ? position : [0, 0, 0];   // X, Y, Z
    this.rotation = rotation ? rotation : [0, 0, 0];  // Roll, pitch, yaw
    this.object = new Paladin.graphics.SceneObject( {
        position: this.position,
        rotation: this.rotation
    } );
}
SpatialComponent.prototype = new Component( {
    type: 'core',
    subtype: [ 'spatial' ]
} );
SpatialComponent.prototype.constructor = SpatialComponent;
SpatialComponent.prototype.addChild = function ( child ) {
    this.object.bindChild( child );
};
SpatialComponent.prototype.setParent = function( parent ) {
    this.parent = parent;
    parent.addChild( this );
};

function SceneComponent( options ) {
    Paladin.debug( "constructing a new scene" );
    options = options || {};
    this.render = new Paladin.graphics.Scene( {
        fov: 60
    } );
    this.spatial = new Paladin.component.Spatial();
    this.render.bindSceneObject( this.spatial );
}
SceneComponent.prototype = new Component( {
    type: 'core',
    subtype: [ 'scene' ]
} );
SceneComponent.prototype.constructor = SceneComponent;
SceneComponent.prototype.addChild = function( child ) {
    Paladin.debug( "setting scene child" );
    if( child.constructor == CameraComponent ) {
        Paladin.debug( "bind camera to scene" );
        this.render.bindCamera( child );
    }
    else {
        Paladin.debug( "bind component to scene" );
        this.spatial.addChild( child );
    }
};
SceneComponent.prototype.setParent = function( parent ) {
    // Nothing to do.
};

function CameraComponent( options ) {
    this.camera = (options && options.camera) ? options.camera : new Paladin.graphics.Camera();
    this.spatial = (options && options.spatial) ? options.spatial : null;
}
CameraComponent.prototype = new Component( {
    type: 'graphics',
    subtype: [ 'camera' ],
    requires: [ 'spatial' ]
} );
CameraComponent.prototype.constructor = CameraComponent;
CameraComponent.prototype.addChild = function( child ) {
    this.spatial.addChild( child );
};
CameraComponent.prototype.setParent = function( parent ) {
    Paladin.debug( "setting camera parent" );
    this.parent = parent;
    parent.addChild( this );
};
CameraComponent.prototype.setSpatial = function( spatial ) {
    Paladin.debug( "setting camera spatial" );
    this.spatial = spatial;
};

function ModelComponent( options ) {
    this.object = new Paladin.graphics.SceneObject( options );
    this.spatial = (options && options.spatial) ? options.spatial : null;
    this.mesh = (options && options.mesh) ? options.mesh : null;
    this.material = (options && options.material) ? options.material : null;
};
ModelComponent.prototype = new Component( {
    type: 'graphics',
    subtype: [ 'model' ],
    requires: [ 'spatial' ]
} );
ModelComponent.prototype.constructor = ModelComponent;

ModelComponent.prototype.addChild = function( child ) {
    this.object.bindChild( child );
};
ModelComponent.prototype.setParent = function( parent ) {
    this.parent = parent;
    parent.addChild( this );
};
ModelComponent.prototype.setSpatial = function( spatial ) {
    /* FIXME(alan.kligman@gmail.com):
     * This needs to be properly managed.
     */
    this.spatial = spatial;
    this.object.position = spatial.position;
    this.object.rotation = spatial.rotation;
};
ModelComponent.prototype.setMesh = function( mesh ) {
    this.mesh = mesh;
    this.object.obj = mesh;
};
ModelComponent.prototype.setMaterial = function( material ) {
    this.material = material;
};

Paladin.tasker = new Tasker();
Paladin.messenger = new Messenger();
Paladin.mouseWatcher = new MouseWatcher();
Paladin.loader = new Loader();

// These are registration points for external implementations. They should be instances.
Paladin.graphics = undefined;
Paladin.physics = undefined;
Paladin.sound = undefined;

// Attach prototypes to Paladin.
Paladin.Entity = Entity;
Paladin.component.Spatial = SpatialComponent;
Paladin.component.Camera = CameraComponent;
Paladin.component.Model = ModelComponent;
Paladin.component.Scene = SceneComponent;
Paladin.component.Light = null;

})( window, document );
<commit_msg>Removed some dead code from Entity.<commit_after>(function (window, document) {

// Array Remove - By John Resig (MIT Licensed)
Array.prototype.remove = function(from, to) {
    var rest = this.slice( (to || from) + 1 || this.length );
    this.length = from < 0 ? this.length + from : from;
    return this.push.apply(this, rest);
};

/***
 * Paladin
 *
 * This is where we put all of our goodies. Some are instances, like the subsystems,
 * and others are prototypes to be used and extended.
 */
Paladin = {};
Paladin.component = {};
Paladin.init = function( options ) {
    Paladin.subsystem.init();
    if( options && options.debug )
        Paladin.debug = console.log;
    else
        Paladin.debug = function () {};
};
Paladin.run = function() {
    Paladin.tasker.run();
};
window.Paladin = Paladin;

/***
 * Tasker
 *
 * Provides a mechanism for scheduling callbacks to run each frame.
 */
function Tasker() {

    this.CONT = 0;
    this.DONE = 1;
    this.AGAIN = 2;

    var nextId = 0,
        tasksById = {},
        tasksByName = {},
        terminate = false,
        that = this;

    this.run = function() {
        for( var id in tasksById ) {
            var task = tasksById[id];
            task.time = Date.now();
            if( task.DONE === task._callback( task ) ) {
                that.remove( task );
            }
        }

        if( !terminate ) {
            setTimeout( that.run, 0 );
        }
    };

    this.terminate = function() {
        terminate = true;
    };

    this.add = function( options ) {
        var id = nextId ++;
        var task = {
            _callback: options.callback || function () {},
            _id: id,
            name: options.name || undefined,
            time: Date.now(),
            CONT: 0,
            DONE: 1,
            AGAIN: 2
        };

        tasksById[id] = task;
        if( task.name )
            tasksByName[task.name] = task;
        return task;
    };

    this.remove = function( task ) {
        if( task._id in tasksById ) {
            delete tasksById[task._id];
        }
        if ( task.name && task.name in tasksByName ) {
            delete tasksByName[task.name];
        }
    };

};

/***
 * Loader
 *
 * Provide resource loaders for game assets like models, textures and sounds.
 */
function Loader() {

    var that = this,
        graphics = undefined,
        physics = undefined,
        sound = undefined;

};

/***
 * MouseWatcher
 *
 * Caches the current mouse position and provides access to the coordinates.
 */
function MouseWatcher() {

    var mousePosition = {
            x: undefined,
            y: undefined
        },
        that = this;

    this.getX = function() {
        return mousePosition['x'];
    };

    this.getY = function() {
        return mousePosition['y'];
    };

    this._mouseMove = function( event ) {
        mousePosition['x'] = event.pageX;
        mousePosition['y'] = event.pageY;
    };

    window.addEventListener( 'mousemove', this._mouseMove, true );
    window.addEventListener( 'mouseover', this._mouseMove, true );
};

/***
 * Messenger
 *
 * Provide a mechanism for game entities to listen for events and to send
 * events. An event is an arbitrary string. Some Javascript events are
 * handled here and converted to game engine events so that entities can
 * listen for them.
 */
function Messenger() {

    var callbacks = {},
        that = this;

    this.listen = function( options ) {
        var id = options.entity.getId();
        if( !callbacks.hasOwnProperty( options.event ) )
            callbacks[options.event] = {};
        callbacks[options.event][id] = {
            callback: options.callback.bind( options.entity ),
            parameters: options.parameters,
            persistent: options.persistent
        };
    };

    this.ignore = function( options ) {
        if( callbacks.hasOwnProperty( options.event ) ) {
            if( callbacks[options.event].hasOwnProperty( options.entity.getId() ) )
                delete callbacks[options.event][options.entity.getId()];
            if( 0 == Object.keys( callbacks[options.event] ).length )
                delete callbacks[options.event];
        }
    };

    this.ignoreAll = function( options ) {

    };

    this.send = function( options ) {
        if( callbacks.hasOwnProperty( options.event ) ) {
            listeners = callbacks[options.event];
            for( var id in listeners ) {
                var callback = listeners[id].callback,
                    parameters = listeners[id].parameters,
                    persistent = listeners[id].persistent;

                callback( parameters.concat( options.parameters ) );
                if( !persistent )
                    delete callbacks[options.event][id];
            }
        }
    };

    this._keyDown = function( event ) {
        that.send( {
            event: that._convertKeyEvent( event, 'down' ),
            parameters: []
        } );
    };

    this._keyUp = function( event ) {
        that.send( {
            event: that._convertKeyEvent( event, 'up' ),
            parameters: []
        } );
    };

    this._mouseButtonDown = function( event ) {
        that.send( {
            event: that._convertMouseButtonEvent( event, 'down' ),
            parameters: []
        } );
    };

    this._mouseButtonUp = function( event ) {
        that.send( {
            event: that._convertMouseButtonEvent( event, 'up' ),
            parameters: []
        } );
    };

    this._mouseWheelScroll = function( event ) {
        that.send( {
            event: that._convertMouseWheelEvent( event ),
            parameters: []
        } );
    };

    this._convertKeyEvent = function( event, mode ) {
        var code = event.keyCode;

        var components = [];
        if( event.shiftKey || code == 16 )
            components.push( 'shift' );
        if( event.ctrlKey || code == 17 )
            components.push( 'control' );
        if( event.altKey || code == 18 )
            components.push( 'alt' );
        if( event.metaKey || code == 0 )
            components.push( 'meta' );

        if( code == 0 || (code >= 16 && code <= 18) || code == 224 ) {
            // These are modifier keys, do nothing.
        }
        else if( code == 27 )
            components.push( 'escape' );
        else if( code == 37 )
            components.push( 'larrow' );
        else if( code == 38 )
            components.push( 'uarrow' );
        else if( code == 39 )
            components.push( 'rarrow' );
        else if( code == 40 )
            components.push( 'darrow' );
        else if( (code >= 48 && code <= 90) )
            components.push( String.fromCharCode( code ).toLocaleLowerCase() );
        else
            components.push( '<' + code + '>' );

        components.push( mode );

        result = components.join( '-' );
        return result;
    };

    this._convertMouseButtonEvent = function( event, mode ) {
        var code = event.button;

        var components = [];
        if( event.shiftKey )
            components.push( 'shift' );
        if( event.ctrlKey )
            components.push( 'control' );
        if( event.altKey )
            components.push( 'alt' );
        if( event.metaKey )
            components.push( 'meta' );

        if( code == 0 )
            components.push( 'mouse1' );
        else if( code == 2 )
            components.push( 'mouse2' );
        else if( code == 1 )
            components.push( 'mouse3' );
        else
            components.push( '<' + code + '>' );

        components.push( mode );

        result = components.join( '-' );
        return result;
    };

    this._convertMouseWheelEvent = function( event ) {
        var code = event.detail;

        var components = [];
        if( event.shiftKey )
            components.push( 'shift' );
        if( event.ctrlKey )
            components.push( 'control' );
        if( event.altKey )
            components.push( 'alt' );
        if( event.metaKey )
            components.push( 'meta' );

        if( code < 0 )
            components.push( 'wheel-up' );
        else if( code > 0 )
            components.push( 'wheel-down' );

        result = components.join( '-' );
        return result;
    };

    window.addEventListener( 'keydown', this._keyDown, true );
    window.addEventListener( 'keyup', this._keyUp, true );
    window.addEventListener( 'mousedown', this._mouseButtonDown, true );
    window.addEventListener( 'mouseup', this._mouseButtonUp, true );
    window.addEventListener( 'DOMMouseScroll', this._mouseWheelScroll, true );
};

/***
 * Entity
 *
 * An entity is a basic game object. It is a container object for components. Each
 * entity has a unique identifier.
 */
var nextEntityId = 0;   // FIXME(alan.kligman@gmail.com): This is a hack.
function Entity() {

    var id = nextEntityId ++,
        componentsByType = {},
        children = [],
        that = this;

    this.getId = function() {
        return id;
    };

    this.listen = function( options ) {
        Paladin.messenger.listen( {
            entity: that,
            event: options.event,
            callback: options.callback,
            parameters: options.parameters || [],
            persistent: options.persistent || true
        } );
    };

    this.ignore = function( options ) {
        Paladin.messenger.ignore( {
            entity: that,
            event: options.event
        } );
    };

    this.send = function( options ) {
        Paladin.messenger.send( {
            event: options.event,
            parameters: options.parameters || []
        } );
    };

};

/***
 * Component (prototype interface)
 *
 * A component is a basic unit of game functionality. Components are narrow in scope and are composed
 * together by entities to form game objects.
 */
function Component( options ) {
    this.type = options.type || undefined;
    this.subtype = options.subtype || [];
    this.requires = options.requires || [];
    this.parent = null;
};
Component.prototype.getType = function() {
    return this.type;
};
Component.prototype.getSubtype = function() {
    return this.subtype;
};

function SpatialComponent( position, rotation ) {
    this.position = position ? position : [0, 0, 0];   // X, Y, Z
    this.rotation = rotation ? rotation : [0, 0, 0];  // Roll, pitch, yaw
    this.object = new Paladin.graphics.SceneObject( {
        position: this.position,
        rotation: this.rotation
    } );
}
SpatialComponent.prototype = new Component( {
    type: 'core',
    subtype: [ 'spatial' ]
} );
SpatialComponent.prototype.constructor = SpatialComponent;
SpatialComponent.prototype.addChild = function ( child ) {
    this.object.bindChild( child );
};
SpatialComponent.prototype.setParent = function( parent ) {
    this.parent = parent;
    parent.addChild( this );
};

function SceneComponent( options ) {
    Paladin.debug( "constructing a new scene" );
    options = options || {};
    this.render = new Paladin.graphics.Scene( {
        fov: 60
    } );
    this.spatial = new Paladin.component.Spatial();
    this.render.bindSceneObject( this.spatial );
}
SceneComponent.prototype = new Component( {
    type: 'core',
    subtype: [ 'scene' ]
} );
SceneComponent.prototype.constructor = SceneComponent;
SceneComponent.prototype.addChild = function( child ) {
    Paladin.debug( "setting scene child" );
    if( child.constructor == CameraComponent ) {
        Paladin.debug( "bind camera to scene" );
        this.render.bindCamera( child );
    }
    else {
        Paladin.debug( "bind component to scene" );
        this.spatial.addChild( child );
    }
};
SceneComponent.prototype.setParent = function( parent ) {
    // Nothing to do.
};

function CameraComponent( options ) {
    this.camera = (options && options.camera) ? options.camera : new Paladin.graphics.Camera();
    this.spatial = (options && options.spatial) ? options.spatial : null;
}
CameraComponent.prototype = new Component( {
    type: 'graphics',
    subtype: [ 'camera' ],
    requires: [ 'spatial' ]
} );
CameraComponent.prototype.constructor = CameraComponent;
CameraComponent.prototype.addChild = function( child ) {
    this.spatial.addChild( child );
};
CameraComponent.prototype.setParent = function( parent ) {
    Paladin.debug( "setting camera parent" );
    this.parent = parent;
    parent.addChild( this );
};
CameraComponent.prototype.setSpatial = function( spatial ) {
    Paladin.debug( "setting camera spatial" );
    this.spatial = spatial;
    this.camera.position = spatial.position;
    this.camera.rotation = spatial.rotation;
};

function ModelComponent( options ) {
    this.object = new Paladin.graphics.SceneObject( options );
    this.spatial = (options && options.spatial) ? options.spatial : null;
    this.mesh = (options && options.mesh) ? options.mesh : null;
    this.material = (options && options.material) ? options.material : null;
};
ModelComponent.prototype = new Component( {
    type: 'graphics',
    subtype: [ 'model' ],
    requires: [ 'spatial' ]
} );
ModelComponent.prototype.constructor = ModelComponent;

ModelComponent.prototype.addChild = function( child ) {
    this.object.bindChild( child );
};
ModelComponent.prototype.setParent = function( parent ) {
    this.parent = parent;
    parent.addChild( this );
};
ModelComponent.prototype.setSpatial = function( spatial ) {
    /* FIXME(alan.kligman@gmail.com):
     * This needs to be properly managed.
     */
    this.spatial = spatial;
    this.object.position = spatial.position;
    this.object.rotation = spatial.rotation;
};
ModelComponent.prototype.setMesh = function( mesh ) {
    this.mesh = mesh;
    this.object.obj = mesh;
};
ModelComponent.prototype.setMaterial = function( material ) {
    this.material = material;
};

Paladin.tasker = new Tasker();
Paladin.messenger = new Messenger();
Paladin.mouseWatcher = new MouseWatcher();
Paladin.loader = new Loader();

// These are registration points for external implementations. They should be instances.
Paladin.graphics = undefined;
Paladin.physics = undefined;
Paladin.sound = undefined;

// Attach prototypes to Paladin.
Paladin.Entity = Entity;
Paladin.component.Spatial = SpatialComponent;
Paladin.component.Camera = CameraComponent;
Paladin.component.Model = ModelComponent;
Paladin.component.Scene = SceneComponent;
Paladin.component.Light = null;

})( window, document );
<|endoftext|>


<commit_before>/*
Copyright 2018, 2019 New Vector Ltd
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { debounce } from 'lodash';
import classNames from 'classnames';
import React from 'react';
import PropTypes from "prop-types";
import * as sdk from '../../../../index';
import {MatrixClientPeg} from '../../../../MatrixClientPeg';
import Field from '../../elements/Field';
import AccessibleButton from '../../elements/AccessibleButton';

import { _t } from '../../../../languageHandler';

// Maximum acceptable size of a key file. It's 59 characters including the spaces we encode,
// so this should be plenty and allow for people putting extra whitespace in the file because
// maybe that's a thing people would do?
const KEY_FILE_MAX_SIZE = 128;

// Don't shout at the user that their key is invalid every time they type a key: wait a short time
const VALIDATION_THROTTLE_MS = 200;

/*
 * Access Secure Secret Storage by requesting the user's passphrase.
 */
export default class AccessSecretStorageDialog extends React.PureComponent {
    static propTypes = {
        // { passphrase, pubkey }
        keyInfo: PropTypes.object.isRequired,
        // Function from one of { passphrase, recoveryKey } -> boolean
        checkPrivateKey: PropTypes.func.isRequired,
    }

    constructor(props) {
        super(props);

        this._fileUpload = React.createRef();

        this.state = {
            recoveryKey: "",
            recoveryKeyValid: null,
            recoveryKeyCorrect: null,
            recoveryKeyFileError: null,
            forceRecoveryKey: false,
            passPhrase: '',
            keyMatches: null,
        };
    }

    _onCancel = () => {
        this.props.onFinished(false);
    }

    _onUseRecoveryKeyClick = () => {
        this.setState({
            forceRecoveryKey: true,
        });
    }

    _validateRecoveryKeyOnChange = debounce(() => {
        this._validateRecoveryKey();
    }, VALIDATION_THROTTLE_MS);

    async _validateRecoveryKey() {
        if (this.state.recoveryKey === '') {
            this.setState({
                recoveryKeyValid: null,
                recoveryKeyCorrect: null,
            });
            return;
        }

        try {
            const cli = MatrixClientPeg.get();
            const decodedKey = cli.keyBackupKeyFromRecoveryKey(this.state.recoveryKey);
            const correct = await cli.checkSecretStorageKey(
                decodedKey, this.props.keyInfo,
            );
            this.setState({
                recoveryKeyValid: true,
                recoveryKeyCorrect: correct,
            });
        } catch (e) {
            this.setState({
                recoveryKeyValid: false,
                recoveryKeyCorrect: false,
            });
        }
    }

    _onRecoveryKeyChange = (e) => {
        this.setState({
            recoveryKey: e.target.value,
            recoveryKeyFileError: null,
        });

        // also clear the file upload control so that the user can upload the same file
        // the did before (otherwise the onchange wouldn't fire)
        if (this._fileUpload.current) this._fileUpload.current.value = null;

        // We don't use Field's validation here because a) we want it in a separate place rather
        // than in a tooltip and b) we want it to display feedback based on the uploaded file
        // as well as the text box. Ideally we would refactor Field's validation logic so we could
        // re-use some of it.
        this._validateRecoveryKeyOnChange();
    }

    _onRecoveryKeyFileChange = async e => {
        if (e.target.files.length === 0) return;

        const f = e.target.files[0];

        if (f.size > KEY_FILE_MAX_SIZE) {
            this.setState({
                recoveryKeyFileError: true,
                recoveryKeyCorrect: false,
                recoveryKeyValid: false,
            });
        } else {
            const contents = await f.text();
            // test it's within the base58 alphabet. We could be more strict here, eg. require the
            // right number of characters, but it's really just to make sure that what we're reading is
            // text because we'll put it in the text field.
            if (/^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\s]+$/.test(contents)) {
                this.setState({
                    recoveryKeyFileError: null,
                    recoveryKey: contents.trim(),
                });
                this._validateRecoveryKey();
            } else {
                this.setState({
                    recoveryKeyFileError: true,
                    recoveryKeyCorrect: false,
                    recoveryKeyValid: false,
                    recoveryKey: '',
                });
            }
        }
    }

    _onRecoveryKeyFileUploadClick = () => {
        this._fileUpload.current.click();
    }

    _onPassPhraseNext = async (e) => {
        e.preventDefault();

        if (this.state.passPhrase.length <= 0) return;

        this.setState({ keyMatches: null });
        const input = { passphrase: this.state.passPhrase };
        const keyMatches = await this.props.checkPrivateKey(input);
        if (keyMatches) {
            this.props.onFinished(input);
        } else {
            this.setState({ keyMatches });
        }
    }

    _onRecoveryKeyNext = async (e) => {
        e.preventDefault();

        if (!this.state.recoveryKeyValid) return;

        this.setState({ keyMatches: null });
        const input = { recoveryKey: this.state.recoveryKey };
        const keyMatches = await this.props.checkPrivateKey(input);
        if (keyMatches) {
            this.props.onFinished(input);
        } else {
            this.setState({ keyMatches });
        }
    }

    _onPassPhraseChange = (e) => {
        this.setState({
            passPhrase: e.target.value,
            keyMatches: null,
        });
    }

    getKeyValidationText() {
        if (this.state.recoveryKeyFileError) {
            return _t("Wrong file type");
        } else if (this.state.recoveryKeyCorrect) {
            return _t("Looks good!");
        } else if (this.state.recoveryKeyValid) {
            return _t("Wrong Recovery Key");
        } else if (this.state.recoveryKeyValid === null) {
            return '';
        } else {
            return _t("Invalid Recovery Key");
        }
    }

    render() {
        const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');

        const hasPassphrase = (
            this.props.keyInfo &&
            this.props.keyInfo.passphrase &&
            this.props.keyInfo.passphrase.salt &&
            this.props.keyInfo.passphrase.iterations
        );

        let content;
        let title;
        let titleClass;
        if (hasPassphrase && !this.state.forceRecoveryKey) {
            const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
            const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
            title = _t("Security Phrase");
            titleClass = ['mx_AccessSecretStorageDialog_titleWithIcon mx_AccessSecretStorageDialog_securePhraseTitle'];

            let keyStatus;
            if (this.state.keyMatches === false) {
                keyStatus =

                    {"\uD83D\uDC4E "}{_t(
                        "Unable to access secret storage. " +
                        "Please verify that you entered the correct recovery passphrase.",
                    )}

;
            } else {
                keyStatus =
;
            }

            content =

{_t(
                    "Enter your Security Phrase or <button>Use your Security Key</button> to continue.", {},
                    {
                        button: s => <AccessibleButton className="mx_linkButton"
                            element="span"
                            onClick={this._onUseRecoveryKeyClick}
                        >
                            {s}
                        </AccessibleButton>,
                    },
                )}

                <form className="mx_AccessSecretStorageDialog_primaryContainer" onSubmit={this._onPassPhraseNext}>
                    <input
                        type="password"
                        className="mx_AccessSecretStorageDialog_passPhraseInput"
                        onChange={this._onPassPhraseChange}
                        value={this.state.passPhrase}
                        autoFocus={true}
                        autoComplete="new-password"
                        placeholder={_t("Security Phrase")}
                    />
                    {keyStatus}
                    <DialogButtons
                        primaryButton={_t('Continue')}
                        onPrimaryButtonClick={this._onPassPhraseNext}
                        hasCancel={true}
                        onCancel={this._onCancel}
                        focus={false}
                        primaryDisabled={this.state.passPhrase.length === 0}
                    />
                </form>

;
        } else {
            title = _t("Security Key");
            titleClass = ['mx_AccessSecretStorageDialog_titleWithIcon mx_AccessSecretStorageDialog_secureBackupTitle'];
            const DialogButtons = sdk.getComponent('views.elements.DialogButtons');

            const feedbackClasses = classNames({
                'mx_AccessSecretStorageDialog_recoveryKeyFeedback': true,
                'mx_AccessSecretStorageDialog_recoveryKeyFeedback_valid': this.state.recoveryKeyCorrect === true,
                'mx_AccessSecretStorageDialog_recoveryKeyFeedback_invalid': this.state.recoveryKeyCorrect === false,
            });
            const recoveryKeyFeedback =

                {this.getKeyValidationText()}

;

            content =

{_t("Use your Security Key to continue.")}

                <form className="mx_AccessSecretStorageDialog_primaryContainer" onSubmit={this._onRecoveryKeyNext} spellCheck={false}>

                            <Field
                                type="text"
                                label={_t('Security Key')}
                                value={this.state.recoveryKey}
                                onChange={this._onRecoveryKeyChange}
                                forceValidity={this.state.recoveryKeyCorrect}
                            />

                            {_t("or")}

                            <input type="file"
                                className="mx_AccessSecretStorageDialog_recoveryKeyEntry_fileInput"
                                ref={this._fileUpload}
                                onChange={this._onRecoveryKeyFileChange}
                            />
                            <AccessibleButton kind="primary" onClick={this._onRecoveryKeyFileUploadClick}>
                                {_t("Upload")}
                            </AccessibleButton>

                    {recoveryKeyFeedback}
                    <DialogButtons
                        primaryButton={_t('Continue')}
                        onPrimaryButtonClick={this._onRecoveryKeyNext}
                        hasCancel={true}
                        cancelButton={_t("Go Back")}
                        cancelButtonClass='danger'
                        onCancel={this._onCancel}
                        focus={false}
                        primaryDisabled={!this.state.recoveryKeyValid}
                    />
                </form>

;
        }

        return (
            <BaseDialog className='mx_AccessSecretStorageDialog'
                onFinished={this.props.onFinished}
                title={title}
                titleClass={titleClass}
            >

                {content}

            </BaseDialog>
        );
    }
}
<commit_msg>remove rogue blank line<commit_after>/*
Copyright 2018, 2019 New Vector Ltd
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { debounce } from 'lodash';
import classNames from 'classnames';
import React from 'react';
import PropTypes from "prop-types";
import * as sdk from '../../../../index';
import {MatrixClientPeg} from '../../../../MatrixClientPeg';
import Field from '../../elements/Field';
import AccessibleButton from '../../elements/AccessibleButton';

import { _t } from '../../../../languageHandler';

// Maximum acceptable size of a key file. It's 59 characters including the spaces we encode,
// so this should be plenty and allow for people putting extra whitespace in the file because
// maybe that's a thing people would do?
const KEY_FILE_MAX_SIZE = 128;

// Don't shout at the user that their key is invalid every time they type a key: wait a short time
const VALIDATION_THROTTLE_MS = 200;

/*
 * Access Secure Secret Storage by requesting the user's passphrase.
 */
export default class AccessSecretStorageDialog extends React.PureComponent {
    static propTypes = {
        // { passphrase, pubkey }
        keyInfo: PropTypes.object.isRequired,
        // Function from one of { passphrase, recoveryKey } -> boolean
        checkPrivateKey: PropTypes.func.isRequired,
    }

    constructor(props) {
        super(props);

        this._fileUpload = React.createRef();

        this.state = {
            recoveryKey: "",
            recoveryKeyValid: null,
            recoveryKeyCorrect: null,
            recoveryKeyFileError: null,
            forceRecoveryKey: false,
            passPhrase: '',
            keyMatches: null,
        };
    }

    _onCancel = () => {
        this.props.onFinished(false);
    }

    _onUseRecoveryKeyClick = () => {
        this.setState({
            forceRecoveryKey: true,
        });
    }

    _validateRecoveryKeyOnChange = debounce(() => {
        this._validateRecoveryKey();
    }, VALIDATION_THROTTLE_MS);

    async _validateRecoveryKey() {
        if (this.state.recoveryKey === '') {
            this.setState({
                recoveryKeyValid: null,
                recoveryKeyCorrect: null,
            });
            return;
        }

        try {
            const cli = MatrixClientPeg.get();
            const decodedKey = cli.keyBackupKeyFromRecoveryKey(this.state.recoveryKey);
            const correct = await cli.checkSecretStorageKey(
                decodedKey, this.props.keyInfo,
            );
            this.setState({
                recoveryKeyValid: true,
                recoveryKeyCorrect: correct,
            });
        } catch (e) {
            this.setState({
                recoveryKeyValid: false,
                recoveryKeyCorrect: false,
            });
        }
    }

    _onRecoveryKeyChange = (e) => {
        this.setState({
            recoveryKey: e.target.value,
            recoveryKeyFileError: null,
        });

        // also clear the file upload control so that the user can upload the same file
        // the did before (otherwise the onchange wouldn't fire)
        if (this._fileUpload.current) this._fileUpload.current.value = null;

        // We don't use Field's validation here because a) we want it in a separate place rather
        // than in a tooltip and b) we want it to display feedback based on the uploaded file
        // as well as the text box. Ideally we would refactor Field's validation logic so we could
        // re-use some of it.
        this._validateRecoveryKeyOnChange();
    }

    _onRecoveryKeyFileChange = async e => {
        if (e.target.files.length === 0) return;

        const f = e.target.files[0];

        if (f.size > KEY_FILE_MAX_SIZE) {
            this.setState({
                recoveryKeyFileError: true,
                recoveryKeyCorrect: false,
                recoveryKeyValid: false,
            });
        } else {
            const contents = await f.text();
            // test it's within the base58 alphabet. We could be more strict here, eg. require the
            // right number of characters, but it's really just to make sure that what we're reading is
            // text because we'll put it in the text field.
            if (/^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\s]+$/.test(contents)) {
                this.setState({
                    recoveryKeyFileError: null,
                    recoveryKey: contents.trim(),
                });
                this._validateRecoveryKey();
            } else {
                this.setState({
                    recoveryKeyFileError: true,
                    recoveryKeyCorrect: false,
                    recoveryKeyValid: false,
                    recoveryKey: '',
                });
            }
        }
    }

    _onRecoveryKeyFileUploadClick = () => {
        this._fileUpload.current.click();
    }

    _onPassPhraseNext = async (e) => {
        e.preventDefault();

        if (this.state.passPhrase.length <= 0) return;

        this.setState({ keyMatches: null });
        const input = { passphrase: this.state.passPhrase };
        const keyMatches = await this.props.checkPrivateKey(input);
        if (keyMatches) {
            this.props.onFinished(input);
        } else {
            this.setState({ keyMatches });
        }
    }

    _onRecoveryKeyNext = async (e) => {
        e.preventDefault();

        if (!this.state.recoveryKeyValid) return;

        this.setState({ keyMatches: null });
        const input = { recoveryKey: this.state.recoveryKey };
        const keyMatches = await this.props.checkPrivateKey(input);
        if (keyMatches) {
            this.props.onFinished(input);
        } else {
            this.setState({ keyMatches });
        }
    }

    _onPassPhraseChange = (e) => {
        this.setState({
            passPhrase: e.target.value,
            keyMatches: null,
        });
    }

    getKeyValidationText() {
        if (this.state.recoveryKeyFileError) {
            return _t("Wrong file type");
        } else if (this.state.recoveryKeyCorrect) {
            return _t("Looks good!");
        } else if (this.state.recoveryKeyValid) {
            return _t("Wrong Recovery Key");
        } else if (this.state.recoveryKeyValid === null) {
            return '';
        } else {
            return _t("Invalid Recovery Key");
        }
    }

    render() {
        const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');

        const hasPassphrase = (
            this.props.keyInfo &&
            this.props.keyInfo.passphrase &&
            this.props.keyInfo.passphrase.salt &&
            this.props.keyInfo.passphrase.iterations
        );

        let content;
        let title;
        let titleClass;
        if (hasPassphrase && !this.state.forceRecoveryKey) {
            const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
            const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
            title = _t("Security Phrase");
            titleClass = ['mx_AccessSecretStorageDialog_titleWithIcon mx_AccessSecretStorageDialog_securePhraseTitle'];

            let keyStatus;
            if (this.state.keyMatches === false) {
                keyStatus =

                    {"\uD83D\uDC4E "}{_t(
                        "Unable to access secret storage. " +
                        "Please verify that you entered the correct recovery passphrase.",
                    )}

;
            } else {
                keyStatus =
;
            }

            content =

{_t(
                    "Enter your Security Phrase or <button>Use your Security Key</button> to continue.", {},
                    {
                        button: s => <AccessibleButton className="mx_linkButton"
                            element="span"
                            onClick={this._onUseRecoveryKeyClick}
                        >
                            {s}
                        </AccessibleButton>,
                    },
                )}

                <form className="mx_AccessSecretStorageDialog_primaryContainer" onSubmit={this._onPassPhraseNext}>
                    <input
                        type="password"
                        className="mx_AccessSecretStorageDialog_passPhraseInput"
                        onChange={this._onPassPhraseChange}
                        value={this.state.passPhrase}
                        autoFocus={true}
                        autoComplete="new-password"
                        placeholder={_t("Security Phrase")}
                    />
                    {keyStatus}
                    <DialogButtons
                        primaryButton={_t('Continue')}
                        onPrimaryButtonClick={this._onPassPhraseNext}
                        hasCancel={true}
                        onCancel={this._onCancel}
                        focus={false}
                        primaryDisabled={this.state.passPhrase.length === 0}
                    />
                </form>

;
        } else {
            title = _t("Security Key");
            titleClass = ['mx_AccessSecretStorageDialog_titleWithIcon mx_AccessSecretStorageDialog_secureBackupTitle'];
            const DialogButtons = sdk.getComponent('views.elements.DialogButtons');

            const feedbackClasses = classNames({
                'mx_AccessSecretStorageDialog_recoveryKeyFeedback': true,
                'mx_AccessSecretStorageDialog_recoveryKeyFeedback_valid': this.state.recoveryKeyCorrect === true,
                'mx_AccessSecretStorageDialog_recoveryKeyFeedback_invalid': this.state.recoveryKeyCorrect === false,
            });
            const recoveryKeyFeedback =

                {this.getKeyValidationText()}

;

            content =

{_t("Use your Security Key to continue.")}

                <form className="mx_AccessSecretStorageDialog_primaryContainer" onSubmit={this._onRecoveryKeyNext} spellCheck={false}>

                            <Field
                                type="text"
                                label={_t('Security Key')}
                                value={this.state.recoveryKey}
                                onChange={this._onRecoveryKeyChange}
                                forceValidity={this.state.recoveryKeyCorrect}
                            />

                            {_t("or")}

                            <input type="file"
                                className="mx_AccessSecretStorageDialog_recoveryKeyEntry_fileInput"
                                ref={this._fileUpload}
                                onChange={this._onRecoveryKeyFileChange}
                            />
                            <AccessibleButton kind="primary" onClick={this._onRecoveryKeyFileUploadClick}>
                                {_t("Upload")}
                            </AccessibleButton>

                    {recoveryKeyFeedback}
                    <DialogButtons
                        primaryButton={_t('Continue')}
                        onPrimaryButtonClick={this._onRecoveryKeyNext}
                        hasCancel={true}
                        cancelButton={_t("Go Back")}
                        cancelButtonClass='danger'
                        onCancel={this._onCancel}
                        focus={false}
                        primaryDisabled={!this.state.recoveryKeyValid}
                    />
                </form>

;
        }

        return (
            <BaseDialog className='mx_AccessSecretStorageDialog'
                onFinished={this.props.onFinished}
                title={title}
                titleClass={titleClass}
            >

                {content}

            </BaseDialog>
        );
    }
}
<|endoftext|>


<commit_before>Ext.onReady(function() {
    if (!Vps.isApp) {
        var a = document.createElement('a');
        a.style.backgroundImage = 'url(/assets/silkicons/bug.png)';
        a.style.backgroundRepeat = 'no-repeat';
        a.style.backgroundPosition = 'center';
        a.style.border = '1px solid gray';
        a.style.position = 'absolute';
        a.style.right = '0';
        a.style.top = '0';
        a.style.width = '20px';
        a.style.height = '20px';
        a.style.zIndex = '200';
        a.title = 'Activate Debugging';
        a.href = '/vps/debug/activate?url=' + location.href;
        a.className = 'printHidden';
        document.body.appendChild(a);
    }
});

//for Vps.Menu.Index
Vps.Debug.showActivator = true;

//if we have the activator loaded we always want to show debug messages
//DebugData-Helper doesn't output anything so we set it here
Vps.Debug.displayErrors = true;
<commit_msg>hilfs-fkt zum xtype-tree ausgeben<commit_after>Ext.onReady(function() {
    if (!Vps.isApp) {
        var a = document.createElement('a');
        a.style.backgroundImage = 'url(/assets/silkicons/bug.png)';
        a.style.backgroundRepeat = 'no-repeat';
        a.style.backgroundPosition = 'center';
        a.style.border = '1px solid gray';
        a.style.position = 'absolute';
        a.style.right = '0';
        a.style.top = '0';
        a.style.width = '20px';
        a.style.height = '20px';
        a.style.zIndex = '200';
        a.title = 'Activate Debugging';
        a.href = '/vps/debug/activate?url=' + location.href;
        a.className = 'printHidden';
        document.body.appendChild(a);
    }
});

//for Vps.Menu.Index
Vps.Debug.showActivator = true;

//if we have the activator loaded we always want to show debug messages
//DebugData-Helper doesn't output anything so we set it here
Vps.Debug.displayErrors = true;

Ext.Container.prototype.logXTypesTree = function() {
    var level = 0;
    function foo(c) {
        if(c.items){
            var cs = c.items.items;
            level++;
            for(var i = 0, len = cs.length; i < len; i++){
                console.log(new Array(level).join(' ')+cs[i].getXTypes());
                foo(cs[i]);
            }
            level--;
        }
    }
    foo(this);
};
<|endoftext|>


<commit_before> var rules = [
{
    "Key": "Service DCO - IP",
    "Impacted": [   "CDN BANQUE", "CDN REGION", "CDN GROUPE", "CDN Agence", "CDN Code Agence",
                    "SG DR", "SG DEC", "SG UC", "SG Agence", "SG Code Agence",
                    "CRCM", "PSC",
                    "ASSU", "Type d'intervention", "Intervention IP", "Formation", "Type d'intervention 2"],
    "StartingEffect": {
        "Css": {
            "display": "none"
        }
    },
    "Conditions": [{
        "Key": "Service DCO",
        "Value": "IP",
    }],
    "Effect": {
        "Css": {
            "display": "block"
        }
    }
},
{
    "Key": "Service DCO - SG",
    "Impacted": ["SG DR", "SG DEC", "SG UC", "SG Agence", "SG Code Agence", "CRCM", "PSC"],
    "StartingEffect": {
        "Css": {
            "display": "none"
        }
    },
    "Conditions": [{
        "Key": "Service DCO",
        "Value": "ANI SG",
    }],
    "Effect": {
        "Css": {
            "display": "block"
        }
    }
},
{
    "Key": "Service DCO - CDN",
    "Impacted": ["CDN BANQUE", "CDN REGION", "CDN GROUPE", "CDN AGENCES", "CDN Code AGENCES"],
    "StartingEffect": {
        "Css": {
            "display": "none"
        }
    },
    "Conditions": [{
        "Key": "Service DCO",
        "Value": "ANI CDN",
    }],
    "Effect": {
        "Css": {
            "display": "block"
        }
    }
},
{
    "Key": "Service DCO - FOR",
    "Impacted": ["ASSU", "Type d'intervention", "Intervention IP", "Formation", "Type d'intervention 2"],
    "StartingEffect": {
        "Css": {
            "display": "none"
        }
    },
    "Conditions": [{
        "Key": "Service DCO",
        "Value": "FOR",
    }],
    "Effect": {
        "Css": {
            "display": "block"
        }
    }
},
{
    "Key": "Laps - heure spécifique",
    "Impacted": ["Heure début", "Heure fin"],
    "StartingEffect": {
        "Css": {
            "display": "none"
        }
    },
    "Conditions": [{
        "Key": "Laps",
        "Value": "Heure spécifique",
    }],
    "Effect": {
        "Css": {
            "display": "block"
        }
    }
}
]

$(function() {
    var elements = []

    var formatKey = function(key) {
        return key.replace(/ /ig, "_").replace(/\'/ig, "")
    }
    var getElement = function(key){
        key = formatKey(key)
        for(var i = 0; i < elements.length; ++i) {
            if(elements[i].Key == key) {
                return elements[i]
            }
            if(element[i].Value.constructor === Array)
            {
                var r = getSubElement(element[i].Key, key);
                if(r != null) return r
            }
        }
        return null
    }

    var getSubElement = function(parentKey, key) {
        var parent = getElement(parentKey)
        var subelements = parent.Value[parent.Value.length - 1]
        for(var i = 0; i < subelements.length; ++i) {
            if(subelements[i].Key == key) {
                return subelements[i]
            }
        }
        return null
    }

    var processEffect = function(impacted, effect) {
        if(effect.Css != undefined && effect.Css != null) {
            for(var i = 0; i < impacted.length; ++i) {
                key = impacted[i];
                key = formatKey(key)
                var element = getElement(key)
                var block = $("#con_" + element.Key)
                for(var property in effect.Css) {
                    block.css(property, effect.Css[property])
                }
            }
        }
    }

    var initEffect = function() {
        for(var i = 0; i < rules.length; ++i) {
            var rule = rules[i]
            if(rule.StartingEffect != undefined && rule.StartingEffect != null) {
                processEffect(rule.Impacted, rule.StartingEffect)
            }
        }
    }

    var processChange = function() {
         for(var i = 0; i < rules.length; ++i) {
            var rule = rules[i]
            var result = true
            for(var j = 0; j < rule.Conditions.length; ++j) {
                var condition = rule.Conditions[j]
                var element = getElement(condition.Key)
                if(element == null || element.Value != condition.Value) {
                    result = false
                    break
                }
            }
            if(result) {
                processEffect(rule.Impacted, rule.Effect)
                break
            }
            else if(rule.StartingEffect != undefined) {
                processEffect(rule.Impacted, rule.StartingEffect)
            }
        }
    }

    var copySummary = function(element) {
        $("[summary=" + element.Key + "]").html(element.Value)
    }

    $("[watch]").each(function(index, watchElement) {
        watchElement = $(watchElement)
        var key = watchElement.attr("watch")

        var parentId = null
        var element = null
        var parent = watchElement.parents("[parent-element]")
        if(parent.length > 0) {
            parentId = $(parent[0]).attr("parent-element")
        }
        else {
            element = {
                Key: key,
                Value: null
            }
            elements.push(element);
        }

        if(watchElement.is("input") || watchElement.is("select")) {
            watchElement.change(function() {
                var e = getElement(watchElement.attr("watch"))
                e.Value = watchElement.val()
                processChange()
                copySummary(e)
            })
        }

        if(watchElement.is("[type=radio]")) {
            watchElement.change(function() {
                if(watchElement.attr("checked")) {
                    var e = getElement(watchElement.attr("watch"))
                    e.Value = watchElement.val()
                    processChange()
                    copySummary(e)
                }
            })
        }

        if(watchElement.is("[type=checkbox]")) {
            watchElement.change(function() {
                var e = getElement(watchElement.attr("watch"))
                e.Value = watchElement.attr("checked")
                processChange()
                copySummary(e)
            })
        }

        if(watchElement.is("[type=button]")) {
            element.Value = [[]]
            watchElement.click(function() {
                var e = getElement(watchElement.attr("watch"))
                var item = e.Value[element.Value.length - 1]
                e.Value.push([])
                var id = ""
                for(var i in item) {
                    id += item[i].Key + ": " + item[i].Value + ", "
                    if(i >= 2) break;
                }

                $("#con_" + e.Key + " [watch-list]").append("

" + id + "

")

                $("#con_" + e.Key + " [parent-element] input[watch]").val("")

                processChange()
                copySummary(e)
            })
        }
    })

    initEffect()

    $("[validate-form]").click(function(){
        console.log(elements)
        $.ajax({
            type: "POST",
            url: "/Home/PushData",
            data: "data=" + JSON.stringify(convertToDictionary(elements)),
            success: function() {
                window.location = window.location.href
            }
        });
    })

    $('select').material_select();
    $('.collapsible').collapsible();
    $('.datepicker').pickadate({
    selectMonths: false, // Creates a dropdown to control month
    selectYears: 15, // Creates a dropdown of 15 years to control year,
    today: 'Today',
    clear: 'Clear',
    close: 'Ok',
    closeOnSelect: true // Close upon selecting a date,
    });

    $(".yuffieNextButton").click(function(e) {
        var parent = $($(e.target).parents("li"));
        var next = parent.next();
        if(next.is("li")) {
            next.find(".collapsible-header").click();
        }
        else {
            var id = parent.parents(".page").attr("id");
            $("li.tab a[href='#" + id + "']").parents("li").next().find("a").click()
        }
    })

    var convertToDictionary = function(array) {
        var dic = {}
        for(var i in array) {
            var e = array[i]
            if(e.Value != null && e.Value.constructor === Array) {
                dic[e.Key] = []
                for(var j in e.Value) {
                    dic[e.Key].push(convertToDictionary(e.Value[j]))
                }
            }
            else {
                dic[e.Key] = e.Value
            }
        }
        return dic;
    }
    exceptions();
})

var exceptions = function() {

}<commit_msg>fix architectural useless complexity<commit_after> var rules = [
{
    "Key": "Service DCO - IP",
    "Impacted": [   "CDN BANQUE", "CDN REGION", "CDN GROUPE", "CDN AGENCES", "CDN Code AGENCES",
                    "SG DR", "SG DEC", "SG UC", "SG Agence", "SG Code Agence",
                    "CRCM", "PSC",
                    "ASSU", "Type d'intervention", "Intervention IP", "Formation", "Type d'intervention 2"],
    "StartingEffect": {
        "Css": {
            "display": "none"
        }
    },
    "Conditions": [{
        "Key": "Service DCO",
        "Value": "IP",
    }],
    "Effect": {
        "Css": {
            "display": "block"
        }
    }
},
{
    "Key": "Service DCO - SG",
    "Impacted": ["SG DR", "SG DEC", "SG UC", "SG Agence", "SG Code Agence", "CRCM", "PSC"],
    "StartingEffect": {
        "Css": {
            "display": "none"
        }
    },
    "Conditions": [{
        "Key": "Service DCO",
        "Value": "ANI SG",
    }],
    "Effect": {
        "Css": {
            "display": "block"
        }
    }
},
{
    "Key": "Service DCO - CDN",
    "Impacted": ["CDN BANQUE", "CDN REGION", "CDN GROUPE", "CDN AGENCES", "CDN Code AGENCES"],
    "StartingEffect": {
        "Css": {
            "display": "none"
        }
    },
    "Conditions": [{
        "Key": "Service DCO",
        "Value": "ANI CDN",
    }],
    "Effect": {
        "Css": {
            "display": "block"
        }
    }
},
{
    "Key": "Service DCO - FOR",
    "Impacted": ["ASSU", "Type d'intervention", "Intervention IP", "Formation", "Type d'intervention 2"],
    "StartingEffect": {
        "Css": {
            "display": "none"
        }
    },
    "Conditions": [{
        "Key": "Service DCO",
        "Value": "FOR",
    }],
    "Effect": {
        "Css": {
            "display": "block"
        }
    }
},
{
    "Key": "Laps - heure spécifique",
    "Impacted": ["Heure début", "Heure fin"],
    "StartingEffect": {
        "Css": {
            "display": "none"
        }
    },
    "Conditions": [{
        "Key": "Laps",
        "Value": "Heure spécifique",
    }],
    "Effect": {
        "Css": {
            "display": "block"
        }
    }
}
]

$(function() {
    var elements = []

    var formatKey = function(key) {
        return key.replace(/ /ig, "_").replace(/\'/ig, "")
    }
    var getElement = function(key){
        key = formatKey(key)

        for(var i = 0; i < elements.length; ++i) {
            if(elements[i].Key == key) {
                return elements[i]
            }
        }
        return null
    }

    var processEffect = function(impacted, effect) {
        if(effect.Css != undefined && effect.Css != null) {
            for(var i = 0; i < impacted.length; ++i) {
                key = impacted[i];
                key = formatKey(key)
                var element = getElement(key)
                var block = $("#con_" + element.Key)
                for(var property in effect.Css) {
                    block.css(property, effect.Css[property])
                }
            }
        }
    }

    var initEffect = function() {
        for(var i = 0; i < rules.length; ++i) {
            var rule = rules[i]
            if(rule.StartingEffect != undefined && rule.StartingEffect != null) {
                processEffect(rule.Impacted, rule.StartingEffect)
            }
        }
    }

    var processChange = function() {
         for(var i = 0; i < rules.length; ++i) {
            var rule = rules[i]
            var result = true
            for(var j = 0; j < rule.Conditions.length; ++j) {
                var condition = rule.Conditions[j]
                var element = getElement(condition.Key)
                if(element == null || element.Value != condition.Value) {
                    result = false
                    break
                }
            }
            if(result) {
                processEffect(rule.Impacted, rule.Effect)
                break
            }
            else if(rule.StartingEffect != undefined) {
                processEffect(rule.Impacted, rule.StartingEffect)
            }
        }
    }

    var copySummary = function(element) {
        $("[summary=" + element.Key + "]").html(element.Value)
    }

    $("[watch]").each(function(index, watchElement) {
        watchElement = $(watchElement)
        var key = watchElement.attr("watch")
        var rawKey = ""+key
        var parentId = null
        var element = null
        var parentElement = null
        var parent = watchElement.parents("[parent-element]")
        element = {
            Key: key,
            Value: null,
            Parent: null
        }
        if(parent.length > 0) {
            parentId = $(parent[0]).attr("parent-element")
            element.Parent = parentId
        }
        elements.push(element);

        if(watchElement.is("input") || watchElement.is("select")) {
            watchElement.change(function() {
                var e = getElement(watchElement.attr("watch"))
                if(e == null && parentId != null) {
                    e = {
                        Key: watchElement.attr("watch"),
                        Value : null
                    }
                    var parent = getElement(parentId)
                    if(parent.Value == null) {
                        parent.Value = [[]]
                    }
                    parent.Value.push(e)
                }
                e.Value = watchElement.val()
                processChange()
                copySummary(e)
            })
        }

        if(watchElement.is("[type=radio]")) {
            watchElement.change(function() {
                if(watchElement.attr("checked")) {
                    var e = getElement(watchElement.attr("watch"))
                    if(e == null && parentId != null) {
                        e = {
                            Key: watchElement.attr("watch"),
                            Value : null
                        }
                        var parent = getElement(parentId)
                        if(parent.Value == null) {
                            parent.Value = [[]]
                        }
                        parent.Value.push(e)
                    }
                    e.Value = watchElement.val()
                    processChange()
                    copySummary(e)
                }
            })
        }

        if(watchElement.is("[type=checkbox]")) {
            watchElement.change(function() {
                var e = getElement(watchElement.attr("watch"))
                if(e == null && parentId != null) {
                    e = {
                        Key: watchElement.attr("watch"),
                        Value : null
                    }
                    var parent = getElement(parentId)
                    if(parent.Value == null) {
                        parent.Value = [[]]
                    }
                    parent.Value.push(e)
                }
                e.Value = watchElement.attr("checked")
                processChange()
                copySummary(e)
            })
        }

        if(watchElement.is("[type=button]")) {
            watchElement.click(function() {
                var e = getElement(watchElement.attr("watch"))

                var obj = {}
                var id = ""
                $("[parent-element=" + e.Key + "] [watch]").each(function(i, wEl){
                    var wKey = $(wEl).attr("watch")
                    var wE = getElement(wKey)

                    if(wE.Value == null || wE.Value == "") return;

                    obj[wKey] = wE.Value
                    if(i < 2) {
                        id = id + " " + obj[wKey]
                    }
                    $(wEl).val(null);
                    wE.Value = null
                });
                if(obj.length == 0) return;

                if(e.Value == null) e.Value = []

                e.Value.push(obj)

                $("#con_" + e.Key + " [watch-list]").append("

" + id + "

")

                processChange()
                copySummary(e)
            })
        }
    })

    initEffect()

    $("[validate-form]").click(function(){
        var toSend = []
        for(var i in elements) {
            var e = elements[i]
            if(e.Value != null && e.Parent == null) {
                toSend.push(e)
            }
        }
        console.log(toSend)
        $.ajax({
            type: "POST",
            url: "/Home/PushData",
            data: "data=" + JSON.stringify(convertToDictionary(toSend)),
            success: function() {
                window.location = window.location.href
            }
        });
    })

    $('select').material_select();
    $('.collapsible').collapsible();
    $('.datepicker').pickadate({
    selectMonths: false, // Creates a dropdown to control month
    selectYears: 15, // Creates a dropdown of 15 years to control year,
    today: 'Today',
    clear: 'Clear',
    close: 'Ok',
    closeOnSelect: true // Close upon selecting a date,
    });

    $(".yuffieNextButton").click(function(e) {
        var parent = $($(e.target).parents("li"));
        var next = parent.next();
        if(next.is("li")) {
            next.find(".collapsible-header").click();
        }
        else {
            var id = parent.parents(".page").attr("id");
            $("li.tab a[href='#" + id + "']").parents("li").next().find("a").click()
        }
    })

    var convertToDictionary = function(array) {
        var dic = {}
        for(var i in array) {
            var e = array[i]
            dic[e.Key] = e.Value
        }
        return dic;
    }
    exceptions();
})

var exceptions = function() {

}<|endoftext|>


<commit_before>var url = require('./url');
var parseURL = url.parseURL;
var extractURLs = url.extractURLs;

function Request (options) {
  var REQUIRED_ROOMS = process.env.HUBOT_REVIEW_REQUIRED_ROOMS || '';

  var text = options.text;
  var room = options.room;

  var URLs = extractURLs(text) || [];
  var requiredRooms = REQUIRED_ROOMS.split(',').filter(Boolean);

  var urlMap = {};

  var githubURLs = URLs.map(function (u) {
    if (urlMap[u]) {
      return false;
    }

    urlMap[u] = true;
    var uo = parseURL(u);
    return uo.hostname === 'github.com' ? uo : false;
  }).filter(Boolean);

  var isReview = false;
  var reviewAgain = false;

  for (var i = 0; i < githubURLs.length; i++) {
    var reviewRequest = text.indexOf('review ' + githubURLs[i].href) !== -1;
    var reviewAgainRequest = text.indexOf('review ' + githubURLs[i].href + ' again') !== -1;

    if (reviewRequest || reviewAgainRequest) {
      isReview = true;
    }

    if (reviewAgainRequest) {
      reviewAgain = true;
    }
  }

  if (isReview && requiredRooms.length && room !== undefined) {
    if (requiredRooms.indexOf(room) === -1) {
      throw Error('Review requests from this room are disabled');
    }
  }

  return {
    'isReview': isReview,
    'reviewAgain': reviewAgain,
    'githubURLs': githubURLs
  };
}

module.exports = Request;<commit_msg>make request parsing more tolerant<commit_after>var url = require('./url');
var parseURL = url.parseURL;
var extractURLs = url.extractURLs;

function Request (options) {
  var REQUIRED_ROOMS = process.env.HUBOT_REVIEW_REQUIRED_ROOMS || '';

  var text = options.text;
  var room = options.room;

  var URLs = extractURLs(text) || [];
  var requiredRooms = REQUIRED_ROOMS.split(',').filter(Boolean);

  var urlMap = {};

  var githubURLs = URLs.map(function (u) {
    if (urlMap[u]) {
      return false;
    }

    urlMap[u] = true;
    var uo = parseURL(u);
    return uo.hostname === 'github.com' ? uo : false;
  }).filter(Boolean);

  var isReview = false;
  var reviewAgain = false;
  var processedText = text.toLowerCase().replace(/\s+/g, ' ');

  for (var i = 0; i < githubURLs.length; i++) {
    var reviewRequest = processedText.indexOf('review ' + githubURLs[i].href) !== -1;
    var reviewAgainRequest = processedText.indexOf('review ' + githubURLs[i].href + ' again') !== -1;

    if (reviewRequest || reviewAgainRequest) {
      isReview = true;
    }

    if (reviewAgainRequest) {
      reviewAgain = true;
    }
  }

  if (isReview && requiredRooms.length && room !== undefined) {
    if (requiredRooms.indexOf(room) === -1) {
      throw Error('Review requests from this room are disabled');
    }
  }

  return {
    'isReview': isReview,
    'reviewAgain': reviewAgain,
    'githubURLs': githubURLs
  };
}

module.exports = Request;
<|endoftext|>


<commit_before>/**
 * Enhanced Select2 Dropmenus
 *
 * @AJAX Mode - When in this mode, your value will be an object (or array of objects) of the data used by Select2
 *     This change is so that you do not have to do an additional query yourself on top of Select2's own query
 * @params [options] {object} The configuration options passed to $.fn.select2(). Refer to the documentation
 */
angular.module('ui.select2', []).value('uiSelect2Config', {}).directive('uiSelect2', ['uiSelect2Config', '$timeout', function (uiSelect2Config, $timeout) {
  var options = {};
  if (uiSelect2Config) {
    angular.extend(options, uiSelect2Config);
  }
  return {
    require: 'ngModel',
    priority: 1,
    compile: function (tElm, tAttrs) {
      var watch,
        repeatOption,
        repeatAttr,
        isSelect = tElm.is('select'),
        isMultiple = angular.isDefined(tAttrs.multiple);

      // Enable watching of the options dataset if in use
      if (tElm.is('select')) {
        repeatOption = tElm.find('option[ng-repeat], option[data-ng-repeat]');

        if (repeatOption.length) {
          repeatAttr = repeatOption.attr('ng-repeat') || repeatOption.attr('data-ng-repeat');
          watch = jQuery.trim(repeatAttr.split('|')[0]).split(' ').pop();
        }
      }

      return function (scope, elm, attrs, controller) {
        // instance-specific options
        var opts = angular.extend({}, options, scope.$eval(attrs.uiSelect2));

        /*
        Convert from Select2 view-model to Angular view-model.
        */
        var convertToAngularModel = function(select2_data) {
          var model;
          if (opts.simple_tags) {
            model = [];
            angular.forEach(select2_data, function(value, index) {
              model.push(value.id);
            });
          } else {
            model = select2_data;
          }
          return model;
        };

        /*
        Convert from Angular view-model to Select2 view-model.
        */
        var convertToSelect2Model = function(angular_data) {
          var model = [];
          if (!angular_data) {
            return model;
          }

          if (opts.simple_tags) {
            model = [];
            angular.forEach(
              angular_data,
              function(value, index) {
                model.push({'id': value, 'text': value});
              });
          } else {
            model = angular_data;
          }
          return model;
        };

        if (isSelect) {
          // Use <select multiple> instead
          delete opts.multiple;
          delete opts.initSelection;
        } else if (isMultiple) {
          opts.multiple = true;
        }

        if (controller) {
          // Watch the model for programmatic changes
           scope.$watch(tAttrs.ngModel, function(current, old) {
            if (!current) {
              return;
            }
            if (current === old) {
              return;
            }
            controller.$render();
          }, true);
          controller.$render = function () {
            if (isSelect) {
              elm.select2('val', controller.$viewValue);
            } else {
              if (opts.multiple) {
                elm.select2(
                  'data', convertToSelect2Model(controller.$viewValue));
              } else {
                if (angular.isObject(controller.$viewValue)) {
                  elm.select2('data', controller.$viewValue);
                } else if (!controller.$viewValue) {
                  elm.select2('data', null);
                } else {
                  elm.select2('val', controller.$viewValue);
                }
              }
            }
          };

          // Watch the options dataset for changes
          if (watch) {
            scope.$watch(watch, function (newVal, oldVal, scope) {
              if (angular.equals(newVal, oldVal)) {
                return;
              }
              // Delayed so that the options have time to be rendered
              $timeout(function () {
                elm.select2('val', controller.$viewValue);
                // Refresh angular to remove the superfluous option
                elm.trigger('change');
                if(newVal && !oldVal && controller.$setPristine) {
                  controller.$setPristine(true);
                }
              });
            });
          }

          // Update valid and dirty statuses
          controller.$parsers.push(function (value) {
            var div = elm.prev();
            div
              .toggleClass('ng-invalid', !controller.$valid)
              .toggleClass('ng-valid', controller.$valid)
              .toggleClass('ng-invalid-required', !controller.$valid)
              .toggleClass('ng-valid-required', controller.$valid)
              .toggleClass('ng-dirty', controller.$dirty)
              .toggleClass('ng-pristine', controller.$pristine);
            return value;
          });

          if (!isSelect) {
            // Set the view and model value and update the angular template manually for the ajax/multiple select2.
            elm.bind("change", function () {
              if (scope.$$phase || scope.$root.$$phase) {
                return;
              }
              scope.$apply(function () {
                controller.$setViewValue(
                  convertToAngularModel(elm.select2('data')));
              });
            });

            if (opts.initSelection) {
              var initSelection = opts.initSelection;
              opts.initSelection = function (element, callback) {
                initSelection(element, function (value) {
                  controller.$setViewValue(convertToAngularModel(value));
                  callback(value);
                });
              };
            }
          }
        }

        elm.bind("$destroy", function() {
          elm.select2("destroy");
        });

        attrs.$observe('disabled', function (value) {
          elm.select2('enable', !value);
        });

        attrs.$observe('readonly', function (value) {
          elm.select2('readonly', !!value);
        });

        if (attrs.ngMultiple) {
          scope.$watch(attrs.ngMultiple, function(newVal) {
            elm.select2(opts);
          });
        }

        // Initialize the plugin late so that the injected DOM does not disrupt the template compiler
        $timeout(function () {
          elm.select2(opts);

          // Set initial value - I'm not sure about this but it seems to need to be there
          elm.val(controller.$viewValue);
          // important!
          controller.$render();

          // Not sure if I should just check for !isSelect OR if I should check for 'tags' key
          if (!opts.initSelection && !isSelect) {
            controller.$setViewValue(
              convertToAngularModel(elm.select2('data'))
            );
          }
        });
      };
    }
  };
}]);
<commit_msg>Fixed #57 - now select2 initializes also for optgroup<commit_after>/**
 * Enhanced Select2 Dropmenus
 *
 * @AJAX Mode - When in this mode, your value will be an object (or array of objects) of the data used by Select2
 *     This change is so that you do not have to do an additional query yourself on top of Select2's own query
 * @params [options] {object} The configuration options passed to $.fn.select2(). Refer to the documentation
 */
angular.module('ui.select2', []).value('uiSelect2Config', {}).directive('uiSelect2', ['uiSelect2Config', '$timeout', function (uiSelect2Config, $timeout) {
  var options = {};
  if (uiSelect2Config) {
    angular.extend(options, uiSelect2Config);
  }
  return {
    require: 'ngModel',
    priority: 1,
    compile: function (tElm, tAttrs) {
      var watch,
        repeatOption,
        repeatAttr,
        isSelect = tElm.is('select'),
        isMultiple = angular.isDefined(tAttrs.multiple);

      // Enable watching of the options dataset if in use
      if (tElm.is('select')) {
        repeatOption = tElm.find( 'optgroup[ng-repeat], optgroup[data-ng-repeat], option[ng-repeat], option[data-ng-repeat]');

        if (repeatOption.length) {
          repeatAttr = repeatOption.attr('ng-repeat') || repeatOption.attr('data-ng-repeat');
          watch = jQuery.trim(repeatAttr.split('|')[0]).split(' ').pop();
        }
      }

      return function (scope, elm, attrs, controller) {
        // instance-specific options
        var opts = angular.extend({}, options, scope.$eval(attrs.uiSelect2));

        /*
        Convert from Select2 view-model to Angular view-model.
        */
        var convertToAngularModel = function(select2_data) {
          var model;
          if (opts.simple_tags) {
            model = [];
            angular.forEach(select2_data, function(value, index) {
              model.push(value.id);
            });
          } else {
            model = select2_data;
          }
          return model;
        };

        /*
        Convert from Angular view-model to Select2 view-model.
        */
        var convertToSelect2Model = function(angular_data) {
          var model = [];
          if (!angular_data) {
            return model;
          }

          if (opts.simple_tags) {
            model = [];
            angular.forEach(
              angular_data,
              function(value, index) {
                model.push({'id': value, 'text': value});
              });
          } else {
            model = angular_data;
          }
          return model;
        };

        if (isSelect) {
          // Use <select multiple> instead
          delete opts.multiple;
          delete opts.initSelection;
        } else if (isMultiple) {
          opts.multiple = true;
        }

        if (controller) {
          // Watch the model for programmatic changes
           scope.$watch(tAttrs.ngModel, function(current, old) {
            if (!current) {
              return;
            }
            if (current === old) {
              return;
            }
            controller.$render();
          }, true);
          controller.$render = function () {
            if (isSelect) {
              elm.select2('val', controller.$viewValue);
            } else {
              if (opts.multiple) {
                elm.select2(
                  'data', convertToSelect2Model(controller.$viewValue));
              } else {
                if (angular.isObject(controller.$viewValue)) {
                  elm.select2('data', controller.$viewValue);
                } else if (!controller.$viewValue) {
                  elm.select2('data', null);
                } else {
                  elm.select2('val', controller.$viewValue);
                }
              }
            }
          };

          // Watch the options dataset for changes
          if (watch) {
            scope.$watch(watch, function (newVal, oldVal, scope) {
              if (angular.equals(newVal, oldVal)) {
                return;
              }
              // Delayed so that the options have time to be rendered
              $timeout(function () {
                elm.select2('val', controller.$viewValue);
                // Refresh angular to remove the superfluous option
                elm.trigger('change');
                if(newVal && !oldVal && controller.$setPristine) {
                  controller.$setPristine(true);
                }
              });
            });
          }

          // Update valid and dirty statuses
          controller.$parsers.push(function (value) {
            var div = elm.prev();
            div
              .toggleClass('ng-invalid', !controller.$valid)
              .toggleClass('ng-valid', controller.$valid)
              .toggleClass('ng-invalid-required', !controller.$valid)
              .toggleClass('ng-valid-required', controller.$valid)
              .toggleClass('ng-dirty', controller.$dirty)
              .toggleClass('ng-pristine', controller.$pristine);
            return value;
          });

          if (!isSelect) {
            // Set the view and model value and update the angular template manually for the ajax/multiple select2.
            elm.bind("change", function () {
              if (scope.$$phase || scope.$root.$$phase) {
                return;
              }
              scope.$apply(function () {
                controller.$setViewValue(
                  convertToAngularModel(elm.select2('data')));
              });
            });

            if (opts.initSelection) {
              var initSelection = opts.initSelection;
              opts.initSelection = function (element, callback) {
                initSelection(element, function (value) {
                  controller.$setViewValue(convertToAngularModel(value));
                  callback(value);
                });
              };
            }
          }
        }

        elm.bind("$destroy", function() {
          elm.select2("destroy");
        });

        attrs.$observe('disabled', function (value) {
          elm.select2('enable', !value);
        });

        attrs.$observe('readonly', function (value) {
          elm.select2('readonly', !!value);
        });

        if (attrs.ngMultiple) {
          scope.$watch(attrs.ngMultiple, function(newVal) {
            elm.select2(opts);
          });
        }

        // Initialize the plugin late so that the injected DOM does not disrupt the template compiler
        $timeout(function () {
          elm.select2(opts);

          // Set initial value - I'm not sure about this but it seems to need to be there
          elm.val(controller.$viewValue);
          // important!
          controller.$render();

          // Not sure if I should just check for !isSelect OR if I should check for 'tags' key
          if (!opts.initSelection && !isSelect) {
            controller.$setViewValue(
              convertToAngularModel(elm.select2('data'))
            );
          }
        });
      };
    }
  };
}]);
<|endoftext|>