<commit_before>
const RG = require('./rg.js');
RG.System = require('./system.js');
RG.Map = require('./map.js');
RG.Time = require('./time.js');

/* Game engine which handles turn scheduling, systems updates and in-game
 * messaging between objects. */
const Engine = function(eventPool) {

    // Ignore GUI commands by default
    this.isGUICommand = () => false;
    this.doGUICommand = null;

    this.nextActor = null;
    this.animation = null;
    this.animationCallback = null;

    this._levelMap = {}; // All levels, ID -> level
    this._activeLevels = []; // Only these levels are simulated
    this._scheduler = new RG.Time.Scheduler();
    this._msg = new RG.MessageHandler();
    this._eventPool = eventPool;

    this.visibleCells = [];

    this._cache = {};

    this.getMessages = () => this._msg.getMessages();
    this.hasNewMessages = () => this._msg.hasNew();
    this.clearMessages = () => { this._msg.clear();};

    //--------------------------------------------------------------
    // ECS SYSTEMS
    //--------------------------------------------------------------

    // These systems updated after each action. Order is important, for example,
    // animations should be seen before actors are killed
    this.systemOrder = ['AreaEffects', 'Disability', 'SpiritBind', 'BaseAction',
        'Attack', 'Chat',
        'Shop', 'SpellCast',
        'SpellEffect', 'Missile', 'Movement', 'Animation', 'Damage',
        'Battle', 'Skills', 'ExpPoints', 'Communication',
        'Events'];

    this.systems = {};
    this.systems.Disability = new RG.System.Disability(
        ['Stun', 'Paralysis']);
    this.systems.SpiritBind = new RG.System.SpiritBind(['SpiritBind']);
    this.systems.BaseAction = new RG.System.BaseAction(['Pickup',
        'UseStairs', 'OpenDoor', 'UseItem', 'UseElement', 'Jump']);
    this.systems.Chat = new RG.System.Chat(['Chat']);
    this.systems.Shop = new RG.System.Shop(['Transaction']);
    this.systems.Attack = new RG.System.Attack(['Attack']);
    this.systems.Missile = new RG.System.Missile(['Missile']);
    this.systems.Movement = new RG.System.Movement(['Movement']);
    this.systems.SpellCast = new RG.System.SpellCast(['SpellCast',
        'PowerDrain']);
    this.systems.SpellEffect = new RG.System.SpellEffect(
        ['SpellRay', 'SpellCell', 'SpellMissile', 'SpellArea', 'SpellSelf']);
    this.systems.Animation = new RG.System.Animation(
        ['Animation']);
    this.systems.Damage = new RG.System.Damage(['Damage', 'Health']);
    this.systems.Battle = new RG.System.Battle(['BattleOver', 'BattleOrder']);
    this.systems.Skills = new RG.System.Skills(['SkillsExp']);
    this.systems.ExpPoints = new RG.System.ExpPoints(
        ['ExpPoints', 'Experience']);
    this.systems.Communication = new RG.System.Communication(
        ['Communication']);
    this.systems.Events = new RG.System.Events(['Event']);
    this.systems.AreaEffects = new RG.System.AreaEffects(['Fire']);

    // Systems updated once each game loop (once for each player action)
    this.loopSystemOrder = ['Hunger'];
    this.loopSystems = {};
    this.loopSystems.Hunger = new RG.System.Hunger(['Action', 'Hunger']);

    // Time-based systems are added to the scheduler directly
    this.timeSystems = {};

    const effects = new RG.System.TimeEffects(
        ['Expiration', 'Poison', 'Fading', 'Heat', 'Coldness']);

    this.updateSystems = function() {
        for (let i = 0; i < this.systemOrder.length; i++) {
            const sysName = this.systemOrder[i];
            this.systems[sysName].update();
        }
    };

    this.updateLoopSystems = function() {
        for (let i = 0; i < this.loopSystemOrder.length; i++) {
            const sysName = this.loopSystemOrder[i];
            this.loopSystems[sysName].update();
        }
    };

    //--------------------------------------------------------------
    // GAME LOOPS
    //--------------------------------------------------------------

    /* Main update command. Call this either with cmd to perform, or object
     * containing the pressed keycode. */
    this.update = function(obj) {
        if (!this.isGameOver()) {
            this.clearMessages();

            if (this.nextActor !== null) {
                if (obj.hasOwnProperty('code')) {
                    const code = obj.code;
                    if (this.isGUICommand(code)) {
                        this.doGUICommand(code);
                        this.playerCommandCallback(this.nextActor);
                    }
                    else {
                        this.updateGameLoop({code});
                    }
                }
                else {
                    this.updateGameLoop(obj);
                }
            }

        }
        else {
            this.clearMessages();
            this._eventPool.emitEvent(RG.EVT_MSG, {msg: 'GAME OVER!'});
            this.simulateGame(100);
        }
    };

    /* Updates the loop by executing one player command, then looping until
     * next player command.*/
    this.updateGameLoop = function(obj) {
        this.playerCommand(obj);
        this.currPlayer = this.nextActor;
        this.nextActor = this.getNextActor();

        this.updateLoopSystems(); // Loop systems once per player action

        // Next/act until player found, then go back waiting for key...
        while (!this.nextActor.isPlayer() && !this.isGameOver()) {
            const action = this.nextActor.nextAction();
            this.doAction(action);

            this.updateSystems(); // All systems for each actor

            this.nextActor = this.getNextActor();
            if (RG.isNullOrUndef([this.nextActor])) {
                RG.err('Game.Engine', 'updateGameLoop',
                    'Game loop out of events! Fatal!');
                break; // if errors suppressed (testing), breaks the loop
            }
        }
        if (!this.isGameOver()) {
            this.setPlayer(this.nextActor);
        }

    };

    this.getPlayer = function() {
        return this.currPlayer;
    };

    this.setPlayer = function(player) {
        this.currPlayer = player;
    };

    /* Simulates the game without a player.*/
    this.simulateGame = function(nTurns = 1) {
        for (let i = 0; i < nTurns; i++) {
            this.nextActor = this.getNextActor();

            if (!this.nextActor.isPlayer()) {
                const action = this.nextActor.nextAction();
                this.doAction(action);
                this.updateSystems();
            }
            else {
                RG.err('Engine', 'simulateGame',
                    "Doesn't work with player.");
            }
        }
    };

    this.playerCommand = function(obj) {
        if (this.nextActor.isPlayer() === false) {
            let msg = '';
            if (this.nextActor.hasOwnProperty('isEvent')) {
                msg = 'Expected player, got an event: ';
            }
            else {
                msg = 'Expected player, got: ' + this.nextActor.getName();
            }
            msg += '\n' + JSON.stringify(this.nextActor);
            RG.err('Engine', 'playerCommand', msg);
        }
        const action = this.nextActor.nextAction(obj);
        this.doAction(action);
        this.updateSystems();
        this.playerCommandCallback(this.nextActor);
    };

    //--------------------------------------------------------------
    // MANAGING ACTIVE LEVELS
    //--------------------------------------------------------------

    this.numActiveLevels = () => this._activeLevels.length;

    this.hasLevel = function(level) {
        return this._levelMap.hasOwnProperty(level.getID());
    };

    this.getLevels = function() {
        return Object.values(this._levelMap);
    };

    /* Adds one level to the game database.*/
    this.addLevel = function(level) {
        const id = level.getID();
        if (!this._levelMap.hasOwnProperty(id)) {
            this._levelMap[level.getID()] = level;
        }
        else {
            RG.err('Game.Engine', 'addLevel',
                'Level ID ' + id + ' already exists!');
        }
    };

    this.removeLevels = function(levels) {
        levels.forEach(level => {
            const id = level.getID();
            if (this._levelMap.hasOwnProperty(id)) {
                const index = this._activeLevels.indexOf(id);
                if (index >= 0) {
                    this._activeLevels.splice(index, 1);

                    const removedLevel = this._levelMap[id];
                    if (removedLevel) {
                        const rmvActors = removedLevel.getActors();
                        for (let i = 0; i < rmvActors.length; i++) {
                            rmvActors[i].get('Action').disable();
                        }
                    }
                }
                delete this._levelMap[id];
            }
            else {
                RG.err('Game.Engine', 'removeLevels',
                    `No level with ID ${id}`);
            }
        });
    };

    /* Adds an active level. Only these levels are simulated.*/
    this.addActiveLevel = function(level) {
        const levelID = level.getID();
        const index = this._activeLevels.indexOf(levelID);

        // Check if a level must be removed
        if (this._activeLevels.length === (RG.MAX_ACTIVE_LEVELS)) {
            if (index === -1) { // No room for new level, pop one
                const removedLevelID = this._activeLevels.pop();
                const removedLevel = this._levelMap[removedLevelID];
                if (removedLevel) {
                    const rmvActors = removedLevel.getActors();
                    for (let i = 0; i < rmvActors.length; i++) {
                        rmvActors[i].get('Action').disable();
                    }
                    RG.debug(this, 'Removed active level to make space...');
                }
                else {
                    const levelIDs = Object.keys(this._levelMap).join(', ');
                    RG.err('Game.Engine', 'addActiveLevel',
                        `Failed to remove level ID ${removedLevelID}.
                        IDs: ${levelIDs}`);
                }
            }
            else { // Level already in actives, move to the front only
                this._activeLevels.splice(index, 1);
                this._activeLevels.unshift(levelID);
                RG.debug(this, 'Moved level to the front of active levels.');
            }
        }

        // This is a new level, enable all actors by enabling Action comp
        if (index === -1) {
            this._activeLevels.unshift(levelID);
            const actActors = level.getActors();
            for (let j = 0; j < actActors.length; j++) {
                actActors[j].get('Action').enable();
            }
        }
    };

    this.isGameOver = () => false;

    this.isActiveLevel = level => {
        const index = this._activeLevels.indexOf(level.getID());
        return index >= 0;
    };

    /* Adds a TimeSystem into the engine. Each system can be updated with given
     * intervals instead of every turn or loop.*/
    this.addTimeSystem = function(name, obj) {
        this.timeSystems[name] = obj;
        // Must schedule the system to activate it
        const updateEvent = new RG.Time.GameEvent(100,
            obj.update.bind(obj), true, 0);
        this.addEvent(updateEvent);
    };
    this.addTimeSystem('TimeEffects', effects);

    //--------------------------------------------------------------
    // EVENT LISTENING
    //--------------------------------------------------------------

    this.hasNotify = true;
    this.notify = function(evtName, args) {
        if (evtName === RG.EVT_DESTROY_ITEM) {
            const item = args.item;

            // chaining due to inventory container
            const owner = item.getOwner().getOwner();
            if (!owner.getInvEq().removeItem(item)) {
                RG.err('Game.Engine', 'notify - DESTROY_ITEM',
                    'Failed to remove item from inventory.');
            }
        }
        else if (evtName === RG.EVT_ACT_COMP_ADDED) {
            if (args.hasOwnProperty('actor')) {
                this.addActor(args.actor);
            }
            else {
                RG.err('Game.Engine', 'notify - ACT_COMP_ADDED',
                    'No actor specified for the event.');
            }
        }
        else if (evtName === RG.EVT_ACT_COMP_REMOVED) {
            if (args.hasOwnProperty('actor')) {
                this.removeActor(args.actor);
            }
            else {
                RG.err('Game.Engine', 'notify - ACT_COMP_REMOVED',
                    'No actor specified for the event.');
            }
        }
        else if (evtName === RG.EVT_ACT_COMP_ENABLED) {
            if (args.hasOwnProperty('actor')) {
                this.addActor(args.actor);
            }
            else {
                RG.err('Game.Engine', 'notify - ACT_COMP_ENABLED',
                    'No actor specified for the event.');
            }
        }
        else if (evtName === RG.EVT_ACT_COMP_DISABLED) {
            if (args.hasOwnProperty('actor')) {
                this.removeActor(args.actor);
            }
            else {
                RG.err('Game', 'notify - ACT_COMP_DISABLED',
                    'No actor specified for the event.');
            }
        }
        else if (evtName === RG.EVT_LEVEL_PROP_ADDED) {
            if (args.propType === 'actors') {
                if (this.isActiveLevel(args.level)) {
                    // args.obj is actor
                    args.obj.get('Action').enable();
                }
            }
        }
        else if (evtName === RG.EVT_LEVEL_CHANGED) {
            const actor = args.actor;
            if (actor.isPlayer()) {
                this.addActiveLevel(actor.getLevel());
                args.src.onExit();
                args.src.onFirstExit();
                args.target.onEnter();
                args.target.onFirstEnter();
            }
        }
        else if (evtName === RG.EVT_ANIMATION) {
            if (this.canPlayerSeeAnimation(args.animation)) {
                if (this.animationCallback) {
                    if (this.animation) {
                        this.animation.combine(args.animation);
                    }
                    else {
                        this.animation = args.animation;
                    }
                    this.animationCallback(this.animation);
                }
            }
        }
    };
    this._eventPool.listenEvent(RG.EVT_DESTROY_ITEM, this);
    this._eventPool.listenEvent(RG.EVT_ACT_COMP_ADDED, this);
    this._eventPool.listenEvent(RG.EVT_ACT_COMP_REMOVED, this);
    this._eventPool.listenEvent(RG.EVT_ACT_COMP_ENABLED, this);
    this._eventPool.listenEvent(RG.EVT_ACT_COMP_DISABLED, this);
    this._eventPool.listenEvent(RG.EVT_LEVEL_PROP_ADDED, this);
    this._eventPool.listenEvent(RG.EVT_LEVEL_CHANGED, this);
    this._eventPool.listenEvent(RG.EVT_ANIMATION, this);

    this.hasAnimation = function() {
        return this.animation !== null &&
            this.animation.hasFrames();
    };

    this.finishAnimation = function() {
        this.animation = null;
    };

    this.setVisibleArea = (level, cells) => {
        this.visibleLevelID = level.getID();
        this.visibleCells = cells;
        this._cache.visibleCoord = {};
    };

    /* Returns true if player can see the given animation. In general, true
     * whenever animation contains at least one cell visible to the player. */
    this.canPlayerSeeAnimation = animation => {
        if (animation.levelID === this.visibleLevelID) {
            if (Object.keys(this._cache.visibleCoord).length === 0) {
                this.visibleCells.forEach(cell => {
                    const [x, y] = [cell.getX(), cell.getY()];
                    this._cache.visibleCoord[x + ',' + y] = true;
                });
            }
            return animation.hasCoord(this._cache.visibleCoord);
        }
        return false;
    };

    this.enableAnimations = () => {
        this.systems['Animation'].enableAnimations();
    };

    this.disableAnimations = () => {
        this.systems['Animation'].disableAnimations();
    };

};

//--------------------------------------------------------------
// SCHEDULING/ACTIONS
//--------------------------------------------------------------

/* Returns next actor from the scheduling queue.*/
Engine.prototype.getNextActor = function() {
    return this._scheduler.next();
};

/* Adds an actor to the scheduler. */
Engine.prototype.addActor = function(actor) {
    this._scheduler.add(actor, true, 0);
};

/* Removes an actor from a scheduler.*/
Engine.prototype.removeActor = function(actor) {
    this._scheduler.remove(actor);
};

/* Adds an event to the scheduler.*/
Engine.prototype.addEvent = function(gameEvent) {
    const repeat = gameEvent.getRepeat();
    const offset = gameEvent.getOffset();
    this._scheduler.add(gameEvent, repeat, offset);
};

/* Performs one game action.*/
Engine.prototype.doAction = function(action) {
    this._scheduler.setAction(action);
    action.doAction();
    if (action.hasOwnProperty('energy')) {
        if (action.hasOwnProperty('actor')) {
            const actor = action.actor;
            if (actor.has('Action')) {
                actor.get('Action').addEnergy(action.energy);
            }
        }
    }
};

module.exports = Engine;
<commit_msg>Changed comp Fire -> Flame.<commit_after>
const RG = require('./rg.js');
RG.System = require('./system.js');
RG.Map = require('./map.js');
RG.Time = require('./time.js');

/* Game engine which handles turn scheduling, systems updates and in-game
 * messaging between objects. */
const Engine = function(eventPool) {

    // Ignore GUI commands by default
    this.isGUICommand = () => false;
    this.doGUICommand = null;

    this.nextActor = null;
    this.animation = null;
    this.animationCallback = null;

    this._levelMap = {}; // All levels, ID -> level
    this._activeLevels = []; // Only these levels are simulated
    this._scheduler = new RG.Time.Scheduler();
    this._msg = new RG.MessageHandler();
    this._eventPool = eventPool;

    this.visibleCells = [];

    this._cache = {};

    this.getMessages = () => this._msg.getMessages();
    this.hasNewMessages = () => this._msg.hasNew();
    this.clearMessages = () => { this._msg.clear();};

    //--------------------------------------------------------------
    // ECS SYSTEMS
    //--------------------------------------------------------------

    // These systems updated after each action. Order is important, for example,
    // animations should be seen before actors are killed
    this.systemOrder = ['AreaEffects', 'Disability', 'SpiritBind', 'BaseAction',
        'Attack', 'Chat',
        'Shop', 'SpellCast',
        'SpellEffect', 'Missile', 'Movement', 'Animation', 'Damage',
        'Battle', 'Skills', 'ExpPoints', 'Communication',
        'Events'];

    this.systems = {};
    this.systems.Disability = new RG.System.Disability(
        ['Stun', 'Paralysis']);
    this.systems.SpiritBind = new RG.System.SpiritBind(['SpiritBind']);
    this.systems.BaseAction = new RG.System.BaseAction(['Pickup',
        'UseStairs', 'OpenDoor', 'UseItem', 'UseElement', 'Jump']);
    this.systems.Chat = new RG.System.Chat(['Chat']);
    this.systems.Shop = new RG.System.Shop(['Transaction']);
    this.systems.Attack = new RG.System.Attack(['Attack']);
    this.systems.Missile = new RG.System.Missile(['Missile']);
    this.systems.Movement = new RG.System.Movement(['Movement']);
    this.systems.SpellCast = new RG.System.SpellCast(['SpellCast',
        'PowerDrain']);
    this.systems.SpellEffect = new RG.System.SpellEffect(
        ['SpellRay', 'SpellCell', 'SpellMissile', 'SpellArea', 'SpellSelf']);
    this.systems.Animation = new RG.System.Animation(
        ['Animation']);
    this.systems.Damage = new RG.System.Damage(['Damage', 'Health']);
    this.systems.Battle = new RG.System.Battle(['BattleOver', 'BattleOrder']);
    this.systems.Skills = new RG.System.Skills(['SkillsExp']);
    this.systems.ExpPoints = new RG.System.ExpPoints(
        ['ExpPoints', 'Experience']);
    this.systems.Communication = new RG.System.Communication(
        ['Communication']);
    this.systems.Events = new RG.System.Events(['Event']);
    this.systems.AreaEffects = new RG.System.AreaEffects(['Flame']);

    // Systems updated once each game loop (once for each player action)
    this.loopSystemOrder = ['Hunger'];
    this.loopSystems = {};
    this.loopSystems.Hunger = new RG.System.Hunger(['Action', 'Hunger']);

    // Time-based systems are added to the scheduler directly
    this.timeSystems = {};

    const effects = new RG.System.TimeEffects(
        ['Expiration', 'Poison', 'Fading', 'Heat', 'Coldness']);

    this.updateSystems = function() {
        for (let i = 0; i < this.systemOrder.length; i++) {
            const sysName = this.systemOrder[i];
            this.systems[sysName].update();
        }
    };

    this.updateLoopSystems = function() {
        for (let i = 0; i < this.loopSystemOrder.length; i++) {
            const sysName = this.loopSystemOrder[i];
            this.loopSystems[sysName].update();
        }
    };

    //--------------------------------------------------------------
    // GAME LOOPS
    //--------------------------------------------------------------

    /* Main update command. Call this either with cmd to perform, or object
     * containing the pressed keycode. */
    this.update = function(obj) {
        if (!this.isGameOver()) {
            this.clearMessages();

            if (this.nextActor !== null) {
                if (obj.hasOwnProperty('code')) {
                    const code = obj.code;
                    if (this.isGUICommand(code)) {
                        this.doGUICommand(code);
                        this.playerCommandCallback(this.nextActor);
                    }
                    else {
                        this.updateGameLoop({code});
                    }
                }
                else {
                    this.updateGameLoop(obj);
                }
            }

        }
        else {
            this.clearMessages();
            this._eventPool.emitEvent(RG.EVT_MSG, {msg: 'GAME OVER!'});
            this.simulateGame(100);
        }
    };

    /* Updates the loop by executing one player command, then looping until
     * next player command.*/
    this.updateGameLoop = function(obj) {
        this.playerCommand(obj);
        this.currPlayer = this.nextActor;
        this.nextActor = this.getNextActor();

        this.updateLoopSystems(); // Loop systems once per player action

        // Next/act until player found, then go back waiting for key...
        while (!this.nextActor.isPlayer() && !this.isGameOver()) {
            const action = this.nextActor.nextAction();
            this.doAction(action);

            this.updateSystems(); // All systems for each actor

            this.nextActor = this.getNextActor();
            if (RG.isNullOrUndef([this.nextActor])) {
                RG.err('Game.Engine', 'updateGameLoop',
                    'Game loop out of events! Fatal!');
                break; // if errors suppressed (testing), breaks the loop
            }
        }
        if (!this.isGameOver()) {
            this.setPlayer(this.nextActor);
        }

    };

    this.getPlayer = function() {
        return this.currPlayer;
    };

    this.setPlayer = function(player) {
        this.currPlayer = player;
    };

    /* Simulates the game without a player.*/
    this.simulateGame = function(nTurns = 1) {
        for (let i = 0; i < nTurns; i++) {
            this.nextActor = this.getNextActor();

            if (!this.nextActor.isPlayer()) {
                const action = this.nextActor.nextAction();
                this.doAction(action);
                this.updateSystems();
            }
            else {
                RG.err('Engine', 'simulateGame',
                    "Doesn't work with player.");
            }
        }
    };

    this.playerCommand = function(obj) {
        if (this.nextActor.isPlayer() === false) {
            let msg = '';
            if (this.nextActor.hasOwnProperty('isEvent')) {
                msg = 'Expected player, got an event: ';
            }
            else {
                msg = 'Expected player, got: ' + this.nextActor.getName();
            }
            msg += '\n' + JSON.stringify(this.nextActor);
            RG.err('Engine', 'playerCommand', msg);
        }
        const action = this.nextActor.nextAction(obj);
        this.doAction(action);
        this.updateSystems();
        this.playerCommandCallback(this.nextActor);
    };

    //--------------------------------------------------------------
    // MANAGING ACTIVE LEVELS
    //--------------------------------------------------------------

    this.numActiveLevels = () => this._activeLevels.length;

    this.hasLevel = function(level) {
        return this._levelMap.hasOwnProperty(level.getID());
    };

    this.getLevels = function() {
        return Object.values(this._levelMap);
    };

    /* Adds one level to the game database.*/
    this.addLevel = function(level) {
        const id = level.getID();
        if (!this._levelMap.hasOwnProperty(id)) {
            this._levelMap[level.getID()] = level;
        }
        else {
            RG.err('Game.Engine', 'addLevel',
                'Level ID ' + id + ' already exists!');
        }
    };

    this.removeLevels = function(levels) {
        levels.forEach(level => {
            const id = level.getID();
            if (this._levelMap.hasOwnProperty(id)) {
                const index = this._activeLevels.indexOf(id);
                if (index >= 0) {
                    this._activeLevels.splice(index, 1);

                    const removedLevel = this._levelMap[id];
                    if (removedLevel) {
                        const rmvActors = removedLevel.getActors();
                        for (let i = 0; i < rmvActors.length; i++) {
                            rmvActors[i].get('Action').disable();
                        }
                    }
                }
                delete this._levelMap[id];
            }
            else {
                RG.err('Game.Engine', 'removeLevels',
                    `No level with ID ${id}`);
            }
        });
    };

    /* Adds an active level. Only these levels are simulated.*/
    this.addActiveLevel = function(level) {
        const levelID = level.getID();
        const index = this._activeLevels.indexOf(levelID);

        // Check if a level must be removed
        if (this._activeLevels.length === (RG.MAX_ACTIVE_LEVELS)) {
            if (index === -1) { // No room for new level, pop one
                const removedLevelID = this._activeLevels.pop();
                const removedLevel = this._levelMap[removedLevelID];
                if (removedLevel) {
                    const rmvActors = removedLevel.getActors();
                    for (let i = 0; i < rmvActors.length; i++) {
                        rmvActors[i].get('Action').disable();
                    }
                    RG.debug(this, 'Removed active level to make space...');
                }
                else {
                    const levelIDs = Object.keys(this._levelMap).join(', ');
                    RG.err('Game.Engine', 'addActiveLevel',
                        `Failed to remove level ID ${removedLevelID}.
                        IDs: ${levelIDs}`);
                }
            }
            else { // Level already in actives, move to the front only
                this._activeLevels.splice(index, 1);
                this._activeLevels.unshift(levelID);
                RG.debug(this, 'Moved level to the front of active levels.');
            }
        }

        // This is a new level, enable all actors by enabling Action comp
        if (index === -1) {
            this._activeLevels.unshift(levelID);
            const actActors = level.getActors();
            for (let j = 0; j < actActors.length; j++) {
                actActors[j].get('Action').enable();
            }
        }
    };

    this.isGameOver = () => false;

    this.isActiveLevel = level => {
        const index = this._activeLevels.indexOf(level.getID());
        return index >= 0;
    };

    /* Adds a TimeSystem into the engine. Each system can be updated with given
     * intervals instead of every turn or loop.*/
    this.addTimeSystem = function(name, obj) {
        this.timeSystems[name] = obj;
        // Must schedule the system to activate it
        const updateEvent = new RG.Time.GameEvent(100,
            obj.update.bind(obj), true, 0);
        this.addEvent(updateEvent);
    };
    this.addTimeSystem('TimeEffects', effects);

    //--------------------------------------------------------------
    // EVENT LISTENING
    //--------------------------------------------------------------

    this.hasNotify = true;
    this.notify = function(evtName, args) {
        if (evtName === RG.EVT_DESTROY_ITEM) {
            const item = args.item;

            // chaining due to inventory container
            const owner = item.getOwner().getOwner();
            if (!owner.getInvEq().removeItem(item)) {
                RG.err('Game.Engine', 'notify - DESTROY_ITEM',
                    'Failed to remove item from inventory.');
            }
        }
        else if (evtName === RG.EVT_ACT_COMP_ADDED) {
            if (args.hasOwnProperty('actor')) {
                this.addActor(args.actor);
            }
            else {
                RG.err('Game.Engine', 'notify - ACT_COMP_ADDED',
                    'No actor specified for the event.');
            }
        }
        else if (evtName === RG.EVT_ACT_COMP_REMOVED) {
            if (args.hasOwnProperty('actor')) {
                this.removeActor(args.actor);
            }
            else {
                RG.err('Game.Engine', 'notify - ACT_COMP_REMOVED',
                    'No actor specified for the event.');
            }
        }
        else if (evtName === RG.EVT_ACT_COMP_ENABLED) {
            if (args.hasOwnProperty('actor')) {
                this.addActor(args.actor);
            }
            else {
                RG.err('Game.Engine', 'notify - ACT_COMP_ENABLED',
                    'No actor specified for the event.');
            }
        }
        else if (evtName === RG.EVT_ACT_COMP_DISABLED) {
            if (args.hasOwnProperty('actor')) {
                this.removeActor(args.actor);
            }
            else {
                RG.err('Game', 'notify - ACT_COMP_DISABLED',
                    'No actor specified for the event.');
            }
        }
        else if (evtName === RG.EVT_LEVEL_PROP_ADDED) {
            if (args.propType === 'actors') {
                if (this.isActiveLevel(args.level)) {
                    // args.obj is actor
                    args.obj.get('Action').enable();
                }
            }
        }
        else if (evtName === RG.EVT_LEVEL_CHANGED) {
            const actor = args.actor;
            if (actor.isPlayer()) {
                this.addActiveLevel(actor.getLevel());
                args.src.onExit();
                args.src.onFirstExit();
                args.target.onEnter();
                args.target.onFirstEnter();
            }
        }
        else if (evtName === RG.EVT_ANIMATION) {
            if (this.canPlayerSeeAnimation(args.animation)) {
                if (this.animationCallback) {
                    if (this.animation) {
                        this.animation.combine(args.animation);
                    }
                    else {
                        this.animation = args.animation;
                    }
                    this.animationCallback(this.animation);
                }
            }
        }
    };
    this._eventPool.listenEvent(RG.EVT_DESTROY_ITEM, this);
    this._eventPool.listenEvent(RG.EVT_ACT_COMP_ADDED, this);
    this._eventPool.listenEvent(RG.EVT_ACT_COMP_REMOVED, this);
    this._eventPool.listenEvent(RG.EVT_ACT_COMP_ENABLED, this);
    this._eventPool.listenEvent(RG.EVT_ACT_COMP_DISABLED, this);
    this._eventPool.listenEvent(RG.EVT_LEVEL_PROP_ADDED, this);
    this._eventPool.listenEvent(RG.EVT_LEVEL_CHANGED, this);
    this._eventPool.listenEvent(RG.EVT_ANIMATION, this);

    this.hasAnimation = function() {
        return this.animation !== null &&
            this.animation.hasFrames();
    };

    this.finishAnimation = function() {
        this.animation = null;
    };

    this.setVisibleArea = (level, cells) => {
        this.visibleLevelID = level.getID();
        this.visibleCells = cells;
        this._cache.visibleCoord = {};
    };

    /* Returns true if player can see the given animation. In general, true
     * whenever animation contains at least one cell visible to the player. */
    this.canPlayerSeeAnimation = animation => {
        if (animation.levelID === this.visibleLevelID) {
            if (Object.keys(this._cache.visibleCoord).length === 0) {
                this.visibleCells.forEach(cell => {
                    const [x, y] = [cell.getX(), cell.getY()];
                    this._cache.visibleCoord[x + ',' + y] = true;
                });
            }
            return animation.hasCoord(this._cache.visibleCoord);
        }
        return false;
    };

    this.enableAnimations = () => {
        this.systems['Animation'].enableAnimations();
    };

    this.disableAnimations = () => {
        this.systems['Animation'].disableAnimations();
    };

};

//--------------------------------------------------------------
// SCHEDULING/ACTIONS
//--------------------------------------------------------------

/* Returns next actor from the scheduling queue.*/
Engine.prototype.getNextActor = function() {
    return this._scheduler.next();
};

/* Adds an actor to the scheduler. */
Engine.prototype.addActor = function(actor) {
    this._scheduler.add(actor, true, 0);
};

/* Removes an actor from a scheduler.*/
Engine.prototype.removeActor = function(actor) {
    this._scheduler.remove(actor);
};

/* Adds an event to the scheduler.*/
Engine.prototype.addEvent = function(gameEvent) {
    const repeat = gameEvent.getRepeat();
    const offset = gameEvent.getOffset();
    this._scheduler.add(gameEvent, repeat, offset);
};

/* Performs one game action.*/
Engine.prototype.doAction = function(action) {
    this._scheduler.setAction(action);
    action.doAction();
    if (action.hasOwnProperty('energy')) {
        if (action.hasOwnProperty('actor')) {
            const actor = action.actor;
            if (actor.has('Action')) {
                actor.get('Action').addEnergy(action.energy);
            }
        }
    }
};

module.exports = Engine;
<|endoftext|>


<commit_before>;jQuery(function ($) {
    'use strict';

    $.plugin('collapseCart', {

        defaults: {

            /**
             * URL that will be called when the plugin is fetching the cart.
             *
             * @type {String}
             */
            'ajaxCartURL': $.controller['ajax_cart'],

            /**
             * Selector for the trigger element.
             * The trigger is the element that attaches to the click/tap/hover events.
             *
             * @type {String}
             */
            'triggerElSelector': '.navigation--entry.entry--cart',

            /**
             * Selector for the elements item container.
             *
             * @type {String}
             */
            'itemContainerSelector': '.item--container',

            /**
             * Selector for the remove button of single items.
             *
             * @type {String}
             */
            'removeItemSelector': '.action--remove',

            /**
             * Selector for the offcanvas close button.
             *
             * @type {String}
             */
            'offcanvasCloseElSelector': '.close--off-canvas',

            /**
             * Class for the loading indicator icon.
             *
             * @type {String}
             */
            'loadingIconClass': 'icon--loading-indicator',

            /**
             * Class that will be used for the loading icon wrapper.
             *
             * @type {String}
             */
            'loadingIconWrapperClass': 'ajax--cart',

            /**
             * Class that will be applied to the main plugin element when the menu opens.
             *
             * @type {String}
             */
            'activeClass': 'is--shown',

            /**
             * Mode of displaying the cart.
             * Can either be "collapsible" or "offcanvas".
             *
             * @type {String} displayMode
             */
            'displayMode': 'collapsible'
        },

        /**
         * Initializes the plugin and create all needed elements.
         */
        init: function () {
            var me = this,
                opts;

            // Override options with data attributes.
            me.applyDataAttributes();

            opts = me.opts;

            /**
             * Element that the events get attached to.
             *
             * @private
             * @property _$triggerEl
             * @type {jQuery}
             */
            me._$triggerEl = $(opts.triggerElSelector);

            /**
             * Loading icon that will be used for loading when an AJAX request is send.
             *
             * @private
             * @property _$loadingIcon
             * @type {jQuery}
             */
            me._$loadingIcon = $('<i>', {
                'class': opts.loadingIconClass
            });

            /**
             * Flag whether or not the menu is opened.
             *
             * @private
             * @property _isOpened
             * @type {Boolean}
             */
            me._isOpened = false;

            // if the display mode is "offcanvas", call the offcanvas plugin.
            if (me.isDisplayMode('offcanvas')) {
                me._$triggerEl.offcanvasMenu({
                    'offCanvasSelector': me.$el,
                    'direction': 'fromRight'
                });
            }

            me.registerEvents();
        },

        /**
         * Registers all needed events specific to the display mode.
         *
         * @public
         * @method registerEvents
         */
        registerEvents: function () {
            var me = this;

            me.$el.on(me.getEventName('click'), me.opts.removeItemSelector, $.proxy(me.onRemoveButtonClick, me));
            me.$el.on(me.getEventName('click touchstart'), me.opts.offcanvasCloseElSelector, $.proxy(me.onCloseButtonClick, me));

            if (me.isDisplayMode('offcanvas')) {
                me._on(me._$triggerEl, 'click touchstart', $.proxy(me.onMouseEnter, me));

                $.subscribe('plugin/addArticle/onAddArticle', $.proxy(me.onArticleAdded, me));
                $.subscribe('plugin/addArticle/onBeforeAddArticle', $.proxy(me.onBeforeAddArticle, me));
            } else {
                me._on(me._$triggerEl, 'mouseenter touchstart', $.proxy(me.onMouseEnter, me));
                me._on(me._$triggerEl, 'mouseleave', $.proxy(me.onMouseLeave, me));
                me._on(me.$el, 'mouseleave', $.proxy(me.onMouseLeave, me));
            }
        },

        /**
         * Will be fired from the addArticle plugin before the add-AJAX request will be send.
         * Sets the loading indicator as the content and opens the menu.
         *
         * @event onBeforeAddArticle
         */
        onBeforeAddArticle: function () {
            var me = this;

            me.showLoadingIndicator();
            me.openMenu();
        },

        /**
         * Will be fired from the addArticle plugin before the add-AJAX request is finished.
         * Loads the cart via AJAX and appends it to the basket.
         *
         * @event onArticleAdded
         */
        onArticleAdded: function (event, plugin, response) {
            var me = this;

            if (me.isDisplayMode('collapsible')) {
                return;
            }

            me.$el
                .html(response)
                .find('.ajax--cart .alert')
                .removeClass('is--hidden');

            picturefill();
        },

        /**
         * Will be called when the pointer enters/clicks/taps the trigger element.
         *
         * @event onMouseEnter
         * @param {jQuery.Event} event
         */
        onMouseEnter: function (event) {
            var me = this;

            me.showLoadingIndicator();
            me.openMenu();

            if (me.isDisplayMode('offcanvas')) {
                event.preventDefault();

                me.loadCart();
                return;
            }

            me.buffer(function () {
                me.loadCart(function () {
                    $('body').one('touchstart', $.proxy(me.onMouseLeave, me));
                });
            }, 500);
        },

        /**
         * Will be called when the mouse leaves the trigger/plugin element.
         *
         * @event onMouseLeave
         * @param {jQuery.Event} event
         */
        onMouseLeave: function (event) {
            var me = this,
                target = event.toElement || event.relatedTarget || event.target;

            $.publish('plugin/collapseCart/onMouseLeave', me);

            if (me.isElementOrChild(me.$el[0], target) || me.isElementOrChild(me._$triggerEl[0], target)) {
                return;
            }

            me.closeMenu();
        },

        /**
         * Will be called when the off canvas close button was clicked/tapped
         *
         * @event onCloseButtonClick
         * @param {jQuery.Event} event
         */
        onCloseButtonClick: function (event) {
            event.preventDefault();

            $.publish('plugin/collapseCart/onCloseButton', this);

            this.closeMenu();
        },

        /**
         * Will be called when the remove item button was clicked.
         *
         * @event onRemoveButtonClick
         * @param {jQuery.Event} event
         */
        onRemoveButtonClick: function (event) {
            event.preventDefault();

            var me = this,
                $currentTarget = $(event.currentTarget),
                $parent = $currentTarget.parent(),
                url = $currentTarget.attr('href');

            $.publish('plugin/collapseCart/onRemoveArticle', me);

            $parent.html(me._$loadingIcon.clone());

            $.ajax({
                'url': url,
                'dataType': 'jsonp',
                'success': function(result) {

                    me.$el.html(result);

                    picturefill();

                    $.publish('plugin/collapseCart/afterRemoveArticle', me);
                }
            });
        },

        /**
         * Sets a timeout and saves its timeout id.
         * When an id already exists, clear the timeout that belongs to that id.
         *
         * @param func
         * @param bufferTime
         */
        buffer: function(func, bufferTime) {
            var me = this;

            if (me.bufferTimeout) {
                clearTimeout(me.bufferTimeout);
            }

            me.bufferTimeout = setTimeout(func, bufferTime);
        },

        /**
         * Returns whether or not the second element is the same as / a child of the first.
         *
         * @param {HTMLElement} firstEl
         * @param {HTMLElement} secondEl
         * @returns {Boolean}
         */
        isElementOrChild: function (firstEl, secondEl) {
            return firstEl === secondEl || $.contains(firstEl, secondEl);
        },

        /**
         * Returns whether or not the current display mode is the given one.
         *
         * @public
         * @method isDisplayMode
         * @param {String} mode
         * @returns {Boolean}
         */
        isDisplayMode: function (mode) {
            return this.opts.displayMode === mode;
        },

        /**
         * Overrides the elements content with the configured loading indicator.
         *
         * @public
         * @method showLoadingIndicator
         */
        showLoadingIndicator: function () {
            var me = this;

            me.$el.html($('<div>', {
                'class': me.opts.loadingIconWrapperClass,
                'html': me._$loadingIcon.clone()
            }));
        },

        /**
         * Opens the offcanvas/collapsible cart.
         * If the offcanvas plugin is active on the element, its openMenu function will also be called.
         *
         * @public
         * @method closeMenu
         */
        openMenu: function () {
            var me = this,
                plugin;

            me._isOpened = true;

            if (me.isDisplayMode('offcanvas') && (plugin = me._$triggerEl.data('plugin_offcanvasMenu'))) {
                plugin.openMenu();
            } else {
                me.$el.addClass(me.opts.activeClass);
            }

            $.publish('plugin/collapseCart/onMenuOpen', me);
        },

        /**
         * Loads the cart content via the configured URL
         * and sets the response as plugin elements content.
         *
         * @public
         * @method loadCart
         * @param {Function} callback
         */
        loadCart: function (callback) {
            var me = this,
                opts = me.opts,
                $el = me.$el;

            $.publish('plugin/collapseCart/onLoadCart', me);

            $.ajax({
                'url': opts.ajaxCartURL,
                'dataType': 'jsonp',
                'success': function (result) {
                    $el.html(result);
                    picturefill();

                    if (typeof callback === 'function') {
                        callback();
                    }

                    $.publish('plugin/collapseCart/afterLoadCart', me);
                }
            });
        },

        /**
         * Closes the offcanvas/collapsible cart.
         * If the offcanvas plugin is active on the element, its closeMenu function will also be called.
         *
         * @public
         * @method closeMenu
         */
        closeMenu: function () {
            var me = this,
                plugin;

            me._isOpened = false;

            if (me.isDisplayMode('offcanvas') && (plugin = me._$triggerEl.data('plugin_offcanvasMenu'))) {
                plugin.closeMenu();
            } else {
                me.$el.removeClass(me.opts.activeClass);
            }

            $.publish('plugin/collapseCart/onCloseMenu', me);
        },

        /**
         * Destroys the plugin and removes all attached events and delegations.
         *
         * @public
         * @method destroy
         */
        destroy: function () {
            var me = this;

            me.off(me.eventSuffix);

            me._destroy();
        }
    });
});
<commit_msg>SW-11293 - Added event-parameter<commit_after>;jQuery(function ($) {
    'use strict';

    $.plugin('collapseCart', {

        defaults: {

            /**
             * URL that will be called when the plugin is fetching the cart.
             *
             * @type {String}
             */
            'ajaxCartURL': $.controller['ajax_cart'],

            /**
             * Selector for the trigger element.
             * The trigger is the element that attaches to the click/tap/hover events.
             *
             * @type {String}
             */
            'triggerElSelector': '.navigation--entry.entry--cart',

            /**
             * Selector for the elements item container.
             *
             * @type {String}
             */
            'itemContainerSelector': '.item--container',

            /**
             * Selector for the remove button of single items.
             *
             * @type {String}
             */
            'removeItemSelector': '.action--remove',

            /**
             * Selector for the offcanvas close button.
             *
             * @type {String}
             */
            'offcanvasCloseElSelector': '.close--off-canvas',

            /**
             * Class for the loading indicator icon.
             *
             * @type {String}
             */
            'loadingIconClass': 'icon--loading-indicator',

            /**
             * Class that will be used for the loading icon wrapper.
             *
             * @type {String}
             */
            'loadingIconWrapperClass': 'ajax--cart',

            /**
             * Class that will be applied to the main plugin element when the menu opens.
             *
             * @type {String}
             */
            'activeClass': 'is--shown',

            /**
             * Mode of displaying the cart.
             * Can either be "collapsible" or "offcanvas".
             *
             * @type {String} displayMode
             */
            'displayMode': 'collapsible'
        },

        /**
         * Initializes the plugin and create all needed elements.
         */
        init: function () {
            var me = this,
                opts;

            // Override options with data attributes.
            me.applyDataAttributes();

            opts = me.opts;

            /**
             * Element that the events get attached to.
             *
             * @private
             * @property _$triggerEl
             * @type {jQuery}
             */
            me._$triggerEl = $(opts.triggerElSelector);

            /**
             * Loading icon that will be used for loading when an AJAX request is send.
             *
             * @private
             * @property _$loadingIcon
             * @type {jQuery}
             */
            me._$loadingIcon = $('<i>', {
                'class': opts.loadingIconClass
            });

            /**
             * Flag whether or not the menu is opened.
             *
             * @private
             * @property _isOpened
             * @type {Boolean}
             */
            me._isOpened = false;

            // if the display mode is "offcanvas", call the offcanvas plugin.
            if (me.isDisplayMode('offcanvas')) {
                me._$triggerEl.offcanvasMenu({
                    'offCanvasSelector': me.$el,
                    'direction': 'fromRight'
                });
            }

            me.registerEvents();
        },

        /**
         * Registers all needed events specific to the display mode.
         *
         * @public
         * @method registerEvents
         */
        registerEvents: function () {
            var me = this;

            me.$el.on(me.getEventName('click'), me.opts.removeItemSelector, $.proxy(me.onRemoveButtonClick, me));
            me.$el.on(me.getEventName('click touchstart'), me.opts.offcanvasCloseElSelector, $.proxy(me.onCloseButtonClick, me));

            if (me.isDisplayMode('offcanvas')) {
                me._on(me._$triggerEl, 'click touchstart', $.proxy(me.onMouseEnter, me));

                $.subscribe('plugin/addArticle/onAddArticle', $.proxy(me.onArticleAdded, me));
                $.subscribe('plugin/addArticle/onBeforeAddArticle', $.proxy(me.onBeforeAddArticle, me));
            } else {
                me._on(me._$triggerEl, 'mouseenter touchstart', $.proxy(me.onMouseEnter, me));
                me._on(me._$triggerEl, 'mouseleave', $.proxy(me.onMouseLeave, me));
                me._on(me.$el, 'mouseleave', $.proxy(me.onMouseLeave, me));
            }
        },

        /**
         * Will be fired from the addArticle plugin before the add-AJAX request will be send.
         * Sets the loading indicator as the content and opens the menu.
         *
         * @event onBeforeAddArticle
         */
        onBeforeAddArticle: function () {
            var me = this;

            me.showLoadingIndicator();
            me.openMenu();
        },

        /**
         * Will be fired from the addArticle plugin before the add-AJAX request is finished.
         * Loads the cart via AJAX and appends it to the basket.
         *
         * @event onArticleAdded
         */
        onArticleAdded: function (event, plugin, response) {
            var me = this;

            if (me.isDisplayMode('collapsible')) {
                return;
            }

            me.$el
                .html(response)
                .find('.ajax--cart .alert')
                .removeClass('is--hidden');

            picturefill();
        },

        /**
         * Will be called when the pointer enters/clicks/taps the trigger element.
         *
         * @event onMouseEnter
         * @param {jQuery.Event} event
         */
        onMouseEnter: function (event) {
            var me = this;

            me.showLoadingIndicator();
            me.openMenu();

            if (me.isDisplayMode('offcanvas')) {
                event.preventDefault();

                me.loadCart();
                return;
            }

            me.buffer(function () {
                me.loadCart(function () {
                    $('body').one('touchstart', $.proxy(me.onMouseLeave, me));
                });
            }, 500);
        },

        /**
         * Will be called when the mouse leaves the trigger/plugin element.
         *
         * @event onMouseLeave
         * @param {jQuery.Event} event
         */
        onMouseLeave: function (event) {
            var me = this,
                target = event.toElement || event.relatedTarget || event.target;

            $.publish('plugin/collapseCart/onMouseLeave', me);

            if (me.isElementOrChild(me.$el[0], target) || me.isElementOrChild(me._$triggerEl[0], target)) {
                return;
            }

            me.closeMenu();
        },

        /**
         * Will be called when the off canvas close button was clicked/tapped
         *
         * @event onCloseButtonClick
         * @param {jQuery.Event} event
         */
        onCloseButtonClick: function (event) {
            event.preventDefault();

            $.publish('plugin/collapseCart/onCloseButton', this);

            this.closeMenu();
        },

        /**
         * Will be called when the remove item button was clicked.
         *
         * @event onRemoveButtonClick
         * @param {jQuery.Event} event
         */
        onRemoveButtonClick: function (event) {
            event.preventDefault();

            var me = this,
                $currentTarget = $(event.currentTarget),
                $parent = $currentTarget.parent(),
                url = $currentTarget.attr('href');

            $.publish('plugin/collapseCart/onRemoveArticle', [me, event]);
            $parent.html(me._$loadingIcon.clone());

            $.ajax({
                'url': url,
                'dataType': 'jsonp',
                'success': function(result) {

                    me.$el.html(result);

                    picturefill();

                    $.publish('plugin/collapseCart/afterRemoveArticle', [me, event]);
                }
            });
        },

        /**
         * Sets a timeout and saves its timeout id.
         * When an id already exists, clear the timeout that belongs to that id.
         *
         * @param func
         * @param bufferTime
         */
        buffer: function(func, bufferTime) {
            var me = this;

            if (me.bufferTimeout) {
                clearTimeout(me.bufferTimeout);
            }

            me.bufferTimeout = setTimeout(func, bufferTime);
        },

        /**
         * Returns whether or not the second element is the same as / a child of the first.
         *
         * @param {HTMLElement} firstEl
         * @param {HTMLElement} secondEl
         * @returns {Boolean}
         */
        isElementOrChild: function (firstEl, secondEl) {
            return firstEl === secondEl || $.contains(firstEl, secondEl);
        },

        /**
         * Returns whether or not the current display mode is the given one.
         *
         * @public
         * @method isDisplayMode
         * @param {String} mode
         * @returns {Boolean}
         */
        isDisplayMode: function (mode) {
            return this.opts.displayMode === mode;
        },

        /**
         * Overrides the elements content with the configured loading indicator.
         *
         * @public
         * @method showLoadingIndicator
         */
        showLoadingIndicator: function () {
            var me = this;

            me.$el.html($('<div>', {
                'class': me.opts.loadingIconWrapperClass,
                'html': me._$loadingIcon.clone()
            }));
        },

        /**
         * Opens the offcanvas/collapsible cart.
         * If the offcanvas plugin is active on the element, its openMenu function will also be called.
         *
         * @public
         * @method closeMenu
         */
        openMenu: function () {
            var me = this,
                plugin;

            me._isOpened = true;

            if (me.isDisplayMode('offcanvas') && (plugin = me._$triggerEl.data('plugin_offcanvasMenu'))) {
                plugin.openMenu();
            } else {
                me.$el.addClass(me.opts.activeClass);
            }

            $.publish('plugin/collapseCart/onMenuOpen', me);
        },

        /**
         * Loads the cart content via the configured URL
         * and sets the response as plugin elements content.
         *
         * @public
         * @method loadCart
         * @param {Function} callback
         */
        loadCart: function (callback) {
            var me = this,
                opts = me.opts,
                $el = me.$el;

            $.publish('plugin/collapseCart/onLoadCart', me);

            $.ajax({
                'url': opts.ajaxCartURL,
                'dataType': 'jsonp',
                'success': function (result) {
                    $el.html(result);
                    picturefill();

                    if (typeof callback === 'function') {
                        callback();
                    }

                    $.publish('plugin/collapseCart/afterLoadCart', me);
                }
            });
        },

        /**
         * Closes the offcanvas/collapsible cart.
         * If the offcanvas plugin is active on the element, its closeMenu function will also be called.
         *
         * @public
         * @method closeMenu
         */
        closeMenu: function () {
            var me = this,
                plugin;

            me._isOpened = false;

            if (me.isDisplayMode('offcanvas') && (plugin = me._$triggerEl.data('plugin_offcanvasMenu'))) {
                plugin.closeMenu();
            } else {
                me.$el.removeClass(me.opts.activeClass);
            }

            $.publish('plugin/collapseCart/onCloseMenu', me);
        },

        /**
         * Destroys the plugin and removes all attached events and delegations.
         *
         * @public
         * @method destroy
         */
        destroy: function () {
            var me = this;

            me.off(me.eventSuffix);

            me._destroy();
        }
    });
});
<|endoftext|>


<commit_before>var common = {};

common.INPUT_TYPES = new Set(["INPUT", "TEXTAREA"]);

common._request =
function _request(method, url, callback)
{
    let request = new XMLHttpRequest();
    let response = {
        "completed": false,
    };

    request.onreadystatechange = function()
    {
        if (request.readyState != 4)
            {return;}

        if (callback == null)
            {return;}

        if (request.status != 0)
        {
            response.completed = true;
            response.data = JSON.parse(request.responseText);
        }
        response.meta = {
            "request_url": url,
            "status": request.status
        }
        callback(response);
    };
    let asynchronous = true;
    request.open(method, url, asynchronous);
    return request;
}

common.get =
function get(url, callback)
{
    request = common._request("GET", url, callback);
    request.send();
}

common.post =
function post(url, data, callback)
{
    request = common._request("POST", url, callback);
    request.send(data);
}

common.bind_box_to_button =
function bind_box_to_button(box, button, ctrl_enter)
{
    /*
    Bind a textbox to a button so that pressing Enter within the textbox is the
    same as clicking the button.

    If `ctrl_enter` is true, then you must press ctrl+Enter to trigger the
    button, which is important for textareas.

    Thanks Yaroslav Yakovlev
    http://stackoverflow.com/a/9343095
    */
    let bound_box_hook = function(event)
    {
        if (event.key !== "Enter")
            {return;}

        ctrl_success = !ctrl_enter || (event.ctrlKey);

        if (! ctrl_success)
            {return;}

        button.click();
    }
    box.addEventListener("keyup", bound_box_hook);
}

common.create_message_bubble =
function create_message_bubble(message_area, message_positivity, message_text, lifespan)
{
    if (lifespan === undefined)
    {
        lifespan = 8000;
    }
    let message = document.createElement("div");
    message.className = "message_bubble " + message_positivity;
    let span = document.createElement("span");
    span.innerHTML = message_text;
    message.appendChild(span);
    message_area.appendChild(message);
    setTimeout(function(){message_area.removeChild(message);}, lifespan);
}

common.delete_all_children =
function delete_all_children(element)
{
    while (element.firstChild)
    {
        element.removeChild(element.firstChild);
    }
}

common.entry_with_history_hook =
function entry_with_history_hook(event)
{
    //console.log(event);
    let box = event.target;

    if (box.entry_history === undefined)
        {box.entry_history = [];}

    if (box.entry_history_pos === undefined)
        {box.entry_history_pos = -1;}

    if (event.key === "Enter")
    {
        box.entry_history.push(box.value);
    }
    else if (event.key === "ArrowUp")
    {
        if (box.entry_history.length == 0)
            {return}

        if (box.entry_history_pos == -1)
            {box.entry_history_pos = box.entry_history.length - 1;}
        else if (box.entry_history_pos > 0)
            {box.entry_history_pos -= 1;}

        box.value = box.entry_history[box.entry_history_pos];
        setTimeout(function(){box.selectionStart = box.value.length;}, 0);
    }
    else if (event.key === "Escape")
    {
        box.value = "";
    }
    else
    {
        box.entry_history_pos = -1;
    }
}

common.html_to_element =
function html_to_element(html)
{
    let template = document.createElement("template");
    template.innerHTML = html;
    return template.content.firstChild;
}

common.init_atag_merge_params =
function init_atag_merge_params()
{
    /*
    To create an <a> tag where the ?parameters written on the href are merged
    with the parameters of the current page URL, give it the class
    "merge_params". If the URL and href contain the same parameter, the href
    takes priority.

    Example:
        URL: ?filter=hello&orderby=score
        href: "?orderby=date"
        Result: "?filter=hello&orderby=date"
    */
    page_params = new URLSearchParams(window.location.search);
    let as = Array.from(document.getElementsByClassName("merge_params"));
    for (const a of as)
    {
        let a_params = new URLSearchParams(a.search);
        let new_params = new URLSearchParams();
        page_params.forEach(function(value, key) {new_params.set(key, value); });
        a_params.forEach(function(value, key) {new_params.set(key, value); });
        a.search = new_params.toString();
        a.classList.remove("merge_params");
    }
}

common.init_button_with_confirm =
function init_button_with_confirm()
{
    /*
    To create a button that requires a second confirmation step, assign it the
    class "button_with_confirm".

    Required:
        data-onclick: String that would normally be the button's onclick.
            This is done so that if the button_with_confirm fails to initialize,
            the button will be non-operational as opposed to being operational
            but with no confirmation. For dangerous actions I think this is a
            worthwhile move though it could lead to feature downtime.

    Optional:
        data-prompt: Text that appears next to the confirm button. Default is
            "Are you sure?".
        data-prompt-class

        data-confirm: Text inside the confirm button. Default is to inherit the
            original button's text.
        data-confirm-class: CSS class for the confirm button. Default is to
            inheret all classes of the original button, except for
            "button_with_confirm" of course.

        data-cancel: Text inside the cancel button. Default is "Cancel".
        data-cancel-class

        data-holder-class: CSS class for the new span that holds the menu.
    */
    let buttons = Array.from(document.getElementsByClassName("button_with_confirm"));
    for (const button of buttons)
    {
        button.classList.remove("button_with_confirm");

        let holder = document.createElement("span");
        holder.classList.add("confirm_holder");
        holder.classList.add(button.dataset.holderClass || "confirm_holder");
        button.parentElement.insertBefore(holder, button);
        button.parentElement.removeChild(button);

        let holder_stage1 = document.createElement("span");
        holder_stage1.classList.add("confirm_holder_stage1");
        holder_stage1.appendChild(button);
        holder.appendChild(holder_stage1);

        let holder_stage2 = document.createElement("span");
        holder_stage2.classList.add("confirm_holder_stage2");
        holder_stage2.classList.add("hidden");
        holder.appendChild(holder_stage2);

        let prompt;
        let input_source;
        if (button.dataset.isInput)
        {
            prompt = document.createElement("input");
            prompt.placeholder = button.dataset.prompt || "";
            input_source = prompt;
        }
        else
        {
            prompt = document.createElement("span");
            prompt.innerText = (button.dataset.prompt || "Are you sure?") + " ";
            input_source = undefined;
        }
        prompt.className = button.dataset.promptClass || "";
        holder_stage2.appendChild(prompt)
        delete button.dataset.prompt;
        delete button.dataset.promptClass;

        let button_confirm = document.createElement("button");
        button_confirm.innerText = (button.dataset.confirm || button.innerText).trim();
        if (button.dataset.confirmClass === undefined)
        {
            button_confirm.className = button.className;
            button_confirm.classList.remove("button_with_confirm");
        }
        else
        {
            button_confirm.className = button.dataset.confirmClass;
        }
        button_confirm.input_source = input_source;
        holder_stage2.appendChild(button_confirm);
        holder_stage2.appendChild(document.createTextNode(" "));
        if (button.dataset.isInput)
        {
            common.bind_box_to_button(prompt, button_confirm);
        }
        delete button.dataset.confirm;
        delete button.dataset.confirmClass;
        delete button.dataset.isInput;

        let button_cancel = document.createElement("button");
        button_cancel.innerText = button.dataset.cancel || "Cancel";
        button_cancel.className = button.dataset.cancelClass || "";
        holder_stage2.appendChild(button_cancel);
        delete button.dataset.cancel;
        delete button.dataset.cancelClass;

        // If this is stupid, let me know.
        let confirm_onclick = button.dataset.onclick + `
            ;
            let holder = event.target.parentElement.parentElement;
            holder.getElementsByClassName("confirm_holder_stage1")[0].classList.remove("hidden");
            holder.getElementsByClassName("confirm_holder_stage2")[0].classList.add("hidden");
        `
        button_confirm.onclick = Function(confirm_onclick);
        button.removeAttribute("onclick");
        button.onclick = function(event)
        {
            let holder = event.target.parentElement.parentElement;
            holder.getElementsByClassName("confirm_holder_stage1")[0].classList.add("hidden");
            holder.getElementsByClassName("confirm_holder_stage2")[0].classList.remove("hidden");
            let input = holder.getElementsByTagName("input")[0];
            if (input)
            {
                input.focus();
            }
        }

        button_cancel.onclick = function(event)
        {
            let holder = event.target.parentElement.parentElement;
            holder.getElementsByClassName("confirm_holder_stage1")[0].classList.remove("hidden");
            holder.getElementsByClassName("confirm_holder_stage2")[0].classList.add("hidden");
        }
        delete button.dataset.onclick;
    }
}

common.init_enable_on_pageload =
function init_enable_on_pageload()
{
    /*
    To create an input element which is disabled at first, and is enabled when
    the DOM has completed loading, give it the disabled attribute and the
    class "enable_on_pageload".
    */
    let elements = Array.from(document.getElementsByClassName("enable_on_pageload"));
    for (const element of elements)
    {
        element.disabled = false;
        element.classList.remove("enable_on_pageload");
    }
}

common.init_entry_with_history =
function init_entry_with_history()
{
    const inputs = Array.from(document.getElementsByClassName("entry_with_history"));
    for (const input of inputs)
    {
        input.addEventListener("keyup", common.entry_with_history_hook);
        input.classList.remove("entry_with_history");
    }
}

common.init_tabbed_container =
function init_tabbed_container()
{
    let switch_tab =
    function switch_tab(event)
    {
        let tab_button = event.target;
        if (tab_button.classList.contains("tab_button_active"))
            { return; }

        let tab_id = tab_button.dataset.tabId;
        let tab_buttons = tab_button.parentElement.getElementsByClassName("tab_button");
        let tabs = tab_button.parentElement.parentElement.getElementsByClassName("tab");
        for (const tab_button of tab_buttons)
        {
            if (tab_button.dataset.tabId === tab_id)
            {
                tab_button.classList.remove("tab_button_inactive");
                tab_button.classList.add("tab_button_active");
            }
            else
            {
                tab_button.classList.remove("tab_button_active");
                tab_button.classList.add("tab_button_inactive");
            }
        }
        for (const tab of tabs)
        {
            if (tab.dataset.tabId === tab_id)
                { tab.classList.remove("hidden"); }
            else
                { tab.classList.add("hidden"); }
        }
    }

    let tabbed_containers = Array.from(document.getElementsByClassName("tabbed_container"));
    for (const tabbed_container of tabbed_containers)
    {
        let button_container = document.createElement("div");
        button_container.className = "tab_buttons";
        tabbed_container.prepend(button_container);
        let tabs = Array.from(tabbed_container.getElementsByClassName("tab"));
        for (const tab of tabs)
        {
            tab.classList.add("hidden");
            let tab_id = tab.dataset.tabId || tab.dataset.tabTitle;
            tab.dataset.tabId = tab_id;
            tab.style.borderTopColor = "transparent";

            let button = document.createElement("button");
            button.className = "tab_button tab_button_inactive";
            button.onclick = switch_tab;
            button.innerText = tab.dataset.tabTitle;
            button.dataset.tabId = tab_id;
            button_container.append(button);
        }
        tabs[0].classList.remove("hidden");
        button_container.firstElementChild.classList.remove("tab_button_inactive");
        button_container.firstElementChild.classList.add("tab_button_active");
    }
}

common.refresh =
function refresh()
{
    window.location.reload();
}

common.on_pageload =
function on_pageload()
{
    common.init_atag_merge_params();
    common.init_button_with_confirm();
    common.init_enable_on_pageload();
    common.init_entry_with_history();
    common.init_tabbed_container();
}
document.addEventListener("DOMContentLoaded", common.on_pageload);
<commit_msg>Add explanation of data-prompt-class et al.<commit_after>var common = {};

common.INPUT_TYPES = new Set(["INPUT", "TEXTAREA"]);

common._request =
function _request(method, url, callback)
{
    let request = new XMLHttpRequest();
    let response = {
        "completed": false,
    };

    request.onreadystatechange = function()
    {
        if (request.readyState != 4)
            {return;}

        if (callback == null)
            {return;}

        if (request.status != 0)
        {
            response.completed = true;
            response.data = JSON.parse(request.responseText);
        }
        response.meta = {
            "request_url": url,
            "status": request.status
        }
        callback(response);
    };
    let asynchronous = true;
    request.open(method, url, asynchronous);
    return request;
}

common.get =
function get(url, callback)
{
    request = common._request("GET", url, callback);
    request.send();
}

common.post =
function post(url, data, callback)
{
    request = common._request("POST", url, callback);
    request.send(data);
}

common.bind_box_to_button =
function bind_box_to_button(box, button, ctrl_enter)
{
    /*
    Bind a textbox to a button so that pressing Enter within the textbox is the
    same as clicking the button.

    If `ctrl_enter` is true, then you must press ctrl+Enter to trigger the
    button, which is important for textareas.

    Thanks Yaroslav Yakovlev
    http://stackoverflow.com/a/9343095
    */
    let bound_box_hook = function(event)
    {
        if (event.key !== "Enter")
            {return;}

        ctrl_success = !ctrl_enter || (event.ctrlKey);

        if (! ctrl_success)
            {return;}

        button.click();
    }
    box.addEventListener("keyup", bound_box_hook);
}

common.create_message_bubble =
function create_message_bubble(message_area, message_positivity, message_text, lifespan)
{
    if (lifespan === undefined)
    {
        lifespan = 8000;
    }
    let message = document.createElement("div");
    message.className = "message_bubble " + message_positivity;
    let span = document.createElement("span");
    span.innerHTML = message_text;
    message.appendChild(span);
    message_area.appendChild(message);
    setTimeout(function(){message_area.removeChild(message);}, lifespan);
}

common.delete_all_children =
function delete_all_children(element)
{
    while (element.firstChild)
    {
        element.removeChild(element.firstChild);
    }
}

common.entry_with_history_hook =
function entry_with_history_hook(event)
{
    //console.log(event);
    let box = event.target;

    if (box.entry_history === undefined)
        {box.entry_history = [];}

    if (box.entry_history_pos === undefined)
        {box.entry_history_pos = -1;}

    if (event.key === "Enter")
    {
        box.entry_history.push(box.value);
    }
    else if (event.key === "ArrowUp")
    {
        if (box.entry_history.length == 0)
            {return}

        if (box.entry_history_pos == -1)
            {box.entry_history_pos = box.entry_history.length - 1;}
        else if (box.entry_history_pos > 0)
            {box.entry_history_pos -= 1;}

        box.value = box.entry_history[box.entry_history_pos];
        setTimeout(function(){box.selectionStart = box.value.length;}, 0);
    }
    else if (event.key === "Escape")
    {
        box.value = "";
    }
    else
    {
        box.entry_history_pos = -1;
    }
}

common.html_to_element =
function html_to_element(html)
{
    let template = document.createElement("template");
    template.innerHTML = html;
    return template.content.firstChild;
}

common.init_atag_merge_params =
function init_atag_merge_params()
{
    /*
    To create an <a> tag where the ?parameters written on the href are merged
    with the parameters of the current page URL, give it the class
    "merge_params". If the URL and href contain the same parameter, the href
    takes priority.

    Example:
        URL: ?filter=hello&orderby=score
        href: "?orderby=date"
        Result: "?filter=hello&orderby=date"
    */
    page_params = new URLSearchParams(window.location.search);
    let as = Array.from(document.getElementsByClassName("merge_params"));
    for (const a of as)
    {
        let a_params = new URLSearchParams(a.search);
        let new_params = new URLSearchParams();
        page_params.forEach(function(value, key) {new_params.set(key, value); });
        a_params.forEach(function(value, key) {new_params.set(key, value); });
        a.search = new_params.toString();
        a.classList.remove("merge_params");
    }
}

common.init_button_with_confirm =
function init_button_with_confirm()
{
    /*
    To create a button that requires a second confirmation step, assign it the
    class "button_with_confirm".

    Required:
        data-onclick: String that would normally be the button's onclick.
            This is done so that if the button_with_confirm fails to initialize,
            the button will be non-operational as opposed to being operational
            but with no confirmation. For dangerous actions I think this is a
            worthwhile move though it could lead to feature downtime.

    Optional:
        data-prompt: Text that appears next to the confirm button. Default is
            "Are you sure?".

        data-prompt-class: CSS class for the prompt span.

        data-confirm: Text inside the confirm button. Default is to inherit the
            original button's text.

        data-confirm-class: CSS class for the confirm button. Default is to
            inheret all classes of the original button, except for
            "button_with_confirm" of course.

        data-cancel: Text inside the cancel button. Default is "Cancel".

        data-cancel-class: CSS class for the cancel button.

        data-holder-class: CSS class for the new span that holds the menu.
    */
    let buttons = Array.from(document.getElementsByClassName("button_with_confirm"));
    for (const button of buttons)
    {
        button.classList.remove("button_with_confirm");

        let holder = document.createElement("span");
        holder.classList.add("confirm_holder");
        holder.classList.add(button.dataset.holderClass || "confirm_holder");
        button.parentElement.insertBefore(holder, button);
        button.parentElement.removeChild(button);

        let holder_stage1 = document.createElement("span");
        holder_stage1.classList.add("confirm_holder_stage1");
        holder_stage1.appendChild(button);
        holder.appendChild(holder_stage1);

        let holder_stage2 = document.createElement("span");
        holder_stage2.classList.add("confirm_holder_stage2");
        holder_stage2.classList.add("hidden");
        holder.appendChild(holder_stage2);

        let prompt;
        let input_source;
        if (button.dataset.isInput)
        {
            prompt = document.createElement("input");
            prompt.placeholder = button.dataset.prompt || "";
            input_source = prompt;
        }
        else
        {
            prompt = document.createElement("span");
            prompt.innerText = (button.dataset.prompt || "Are you sure?") + " ";
            input_source = undefined;
        }
        prompt.className = button.dataset.promptClass || "";
        holder_stage2.appendChild(prompt)
        delete button.dataset.prompt;
        delete button.dataset.promptClass;

        let button_confirm = document.createElement("button");
        button_confirm.innerText = (button.dataset.confirm || button.innerText).trim();
        if (button.dataset.confirmClass === undefined)
        {
            button_confirm.className = button.className;
            button_confirm.classList.remove("button_with_confirm");
        }
        else
        {
            button_confirm.className = button.dataset.confirmClass;
        }
        button_confirm.input_source = input_source;
        holder_stage2.appendChild(button_confirm);
        holder_stage2.appendChild(document.createTextNode(" "));
        if (button.dataset.isInput)
        {
            common.bind_box_to_button(prompt, button_confirm);
        }
        delete button.dataset.confirm;
        delete button.dataset.confirmClass;
        delete button.dataset.isInput;

        let button_cancel = document.createElement("button");
        button_cancel.innerText = button.dataset.cancel || "Cancel";
        button_cancel.className = button.dataset.cancelClass || "";
        holder_stage2.appendChild(button_cancel);
        delete button.dataset.cancel;
        delete button.dataset.cancelClass;

        // If this is stupid, let me know.
        let confirm_onclick = button.dataset.onclick + `
            ;
            let holder = event.target.parentElement.parentElement;
            holder.getElementsByClassName("confirm_holder_stage1")[0].classList.remove("hidden");
            holder.getElementsByClassName("confirm_holder_stage2")[0].classList.add("hidden");
        `
        button_confirm.onclick = Function(confirm_onclick);
        button.removeAttribute("onclick");
        button.onclick = function(event)
        {
            let holder = event.target.parentElement.parentElement;
            holder.getElementsByClassName("confirm_holder_stage1")[0].classList.add("hidden");
            holder.getElementsByClassName("confirm_holder_stage2")[0].classList.remove("hidden");
            let input = holder.getElementsByTagName("input")[0];
            if (input)
            {
                input.focus();
            }
        }

        button_cancel.onclick = function(event)
        {
            let holder = event.target.parentElement.parentElement;
            holder.getElementsByClassName("confirm_holder_stage1")[0].classList.remove("hidden");
            holder.getElementsByClassName("confirm_holder_stage2")[0].classList.add("hidden");
        }
        delete button.dataset.onclick;
    }
}

common.init_enable_on_pageload =
function init_enable_on_pageload()
{
    /*
    To create an input element which is disabled at first, and is enabled when
    the DOM has completed loading, give it the disabled attribute and the
    class "enable_on_pageload".
    */
    let elements = Array.from(document.getElementsByClassName("enable_on_pageload"));
    for (const element of elements)
    {
        element.disabled = false;
        element.classList.remove("enable_on_pageload");
    }
}

common.init_entry_with_history =
function init_entry_with_history()
{
    const inputs = Array.from(document.getElementsByClassName("entry_with_history"));
    for (const input of inputs)
    {
        input.addEventListener("keyup", common.entry_with_history_hook);
        input.classList.remove("entry_with_history");
    }
}

common.init_tabbed_container =
function init_tabbed_container()
{
    let switch_tab =
    function switch_tab(event)
    {
        let tab_button = event.target;
        if (tab_button.classList.contains("tab_button_active"))
            { return; }

        let tab_id = tab_button.dataset.tabId;
        let tab_buttons = tab_button.parentElement.getElementsByClassName("tab_button");
        let tabs = tab_button.parentElement.parentElement.getElementsByClassName("tab");
        for (const tab_button of tab_buttons)
        {
            if (tab_button.dataset.tabId === tab_id)
            {
                tab_button.classList.remove("tab_button_inactive");
                tab_button.classList.add("tab_button_active");
            }
            else
            {
                tab_button.classList.remove("tab_button_active");
                tab_button.classList.add("tab_button_inactive");
            }
        }
        for (const tab of tabs)
        {
            if (tab.dataset.tabId === tab_id)
                { tab.classList.remove("hidden"); }
            else
                { tab.classList.add("hidden"); }
        }
    }

    let tabbed_containers = Array.from(document.getElementsByClassName("tabbed_container"));
    for (const tabbed_container of tabbed_containers)
    {
        let button_container = document.createElement("div");
        button_container.className = "tab_buttons";
        tabbed_container.prepend(button_container);
        let tabs = Array.from(tabbed_container.getElementsByClassName("tab"));
        for (const tab of tabs)
        {
            tab.classList.add("hidden");
            let tab_id = tab.dataset.tabId || tab.dataset.tabTitle;
            tab.dataset.tabId = tab_id;
            tab.style.borderTopColor = "transparent";

            let button = document.createElement("button");
            button.className = "tab_button tab_button_inactive";
            button.onclick = switch_tab;
            button.innerText = tab.dataset.tabTitle;
            button.dataset.tabId = tab_id;
            button_container.append(button);
        }
        tabs[0].classList.remove("hidden");
        button_container.firstElementChild.classList.remove("tab_button_inactive");
        button_container.firstElementChild.classList.add("tab_button_active");
    }
}

common.refresh =
function refresh()
{
    window.location.reload();
}

common.on_pageload =
function on_pageload()
{
    common.init_atag_merge_params();
    common.init_button_with_confirm();
    common.init_enable_on_pageload();
    common.init_entry_with_history();
    common.init_tabbed_container();
}
document.addEventListener("DOMContentLoaded", common.on_pageload);
<|endoftext|>


<commit_before>'use strict';

var React = require('react/addons');
var TreeView = require('react-treeview');
var _ = require('underscore');
var dataSource = [
    {
        type: 'Employees',
        collapsed: false,
        people: [
            {name: 'Paul Gordon', age: 25, sex: 'male', role: 'coder', collapsed: false},
            {name: 'Sarah Lee', age: 23, sex: 'female', role: 'jqueryer', collapsed: false}
        ]
    },
    {
        type: 'CEO',
        collapsed: false,
        people: [
            {name: 'Drew Anderson', age: 35, sex: 'male', role: 'boss', collapsed: false}
        ]
    }
];
require('styles/GeoList.less');

function getCategoriesState (categories, geoObjects) {
    var categoriesWithObjects = [];
    _.each(categories, function (value) {
        categoriesWithObjects.push({
            name: value,
            collapsed: false,
            geoObjects: _.where(geoObjects, {category:value})
        });
    });
    return {
        categoriesWithObjects: categoriesWithObjects
    }
}

var GeoList = React.createClass({
    getInitialState: function() {
        return getCategoriesState(this.props.categories, this.props.geoObjects);
    },
    render: function () {
        return (

                        <input type="radio" id="tab-categories" name="tab-group-1" defaultChecked/>
                        Категории

                            {this.state.categoriesWithObjects.map(function(node, index) {
                                var type = node.name;
                                var label = {type};
                                return (
                                    <TreeView key={type + '|' + index} nodeLabel={label} defaultCollapsed={false}>
                                        {node.geoObjects.map(function(geoObject) {
                                            return (
                                                {geoObject.name}
                                            );
                                        })}
                                    </TreeView>
                                );
                            }, this)}

                        <input type="radio" id="tab-search" name="tab-group-1"/>
                        Поиск

                            stuff

        );
    }
});

module.exports = GeoList;

<commit_msg>remove unused data<commit_after>'use strict';

var React = require('react/addons');
var TreeView = require('react-treeview');
var _ = require('underscore');

require('styles/GeoList.less');

function getCategoriesState (categories, geoObjects) {
    var categoriesWithObjects = [];
    _.each(categories, function (value) {
        categoriesWithObjects.push({
            name: value,
            collapsed: false,
            geoObjects: _.where(geoObjects, {category:value})
        });
    });
    return {
        categoriesWithObjects: categoriesWithObjects
    }
}

var GeoList = React.createClass({
    getInitialState: function() {
        return getCategoriesState(this.props.categories, this.props.geoObjects);
    },
    render: function () {
        return (

                        <input type="radio" id="tab-categories" name="tab-group-1" defaultChecked/>
                        Категории

                            {this.state.categoriesWithObjects.map(function(node, index) {
                                var type = node.name;
                                var label = {type};
                                return (
                                    <TreeView key={type + '|' + index} nodeLabel={label} defaultCollapsed={false}>
                                        {node.geoObjects.map(function(geoObject) {
                                            return (
                                                {geoObject.name}
                                            );
                                        })}
                                    </TreeView>
                                );
                            }, this)}

                        <input type="radio" id="tab-search" name="tab-group-1"/>
                        Поиск

                            stuff

        );
    }
});

module.exports = GeoList;

<|endoftext|>


<commit_before>
/* Contains functions to place props into levels. */
const RG = require('./rg');
const Random = require('./random');

const RNG = Random.getRNG();

const Placer = {};

Placer.addPropsToFreeCells = function(level, props, type) {
    const freeCells = level.getMap().getFree();
    Placer.addPropsToCells(level, freeCells, props, type);
};

/* Adds to the given level, and its cells, all props given in the list. Assumes
 * that all props are of given type (placement function is different for
 * different types. */
Placer.addPropsToCells = function(level, cells, props, type) {
    for (let i = 0; i < props.length; i++) {
        if (cells.length > 0) {
            const index = RNG.randIndex(cells);
            const cell = cells[index];
            if (type === RG.TYPE_ACTOR) {
                level.addActor(props[i], cell.getX(), cell.getY());
            }
            else if (type === RG.TYPE_ITEM) {
                level.addItem(props[i], cell.getX(), cell.getY());
            }
            else {
                RG.err('Placer', 'addPropsToCells',
                    `Type ${type} not supported`);
            }
            cells.splice(index, 1); // remove used cell
        }
    }
};

Placer.addActorsToBbox = function(level, bbox, actors) {
    const nActors = actors.length;
    const freeCells = level.getMap().getFreeInBbox(bbox);
    if (freeCells.length < nActors) {
        RG.warn('Factory', 'addActorsToBbox',
            'Not enough free cells');
    }
    Placer.addPropsToCells(level, freeCells, actors, RG.TYPE_ACTOR);
};

Placer.addItemsToBbox = function(level, bbox, items) {
    const freeCells = level.getMap().getFreeInBbox(bbox);
    Placer.addPropsToCells(level, freeCells, items, RG.TYPE_ITEM);
};

/* Adds entity to a random cell of matching filterFunc. Returns true if success,
 * otherwise returns false (for example if no cells found). */
Placer.addEntityToCellType = function(entity, level, filterFunc) {
    let ok = false;
    const cells = level.getMap().getCells(filterFunc);
    if (cells.length === 0) {return false;}
    const randCell = RNG.arrayGetRand(cells);
    const [x, y] = randCell.getXY();
    if (RG.isActor(entity)) {
        ok = level.addActor(entity, x, y);
    }
    else if (RG.isItem(entity)) {
        ok = level.addItem(entity, x, y);
    }
    else if (RG.isElement(entity)) {
        ok = level.addElement(entity, x, y);
    }
    return ok;
};

module.exports = Placer;
<commit_msg>Added addPropsToRoom().<commit_after>
/* Contains functions to place props into levels. */
const RG = require('./rg');
const Random = require('./random');

const RNG = Random.getRNG();

const Placer = {};

Placer.addPropsToFreeCells = function(level, props, type) {
    const freeCells = level.getMap().getFree();
    Placer.addPropsToCells(level, freeCells, props, type);
};

/* Adds to the given level, and its cells, all props given in the list. Assumes
 * that all props are of given type (placement function is different for
 * different types. */
Placer.addPropsToCells = function(level, cells, props, type) {
    for (let i = 0; i < props.length; i++) {
        if (cells.length > 0) {
            const index = RNG.randIndex(cells);
            const cell = cells[index];
            if (type === RG.TYPE_ACTOR) {
                level.addActor(props[i], cell.getX(), cell.getY());
            }
            else if (type === RG.TYPE_ITEM) {
                level.addItem(props[i], cell.getX(), cell.getY());
            }
            else {
                RG.err('Placer', 'addPropsToCells',
                    `Type ${type} not supported`);
            }
            cells.splice(index, 1); // remove used cell
        }
    }
};

Placer.addPropsToRoom = function(level, room, props) {
    if (!Array.isArray(props)) {
        RG.err('Placer', 'addPropsToRoom',
            `props must be an array. Got: ${props}`);
    }
    const bbox = room.getBbox();
    const prop = props[0];
    if (RG.isActor(prop)) {
        Placer.addActorsToBbox(level, bbox, props);
    }
    else if (RG.isItem(prop)) {
        Placer.addItemsToBbox(level, bbox, props);
    }
    else {
        RG.err('Placer', 'addPropsToRoom',
            `Prop type not supported: ${prop}`);
    }
};

Placer.addActorsToBbox = function(level, bbox, actors) {
    const nActors = actors.length;
    const freeCells = level.getMap().getFreeInBbox(bbox);
    if (freeCells.length < nActors) {
        RG.warn('Factory', 'addActorsToBbox',
            'Not enough free cells');
    }
    Placer.addPropsToCells(level, freeCells, actors, RG.TYPE_ACTOR);
};

Placer.addItemsToBbox = function(level, bbox, items) {
    const freeCells = level.getMap().getFreeInBbox(bbox);
    Placer.addPropsToCells(level, freeCells, items, RG.TYPE_ITEM);
};

/* Adds entity to a random cell of matching filterFunc. Returns true if success,
 * otherwise returns false (for example if no cells found). */
Placer.addEntityToCellType = function(entity, level, filterFunc) {
    let ok = false;
    const cells = level.getMap().getCells(filterFunc);
    if (cells.length === 0) {return false;}
    const randCell = RNG.arrayGetRand(cells);
    const [x, y] = randCell.getXY();
    if (RG.isActor(entity)) {
        ok = level.addActor(entity, x, y);
    }
    else if (RG.isItem(entity)) {
        ok = level.addItem(entity, x, y);
    }
    else if (RG.isElement(entity)) {
        ok = level.addElement(entity, x, y);
    }
    return ok;
};

module.exports = Placer;
<|endoftext|>


<commit_before>import React from 'react';
import styled from 'styled-components';

const Wrapper = styled.div`
  border-bottom: 1px solid rgba(0, 0, 0, 0.2);
  &:last-child {
    border: none;
  }
`;

const Agency = styled.h5`
  font-weight: normal;
  margin: 1.6875rem 0;
`;

export default ({
  agency,
  description,
  images,
  link,
  title
}) => (
  <Wrapper>

{title}

    {agency &&
      <Agency>
        Agency: {agency}
      </Agency>
    }

{description}

    {link &&
      visit site
    }
    {images && images.map((image, index) => (

    ))}
  </Wrapper>
);
<commit_msg>move styled-components types to within main Project function so it can leverage props<commit_after>import React from 'react';
import styled from 'styled-components';

export default ({
  agency,
  alignImageRight,
  description,
  images,
  link,
  title
}) => {
  const Wrapper = styled.div`
    border-bottom: 1px solid rgba(0, 0, 0, 0.2);
    &:last-child {
      border: none;
    }
  `;

  const Agency = styled.h5`
    font-weight: normal;
    margin: 1.6875rem 0;
  `;

  return (
    <Wrapper>

{title}

      {agency &&
        <Agency>
          Agency: {agency}
        </Agency>
      }

{description}

      {link &&
        visit site
      }
      {images && images.map((image, index) => (

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