<commit_before>import GameEvent from '../events/GameEvent.js';
import GameEvents from '../events/GameEvents.js';

import GridAnimations from './GridAnimations.js';
import DungeonTooltips from './DungeonTooltips.js';

//var ANIMATE_BARS = false;

function updateRangeIndicator(grid, dungeon, attack) {
    $('.range-indicator').remove();
    if(!attack) {
        return;
    }
    var player = dungeon.getPlayableCharacter();
    var playerTile = dungeon.getTile(player);
    var playerX = playerTile.getX();
    var playerY = playerTile.getY();
    var range = attack.getRange();
    var dimension = range * 2 + 3;
    var rangeArray = new Array(dimension).fill(0).map(function(unused, x) {
        let dx = x - range - 1;
        return new Array(dimension).fill(0).map(function(unused, y) {
            let dy = y - range - 1;
            return dx * dx + dy * dy <= range * range;
        });
    });
    $('
').css({
        zIndex: 5,
        position: 'absolute',
        pointerEvents: 'none',
        left: (playerX - range - 1) * 5 + 'em',
        top: (playerY - range - 1) * 5 + 'em'
    }).append(rangeArray.map(function(row, dy) {
        return $('
').append(row.map(function(isTargettable, dx) {
            let cellX = playerX + dx - range - 1;
            let cellY = playerY + dy - range - 1;
            return $('
').css({
                //visibility: player.canSee(dungeon, dungeon.getTile(cellX, cellY)) ? 'visible' : 'hidden', // TODO: Make canSee accept x,y values
                float: 'left',
                width: '5em',
                height: '5em',
                pointerEvents: 'none',
                borderStyle: 'solid',
                borderColor: 'rgba(70, 70, 90, 1)',
                borderRadius: '-8px',
                borderTopWidth: (isTargettable && !rangeArray[dy - 1][dx]) ? '2px' : '0',
                borderRightWidth: (isTargettable && !rangeArray[dy][dx + 1]) ? '2px' : '0',
                borderBottomWidth: (isTargettable && !rangeArray[dy + 1][dx]) ? '2px' : '0',
                borderLeftWidth: (isTargettable && !rangeArray[dy][dx - 1]) ? '2px' : '0'
            });
        }));
    })).appendTo(grid);
    console.log(rangeArray);
}

export default class GraphicDungeonView {
    constructor(sharedData) {
        var self = this;
        this._sharedData = sharedData;
        var dungeon = sharedData.getDungeon();
        var width = dungeon.getWidth();
        var height = dungeon.getHeight();

        var scrollPane = this._scrollPane = document.createElement('div');
        scrollPane.classList.add('grid-scroll');
        var grid = this._grid = document.createElement('div');
        grid.classList.add('grid');
        grid.classList.add('theme-default');
        grid.setAttribute('role', 'grid');
        grid.setAttribute('aria-readonly', true);
        scrollPane.appendChild(grid);
        for(var y = 0; y < height; y++) {
            var row = document.createElement('div');
            row.classList.add('row');
            row.setAttribute('role', 'row');
            grid.appendChild(row);
            for(var x = 0; x < width; x++) {
                var cell = document.createElement('div');
                cell.classList.add('cell');
                cell.setAttribute('data-x', x);
                cell.setAttribute('data-y', y);
                cell.setAttribute('role', 'gridcell');
                row.appendChild(cell);
            }
        }
        this._synchronizeView();

        dungeon.addObserver(function observer(event) {
            self.update(event);
        });

        (function() {
            var timer;
            sharedData.addObserver(function() {
                clearTimeout(timer);
                timer = setTimeout(function() {
                    var dungeon = sharedData.getDungeon();
                    var targettable = sharedData.getTargettedAbility() ||
                        sharedData.getTargettedItem() ||
                        dungeon.getPlayableCharacter().getRangedWeapon();
                    updateRangeIndicator(grid, sharedData.getDungeon(), targettable);
                });
            });
        })();

        DungeonTooltips.bindTooltips(dungeon, grid);
    }

    getDom() {
        return this._scrollPane;
    }

    /**
     * Makes the view match the current game state.
     * This is not called every event. It is called
     * when skipping animations (e.g because player
     * provided multiple inputs quickly)
     */
    _synchronizeView() {
        var self = this;
        var grid = this.getDom();
        var dungeon = this._sharedData.getDungeon();
        var player = dungeon.getPlayableCharacter();

        this._resetAnimationQueue();

        dungeon.forEachTile(function(tile, x, y) {
            var cell = grid.querySelector('[data-x="'+x+'"][data-y="'+y+'"]');
            var fc;
            while(fc = cell.firstChild) {
                cell.removeChild(fc);
            }
            cell.setAttribute('data-tile-type', tile.constructor.name);
            if(player) {
                cell.setAttribute('data-explored', player.hasSeen(tile));
                cell.setAttribute('data-visible', player.canSee(dungeon, tile));
                var creature = tile.getCreature();
                if(creature) {
                    var dom = self._getDomForCreature(creature);
                    cell.appendChild(dom);
                    self._animateBars(creature);
                }
            }
        });

        // Set grid width programatically to override table layout algorithm
        var table = grid.querySelector('.grid');
        table.style.width = 5 * dungeon.getWidth() + 'em';
    }

    _animateBars(creature) {
        //var SCALE = 2;
        if(creature.isDead()) {
            return;
        }
        var dom = this._getDomForCreature(creature);
        dom.querySelector('.hp').style.width = creature.getCurrentHP() * 100 / creature.getBaseHP() + '%';
        dom.querySelector('.action-bar').style.width = creature.getTimeToNextMove() * 100 / creature.getSpeed() + '%';
    }

    _resetAnimationQueue() {
        if(this._rejections) {
            this._rejections.forEach(clearTimeout);
        }
        this._rejections = [];
    }

    _createDelay(action, delay) {
        this._rejections.push(setTimeout(action, delay));
    }

    _queueAnimation(event) {
        var self = this;
        var grid = this.getDom();
        var dungeon = this._sharedData.getDungeon();
        var delay = event.getTimestamp() - (this._lastHumanMovingEvent ? this._lastHumanMovingEvent.getTimestamp() : 0);
        if(event instanceof GameEvents.AbilityEvent) {
            var ability = event.getAbility();
            if(ability.getRange() > 1 && ability.isTargetted() && ability.isTargetCreature() && !ability.isMovementAbility()) {
                var caster = event.getCreature();
                var casterLocation = dungeon.getTile(caster);
                var target = event.getTile().getCreature();
                this._createDelay(function() {
                    let targetTile = (target && dungeon.getTile(target)) || event.getTile(); // Get target position dynamically so shooting at moving targets looks ok
                    GridAnimations.animateProjectile(dungeon, grid, ability, casterLocation, targetTile);
                }, delay);
            }
        } else if(event instanceof GameEvents.AttackEvent) {
            let attacker = event.getAttacker();
            let target = event.getTarget();
            let weapon = event.getWeapon();
            let tile = dungeon.getTile(attacker);
            let cell = grid.querySelector('[data-x="'+tile.getX()+'"][data-y="'+tile.getY()+'"]');
            this._createDelay(function() {
                let targetTile = dungeon.getTile(target); // Get target position dynamically so shooting at moving targets looks ok
                if(weapon.getRange() === 1) {
                    // TODO: Always add AttackEvent and consider range in CSS
                    cell.setAttribute('data-event-name', 'AttackEvent');
                } else {
                    GridAnimations.animateProjectile(dungeon, grid, weapon, tile, targetTile);
                }
            }, delay);
        } else if(event instanceof GameEvents.MoveEvent || event instanceof GameEvents.PositionChangeEvent) {
            this._createDelay(function() {
                let player = dungeon.getPlayableCharacter();
                let to = event.getToCoords();
                let cell = grid.querySelector('[data-x="'+to.x+'"][data-y="'+to.y+'"]');
                let creature = event.getCreature();
                let dom = self._getDomForCreature(creature);
                cell.appendChild(dom);

                // Update player vision
                Array.from(grid.querySelector('[data-visible="true"]')).forEach(function(cell) {
                    cell.setAttribute('data-visible', 'false');
                });
                if(player) {
                    let playerLocation = dungeon.getTile(player);
                    let visionRadius = Math.ceil(player.getVisionRadius());
                    let startX = Math.max(0, playerLocation.getX() - visionRadius);
                    let endX = Math.min(dungeon.getWidth() - 1, playerLocation.getX() + visionRadius);
                    let startY = Math.max(0, playerLocation.getY() - visionRadius);
                    let endY = Math.min(dungeon.getHeight() - 1, playerLocation.getY() + visionRadius);
                    for(let x = startX; x <= endX; x++) {
                        for(let y = startY; y <= endY; y++) {
                            let tile = dungeon.getTile(x, y);
                            let cell = grid.querySelector('[data-x="'+x+'"][data-y="'+y+'"]');
                            cell.setAttribute('data-tile-type', tile.constructor.name);
                            cell.setAttribute('data-explored', player.hasSeen(tile));
                            cell.setAttribute('data-visible', player.canSee(dungeon, tile));
                            tile.getItems().forEach(function(item) {
                                cell.appendChild(self._getDomForItem(item));
                            });
                        }
                    }
                }

            }, delay);
        } else if(event instanceof GameEvents.TakeItemEvent) {
            this._createDelay(function() {
                let location = dungeon.getTile(event.getCreature());
                let cell = grid.querySelector('[data-x="'+location.getX()+'"][data-y="'+location.getY()+'"]');
                cell.removeChild(self._getDomForItem(event.getItem()));
            }, delay);
        } else if(event instanceof GameEvents.HitpointsEvent) {
            let creature = event.getCreature();
            let tile = dungeon.getTile(creature);
            let cell = grid.querySelector('[data-x="'+tile.getX()+'"][data-y="'+tile.getY()+'"]');
            //let tile = this._dungeon.getTile(creature);
            //let cell = grid.querySelector(`[data-x="${tile.getX()}"][data-y="${tile.getY()}"]`);
            this._createDelay(function() {
                cell.setAttribute('data-event-name', 'HitpointsEvent');
                cell.setAttribute('data-is-hp-change-negative', event.getAmount() < 0);
                let dom = self._getDomForCreature(creature);
                if(creature.isDead()) {
                    dom.setAttribute('data-is-dead', true);
                }
            }, delay + 200); // Death needs to be delayed so it appears to follow its cause
        } else if(event instanceof GameEvents.BuffAppliedEvent || event instanceof GameEvents.BuffEndedEvent) {
            this._createDelay(function() {
                var creature = event.getCreature();
                var dom = self._getDomForCreature(creature);
                dom.setAttribute('buffs', creature.getBuffs().map((buff)=>buff.toString()).join(' '));
            }, delay);
        }

        this._createDelay(function() {
            GridAnimations.animateEvent(dungeon, self, event);
        });
    }

    update(event) {
        var self = this;
        var grid = this.getDom();
        var dungeon = this._sharedData.getDungeon();

        if(event instanceof GameEvent) {
            if(event instanceof GameEvents.HumanMovingEvent) {
                Array.from(grid.querySelectorAll('[data-event-name]')).forEach(function(tile) {
                    tile.removeAttribute('data-event-name');
                });
                //this._synchronizeView();
                this._lastHumanMovingEvent = event;
            } else {
                this._queueAnimation(event);
            }
        }

        if(event instanceof GameEvents.HumanToMoveEvent || event instanceof GameEvents.HitpointsEvent) {
            dungeon.getCreatures().forEach(function(creature) {
                // TODO: Animate HP by moving this to AttackEvent handling
                self._animateBars(creature);
            });
        }

        // Tempory
        // Sync phone charge state
        dungeon.getCreatures().filter(function(creature) {
            return creature.constructor.name === 'ClunkyNinetiesCellPhone';
        }).forEach(function(creature) {
            self._getDomForCreature(creature).setAttribute('data-phone-charged', creature.getRangedWeapon().isCharged(dungeon));
        });

        // TODO: Consider if visibility needs to be animated
        // during events other than HumanMovingEvent
        // Should only be an issue in destructible environment
    }

    getSelectedTileCoordinates() {

    }

    _getDomForCreature(creature) {
        var id = creature.getId();
        var node = this.getDom().querySelector('[data-id="'+id+'"]');
        if(node) {
            return node;
        } else {
            var div = document.createElement('div');
            div.setAttribute('data-id', id);
            div.setAttribute('data-creature-name', creature.toString());
            div.classList.add('entity');
            div.classList.add('creature');

            var stats = document.createElement('div');
            stats.classList.add('stats');
            div.appendChild(stats);

            var hp = document.createElement('div');
            hp.classList.add('hp');
            stats.appendChild(hp);

            var actionBar = document.createElement('div');
            actionBar.classList.add('action-bar');
            stats.appendChild(actionBar);

            return div;
        }
    }

    _getDomForItem(item) {
        var id = item.getId();
        var node = this.getDom().querySelector('[data-id="'+id+'"]');
        if(node) {
            return node;
        } else {
            var div = document.createElement('div');
            div.setAttribute('data-id', id);
            div.setAttribute('data-item-name', item.toString());
            div.classList.add('entity');
            div.classList.add('item');
            div.classList.add('icon');

            return div;
        }
    }
}
<commit_msg>Range indicator fixes<commit_after>import GameEvent from '../events/GameEvent.js';
import GameEvents from '../events/GameEvents.js';

import GridAnimations from './GridAnimations.js';
import DungeonTooltips from './DungeonTooltips.js';

//var ANIMATE_BARS = false;

function updateRangeIndicator(grid, dungeon, attack) {
    $('.range-indicator').remove();
    if(!attack) {
        return;
    }
    var color = 'rgb(70, 70, 90)';
    if(typeof attack.isMovementAbility === 'function') {
        color = 'darkviolet';
    } else if(typeof attack.isTargetted === 'function') {
        color = 'violet';
    }
    var player = dungeon.getPlayableCharacter();
    var playerTile = dungeon.getTile(player);
    var playerX = playerTile.getX();
    var playerY = playerTile.getY();
    var range = attack.getRange();
    var dimension = range * 2 + 3;
    var rangeArray = new Array(dimension).fill(0).map(function(unused, x) {
        let dx = x - range - 1;
        return new Array(dimension).fill(0).map(function(unused, y) {
            let dy = y - range - 1;
            return dx * dx + dy * dy <= range * range;
        });
    });
    $('
').css({
        zIndex: 5,
        position: 'absolute',
        pointerEvents: 'none',
        left: (playerX - range - 1) * 5 + 'em',
        top: (playerY - range - 1) * 5 + 'em'
    }).append(rangeArray.map(function(row, dy) {
        return $('
').css({
            width: dimension * 5 + 'em',
            overflow: 'auto'
        }).append(row.map(function(isTargettable, dx) {
            let cellX = playerX + dx - range - 1;
            let cellY = playerY + dy - range - 1;
            return $('
').css({
                //visibility: player.canSee(dungeon, dungeon.getTile(cellX, cellY)) ? 'visible' : 'hidden', // TODO: Make canSee accept x,y values
                float: 'left',
                width: '5em',
                height: '5em',
                pointerEvents: 'none',
                borderStyle: 'solid',
                borderColor: color,
                borderRadius: '-8px',
                borderTopWidth: (isTargettable && !rangeArray[dy - 1][dx]) ? '2px' : '0',
                borderRightWidth: (isTargettable && !rangeArray[dy][dx + 1]) ? '2px' : '0',
                borderBottomWidth: (isTargettable && !rangeArray[dy + 1][dx]) ? '2px' : '0',
                borderLeftWidth: (isTargettable && !rangeArray[dy][dx - 1]) ? '2px' : '0'
            });
        }));
    })).appendTo(grid);
    console.log(rangeArray);
}

export default class GraphicDungeonView {
    constructor(sharedData) {
        var self = this;
        this._sharedData = sharedData;
        var dungeon = sharedData.getDungeon();
        var width = dungeon.getWidth();
        var height = dungeon.getHeight();

        var scrollPane = this._scrollPane = document.createElement('div');
        scrollPane.classList.add('grid-scroll');
        var grid = this._grid = document.createElement('div');
        grid.classList.add('grid');
        grid.classList.add('theme-default');
        grid.setAttribute('role', 'grid');
        grid.setAttribute('aria-readonly', true);
        scrollPane.appendChild(grid);
        for(var y = 0; y < height; y++) {
            var row = document.createElement('div');
            row.classList.add('row');
            row.setAttribute('role', 'row');
            grid.appendChild(row);
            for(var x = 0; x < width; x++) {
                var cell = document.createElement('div');
                cell.classList.add('cell');
                cell.setAttribute('data-x', x);
                cell.setAttribute('data-y', y);
                cell.setAttribute('role', 'gridcell');
                row.appendChild(cell);
            }
        }
        this._synchronizeView();

        dungeon.addObserver(function observer(event) {
            self.update(event);
        });

        (function() {
            var timer;
            sharedData.addObserver(function() {
                clearTimeout(timer);
                timer = setTimeout(function() {
                    var dungeon = sharedData.getDungeon();
                    var targettable;
                    if(typeof sharedData.getTargettedAbility() === 'number') {
                        targettable = dungeon.getPlayableCharacter().getAbility(sharedData.getTargettedAbility());
                    } else if(typeof sharedData.getTargettedItem() === 'number') {
                        targettable = dungeon.getPlayableCharacter().getAbility(sharedData.getTargettedItem());
                    } else {
                        targettable = dungeon.getPlayableCharacter().getRangedWeapon();
                    }
                    updateRangeIndicator(grid, sharedData.getDungeon(), targettable);
                });
            });
        })();

        DungeonTooltips.bindTooltips(dungeon, grid);
    }

    getDom() {
        return this._scrollPane;
    }

    /**
     * Makes the view match the current game state.
     * This is not called every event. It is called
     * when skipping animations (e.g because player
     * provided multiple inputs quickly)
     */
    _synchronizeView() {
        var self = this;
        var grid = this.getDom();
        var dungeon = this._sharedData.getDungeon();
        var player = dungeon.getPlayableCharacter();

        this._resetAnimationQueue();

        dungeon.forEachTile(function(tile, x, y) {
            var cell = grid.querySelector('[data-x="'+x+'"][data-y="'+y+'"]');
            var fc;
            while(fc = cell.firstChild) {
                cell.removeChild(fc);
            }
            cell.setAttribute('data-tile-type', tile.constructor.name);
            if(player) {
                cell.setAttribute('data-explored', player.hasSeen(tile));
                cell.setAttribute('data-visible', player.canSee(dungeon, tile));
                var creature = tile.getCreature();
                if(creature) {
                    var dom = self._getDomForCreature(creature);
                    cell.appendChild(dom);
                    self._animateBars(creature);
                }
            }
        });

        // Set grid width programatically to override table layout algorithm
        var table = grid.querySelector('.grid');
        table.style.width = 5 * dungeon.getWidth() + 'em';
    }

    _animateBars(creature) {
        //var SCALE = 2;
        if(creature.isDead()) {
            return;
        }
        var dom = this._getDomForCreature(creature);
        dom.querySelector('.hp').style.width = creature.getCurrentHP() * 100 / creature.getBaseHP() + '%';
        dom.querySelector('.action-bar').style.width = creature.getTimeToNextMove() * 100 / creature.getSpeed() + '%';
    }

    _resetAnimationQueue() {
        if(this._rejections) {
            this._rejections.forEach(clearTimeout);
        }
        this._rejections = [];
    }

    _createDelay(action, delay) {
        this._rejections.push(setTimeout(action, delay));
    }

    _queueAnimation(event) {
        var self = this;
        var grid = this.getDom();
        var dungeon = this._sharedData.getDungeon();
        var delay = event.getTimestamp() - (this._lastHumanMovingEvent ? this._lastHumanMovingEvent.getTimestamp() : 0);
        if(event instanceof GameEvents.AbilityEvent) {
            var ability = event.getAbility();
            if(ability.getRange() > 1 && ability.isTargetted() && ability.isTargetCreature() && !ability.isMovementAbility()) {
                var caster = event.getCreature();
                var casterLocation = dungeon.getTile(caster);
                var target = event.getTile().getCreature();
                this._createDelay(function() {
                    let targetTile = (target && dungeon.getTile(target)) || event.getTile(); // Get target position dynamically so shooting at moving targets looks ok
                    GridAnimations.animateProjectile(dungeon, grid, ability, casterLocation, targetTile);
                }, delay);
            }
        } else if(event instanceof GameEvents.AttackEvent) {
            let attacker = event.getAttacker();
            let target = event.getTarget();
            let weapon = event.getWeapon();
            let tile = dungeon.getTile(attacker);
            let cell = grid.querySelector('[data-x="'+tile.getX()+'"][data-y="'+tile.getY()+'"]');
            this._createDelay(function() {
                let targetTile = dungeon.getTile(target); // Get target position dynamically so shooting at moving targets looks ok
                if(weapon.getRange() === 1) {
                    // TODO: Always add AttackEvent and consider range in CSS
                    cell.setAttribute('data-event-name', 'AttackEvent');
                } else {
                    GridAnimations.animateProjectile(dungeon, grid, weapon, tile, targetTile);
                }
            }, delay);
        } else if(event instanceof GameEvents.MoveEvent || event instanceof GameEvents.PositionChangeEvent) {
            this._createDelay(function() {
                let player = dungeon.getPlayableCharacter();
                let to = event.getToCoords();
                let cell = grid.querySelector('[data-x="'+to.x+'"][data-y="'+to.y+'"]');
                let creature = event.getCreature();
                let dom = self._getDomForCreature(creature);
                cell.appendChild(dom);

                // Update player vision
                Array.from(grid.querySelector('[data-visible="true"]')).forEach(function(cell) {
                    cell.setAttribute('data-visible', 'false');
                });
                if(player) {
                    let playerLocation = dungeon.getTile(player);
                    let visionRadius = Math.ceil(player.getVisionRadius());
                    let startX = Math.max(0, playerLocation.getX() - visionRadius);
                    let endX = Math.min(dungeon.getWidth() - 1, playerLocation.getX() + visionRadius);
                    let startY = Math.max(0, playerLocation.getY() - visionRadius);
                    let endY = Math.min(dungeon.getHeight() - 1, playerLocation.getY() + visionRadius);
                    for(let x = startX; x <= endX; x++) {
                        for(let y = startY; y <= endY; y++) {
                            let tile = dungeon.getTile(x, y);
                            let cell = grid.querySelector('[data-x="'+x+'"][data-y="'+y+'"]');
                            cell.setAttribute('data-tile-type', tile.constructor.name);
                            cell.setAttribute('data-explored', player.hasSeen(tile));
                            cell.setAttribute('data-visible', player.canSee(dungeon, tile));
                            tile.getItems().forEach(function(item) {
                                cell.appendChild(self._getDomForItem(item));
                            });
                        }
                    }
                }

            }, delay);
        } else if(event instanceof GameEvents.TakeItemEvent) {
            this._createDelay(function() {
                let location = dungeon.getTile(event.getCreature());
                let cell = grid.querySelector('[data-x="'+location.getX()+'"][data-y="'+location.getY()+'"]');
                cell.removeChild(self._getDomForItem(event.getItem()));
            }, delay);
        } else if(event instanceof GameEvents.HitpointsEvent) {
            let creature = event.getCreature();
            let tile = dungeon.getTile(creature);
            let cell = grid.querySelector('[data-x="'+tile.getX()+'"][data-y="'+tile.getY()+'"]');
            //let tile = this._dungeon.getTile(creature);
            //let cell = grid.querySelector(`[data-x="${tile.getX()}"][data-y="${tile.getY()}"]`);
            this._createDelay(function() {
                cell.setAttribute('data-event-name', 'HitpointsEvent');
                cell.setAttribute('data-is-hp-change-negative', event.getAmount() < 0);
                let dom = self._getDomForCreature(creature);
                if(creature.isDead()) {
                    dom.setAttribute('data-is-dead', true);
                }
            }, delay + 200); // Death needs to be delayed so it appears to follow its cause
        } else if(event instanceof GameEvents.BuffAppliedEvent || event instanceof GameEvents.BuffEndedEvent) {
            this._createDelay(function() {
                var creature = event.getCreature();
                var dom = self._getDomForCreature(creature);
                dom.setAttribute('buffs', creature.getBuffs().map((buff)=>buff.toString()).join(' '));
            }, delay);
        }

        this._createDelay(function() {
            GridAnimations.animateEvent(dungeon, self, event);
        });
    }

    update(event) {
        var self = this;
        var grid = this.getDom();
        var dungeon = this._sharedData.getDungeon();

        if(event instanceof GameEvent) {
            if(event instanceof GameEvents.HumanMovingEvent) {
                Array.from(grid.querySelectorAll('[data-event-name]')).forEach(function(tile) {
                    tile.removeAttribute('data-event-name');
                });
                //this._synchronizeView();
                this._lastHumanMovingEvent = event;
            } else {
                this._queueAnimation(event);
            }
        }

        if(event instanceof GameEvents.HumanToMoveEvent || event instanceof GameEvents.HitpointsEvent) {
            dungeon.getCreatures().forEach(function(creature) {
                // TODO: Animate HP by moving this to AttackEvent handling
                self._animateBars(creature);
            });
        }

        // Tempory
        // Sync phone charge state
        dungeon.getCreatures().filter(function(creature) {
            return creature.constructor.name === 'ClunkyNinetiesCellPhone';
        }).forEach(function(creature) {
            self._getDomForCreature(creature).setAttribute('data-phone-charged', creature.getRangedWeapon().isCharged(dungeon));
        });

        // TODO: Consider if visibility needs to be animated
        // during events other than HumanMovingEvent
        // Should only be an issue in destructible environment
    }

    getSelectedTileCoordinates() {

    }

    _getDomForCreature(creature) {
        var id = creature.getId();
        var node = this.getDom().querySelector('[data-id="'+id+'"]');
        if(node) {
            return node;
        } else {
            var div = document.createElement('div');
            div.setAttribute('data-id', id);
            div.setAttribute('data-creature-name', creature.toString());
            div.classList.add('entity');
            div.classList.add('creature');

            var stats = document.createElement('div');
            stats.classList.add('stats');
            div.appendChild(stats);

            var hp = document.createElement('div');
            hp.classList.add('hp');
            stats.appendChild(hp);

            var actionBar = document.createElement('div');
            actionBar.classList.add('action-bar');
            stats.appendChild(actionBar);

            return div;
        }
    }

    _getDomForItem(item) {
        var id = item.getId();
        var node = this.getDom().querySelector('[data-id="'+id+'"]');
        if(node) {
            return node;
        } else {
            var div = document.createElement('div');
            div.setAttribute('data-id', id);
            div.setAttribute('data-item-name', item.toString());
            div.classList.add('entity');
            div.classList.add('item');
            div.classList.add('icon');

            return div;
        }
    }
}
<|endoftext|>


<commit_before>define([
    'require',
    'promise',
    'knockout'
], function (
    require,
    Promise,
    ko
) {
    'use strict';

    function factory(config) {
        var runtime = config.runtime;
        var components = {};

        function start() {
            return true;
        }

        function stop() {
            return true;
        }

        function pluginHandler(serviceConfig, pluginConfig) {
            return Promise.try(function () {

                return Promise.all(serviceConfig.map(function (componentConfig) {
                    // keep a map of loaded components
                    if (components[componentConfig.name]) {
                        throw new Error('Component already loaded "' + componentConfig.name);
                    }

                    // simply require the module to register the component, or components.
                    // oops, that would break the model...

                    return new Promise(function (resolve, reject) {
                        var modulePaths = [];
                        modulePaths.push([pluginConfig.moduleRoot, componentConfig.module].join('/'));
                        if (componentConfig.css) {
                            modulePaths.push('css!' + [pluginConfig.moduleRoot, componentConfig.module].join('/'));
                        }
                        require(modulePaths, function (result) {
                            // The result is a component factory which takes no arguments.
                            if (typeof result !== 'function') {
                                reject(new Error('The component module is not a factory function; perhaps it shouldn\'t be mapped in config.yml'));
                            }
                            try {
                                ko.components.register(componentConfig.name, result());
                                resolve(result);
                            } catch (ex) {
                                reject(ex);
                            }
                        }, function (err) {
                            reject(err);
                        });
                    });
                }));
            });
        }
        return {
            // lifecycle interface
            start: start,
            stop: stop,
            // plugin interface
            pluginHandler: pluginHandler
        };
    }
    return {
        make: function (config) {
            return factory(config);
        }
    };
});
<commit_msg>improve error message to include module path<commit_after>define([
    'require',
    'promise',
    'knockout'
], function (
    require,
    Promise,
    ko
) {
    'use strict';

    function factory(config) {
        var runtime = config.runtime;
        var components = {};

        function start() {
            return true;
        }

        function stop() {
            return true;
        }

        function pluginHandler(serviceConfig, pluginConfig) {
            return Promise.try(function () {

                return Promise.all(serviceConfig.map(function (componentConfig) {
                    // keep a map of loaded components
                    if (components[componentConfig.name]) {
                        throw new Error('Component already loaded "' + componentConfig.name);
                    }

                    // simply require the module to register the component, or components.
                    // oops, that would break the model...

                    return new Promise(function (resolve, reject) {
                        var modulePaths = [];
                        modulePaths.push([pluginConfig.moduleRoot, componentConfig.module].join('/'));
                        if (componentConfig.css) {
                            modulePaths.push('css!' + [pluginConfig.moduleRoot, componentConfig.module].join('/'));
                        }
                        require(modulePaths, function (result) {
                            // The result is a component factory which takes no arguments.
                            if (typeof result !== 'function') {
                                reject(new Error('The component module is not a factory function; perhaps it shouldn\'t be mapped in config.yml: ' + modulePaths.join(',')));
                            }
                            try {
                                ko.components.register(componentConfig.name, result());
                                resolve(result);
                            } catch (ex) {
                                reject(ex);
                            }
                        }, function (err) {
                            reject(err);
                        });
                    });
                }));
            });
        }
        return {
            // lifecycle interface
            start: start,
            stop: stop,
            // plugin interface
            pluginHandler: pluginHandler
        };
    }
    return {
        make: function (config) {
            return factory(config);
        }
    };
});
<|endoftext|>


<commit_before>'use strict';

import React, { PropTypes } from 'react';
import CASE_INFO_TYPES from './constants/case_info_types';
import { userScore } from './helpers/user_score';

var _ = require('underscore');

// Render scores viewer in Gene Curation Interface
var ScoreViewer = module.exports.ScoreViewer = React.createClass({
    propTypes: {
        evidence: React.PropTypes.object, // Individual, Experimental or Case Control
        otherScores: React.PropTypes.bool // TRUE if we only want show scores by others
    },

    getInitialState() {
        return {
            evidenceScores: [] // One or more scores
        };
    },

    componentDidMount() {
        this.loadData();
    },

    loadData() {
        let evidenceObj = this.props.evidence;
        // Get evidenceScore object for the logged-in user if exists
        if (evidenceObj.scores && evidenceObj.scores.length) {
            this.setState({evidenceScores: evidenceObj.scores});
        }
    },

    // Find the scores owned by other users
    getOtherScores(evidenceScores) {
        let validScores = [], filteredScores = [];
        let user = this.props.session && this.props.session.user_properties;
        let otherScores = this.props.otherScores;

        if (evidenceScores && evidenceScores.length) {
            if (otherScores) {
                filteredScores = evidenceScores.filter(score => {
                    return score.submitted_by.uuid !== user.uuid;
                });
            } else {
                filteredScores = evidenceScores;
            }
        }
        // Insanity check for erroneous score objects with Score Status value as 'none'
        _.map(filteredScores, score => {
            if (score.scoreStatus !== 'none') {
                validScores.push(score);
            }
        });

        return validScores;
    },

    render() {
        // states
        let evidenceScores = this.state.evidenceScores;

        // variables
        let scores = this.getOtherScores(evidenceScores);

        return (

                {scores.map((item, i) => {
                    return (

{item.submitted_by.title}

                            {item.scoreStatus && item.scoreStatus === 'Score' ?

Score Status

{item.scoreStatus}

Variant Kind

{item.caseInfoType ? renderCaseInfoType(item.caseInfoType) : null}

Default Score

{item.calculatedScore ? item.calculatedScore : null}

Changed Score

{item.score ? item.score : null}

Reaon(s) for score change

{item.scoreExplanation ? item.scoreExplanation : null}

                                :

Score Status

{item.scoreStatus}

                            }

                    );
                })}

        );
    },
});

// Transform the stored Case Information type 'value' into 'description'
function renderCaseInfoType(value) {
    let description;
    // Put CASE_INFO_TYPES object keys into an array
    const caseInfoTypeKeys = Object.keys(CASE_INFO_TYPES);
    // Use the 'OTHER' group because it has all 5 Case Information types
    let caseInfoTypeGroup = CASE_INFO_TYPES.OTHER;
    // Assign different number of variant kinds given a matched modeInheritance
    caseInfoTypeGroup.forEach(item => {
        if (value === item.TYPE) {
            description = item.DESCRIPTION;
        }
    });

    return description;
}
<commit_msg>Added session props<commit_after>'use strict';

import React, { PropTypes } from 'react';
import CASE_INFO_TYPES from './constants/case_info_types';
import { userScore } from './helpers/user_score';

var _ = require('underscore');

// Render scores viewer in Gene Curation Interface
var ScoreViewer = module.exports.ScoreViewer = React.createClass({
    propTypes: {
        session: React.PropTypes.object, // Session object passed from parent
        evidence: React.PropTypes.object, // Individual, Experimental or Case Control
        otherScores: React.PropTypes.bool // TRUE if we only want show scores by others
    },

    getInitialState() {
        return {
            evidenceScores: [] // One or more scores
        };
    },

    componentDidMount() {
        this.loadData();
    },

    loadData() {
        let evidenceObj = this.props.evidence;
        // Get evidenceScore object for the logged-in user if exists
        if (evidenceObj.scores && evidenceObj.scores.length) {
            this.setState({evidenceScores: evidenceObj.scores});
        }
    },

    // Find the scores owned by other users
    getOtherScores(evidenceScores) {
        let validScores = [], filteredScores = [];
        let user = this.props.session && this.props.session.user_properties;
        let otherScores = this.props.otherScores;

        if (evidenceScores && evidenceScores.length) {
            if (otherScores) {
                filteredScores = evidenceScores.filter(score => {
                    return score.submitted_by.uuid !== user.uuid;
                });
            } else {
                filteredScores = evidenceScores;
            }
        }
        // Insanity check for erroneous score objects with Score Status value as 'none'
        _.map(filteredScores, score => {
            if (score.scoreStatus !== 'none') {
                validScores.push(score);
            }
        });

        return validScores;
    },

    render() {
        // states
        let evidenceScores = this.state.evidenceScores;

        // variables
        let scores = this.getOtherScores(evidenceScores);

        return (

                {scores.map((item, i) => {
                    return (

{item.submitted_by.title}

                            {item.scoreStatus && item.scoreStatus === 'Score' ?

Score Status

{item.scoreStatus}

Variant Kind

{item.caseInfoType ? renderCaseInfoType(item.caseInfoType) : null}

Default Score

{item.calculatedScore ? item.calculatedScore : null}

Changed Score

{item.score ? item.score : null}

Reaon(s) for score change

{item.scoreExplanation ? item.scoreExplanation : null}

                                :

Score Status

{item.scoreStatus}

                            }

                    );
                })}

        );
    },
});

// Transform the stored Case Information type 'value' into 'description'
function renderCaseInfoType(value) {
    let description;
    // Put CASE_INFO_TYPES object keys into an array
    const caseInfoTypeKeys = Object.keys(CASE_INFO_TYPES);
    // Use the 'OTHER' group because it has all 5 Case Information types
    let caseInfoTypeGroup = CASE_INFO_TYPES.OTHER;
    // Assign different number of variant kinds given a matched modeInheritance
    caseInfoTypeGroup.forEach(item => {
        if (value === item.TYPE) {
            description = item.DESCRIPTION;
        }
    });

    return description;
}
<|endoftext|>


<commit_before>import React from 'react';
import { Button, Popover, Navbar, Tooltip, OverlayTrigger, Form, FormGroup, FormControl, Col} from 'react-bootstrap';
import Modal from 'react-modal';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { unloadLoginModal, loadSignUpModal, unloadSignUpModal } from '../actions/modalPopUpActions';

export class Login extends React.Component {

  constructor(props, ctx) {
      super(props);
      console.log('login constructor called');
      this.login = this.login.bind(this);
  }

  login() {
    console.log('Logging user in');
    console.log('email:', this.refs.loginEmail.value);
    console.log('passwd:', this.refs.loginPasswd.value);
  }

  render() {
    console.log('Login modal render');
    const customStyles = {
      content : {
        top                   : '50%',
        left                  : '50%',
        right                 : 'auto',
        bottom                : 'auto',
        marginRight           : '-50%',
        transform             : 'translate(-50%, -50%)'
      }
    };
    return (

        <Modal
          isOpen={this.props.showLogin}
           onRequestClose={this.props.unloadLoginModal}
           style={customStyles}
        >

Login

          <form onSubmit={this.login}>
            Email: <input type="text" name="Email" ref="loginEmail" />

            Password: <input type="password" name="password" ref="loginPasswd" />
            <input type="submit" value="Submit" />
          </form>
        </Modal>

    )
  }
}

function mapStateToProps(state) {
  return {
    showLogin : state.ui.showLoginModal
  }
}

function matchDispatchToProps(dispatch) {
  return bindActionCreators({ unloadLoginModal : unloadLoginModal, loadSignUpModal : loadSignUpModal} , dispatch)
}

export default connect(mapStateToProps, matchDispatchToProps)(Login);
<commit_msg>login onChange<commit_after>import React from 'react';
import { Button, Popover, Navbar, Tooltip, OverlayTrigger, Form, FormGroup, FormControl, Col} from 'react-bootstrap';
import Modal from 'react-modal';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { unloadLoginModal, loadSignUpModal, unloadSignUpModal } from '../actions/modalPopUpActions';

export class Login extends React.Component {

  constructor(props, ctx) {
      super(props);
      console.log('login constructor called');
      this.login = this.login.bind(this);
      this.onFieldChange = this.onFieldChange.bind(this);
      this.state = {
        email : '',
        passwd : ''
      }
  }

  login(e) {
    e.preventDefault();
    console.log('Logging user in');
    console.log('email:', this.refs.loginEmail.value);
    console.log('passwd:', this.refs.loginPasswd.value);
  }

  onFieldChange(e) {
    //e.preventDefault();
    const state = this.state;
    state[e.target.name] = e.target.value;
    this.setState(state);
  }

  render() {
    console.log('Login modal render');
    const customStyles = {
      content : {
        top                   : '50%',
        left                  : '50%',
        right                 : 'auto',
        bottom                : 'auto',
        marginRight           : '-50%',
        transform             : 'translate(-50%, -50%)'
      }
    };
    const { email, passwd} = this.state;
    return (

        <Modal
          isOpen={this.props.showLogin}
           onRequestClose={this.props.unloadLoginModal}
           style={customStyles}
        >

Login

          <form onSubmit={this.login}>
            Email: <input type="text" name="email" value={email} onChange={this.onFieldChange} />

            Password: <input type="password" name="passwd" value={passwd} onChange={this.onFieldChange} />
            <input type="submit" value="Submit" />
          </form>
        </Modal>

    )
  }
}

function mapStateToProps(state) {
  return {
    showLogin : state.ui.showLoginModal
  }
}

function matchDispatchToProps(dispatch) {
  return bindActionCreators({ unloadLoginModal : unloadLoginModal, loadSignUpModal : loadSignUpModal} , dispatch)
}

export default connect(mapStateToProps, matchDispatchToProps)(Login);
<|endoftext|>


<commit_before>var db = require('../db');

module.exports = {
  login: function (req, res, next) {
    console.log('ENTER');
    var user = req.body;
    db.User.findOne({
      where: user
    })
    .then(function (user) {
      console.log('USER FIND ONE ', user);
      if (user) {
        req.session.email = user.email;
        res.status(200).json(user);
      } else {
        res.status(401).json({error: 'User or Password invalid'});
      }
    });
  },

  signup: function (req, res, next) {
    var user = req.body;
    console.log('user ', user);
    db.User.findOne({
      where: user
    })
    .then(function (userFound) {
      if (userFound) {
        res.status(401).json({error: 'User is already taken'});
      } else {
        db.User.create(user)
        .then(function (newUser){
          console.log('user created ', newUser);
          res.status(201).json(newUser);
        });
      }
    });
  }
};<commit_msg>[Refactor] Test login logout<commit_after>var db = require('../db');

module.exports = {
  login: function (req, res, next) {
    var user = req.body;
    db.User.findOne({
      where: user
    })
    .then(function (user) {
      if (user) {
        req.session.email = user.email;
        res.status(200).json(user);
      } else {
        res.status(401).json({error: 'User or Password invalid'});
      }
    });
  },

  signup: function (req, res, next) {
    var user = req.body;
    db.User.findOne({
      where: user
    })
    .then(function (userFound) {
      if (userFound) {
        res.status(401).json({error: 'User is already taken'});
      } else {
        db.User.create(user)
        .then(function (newUser){
          res.status(201).json(newUser);
        });
      }
    });
  }
};<|endoftext|>


<commit_before>Meteor.methods({
	'discussionComments.insert': function insertDiscussionComment(discussionCommentCandidate) {
		check(discussionCommentCandidate, Object);
		const discussionComment = discussionCommentCandidate;
		// Make sure the user is logged in before inserting
		if (!this.userId) {
			throw new Meteor.Error('not-authorized');
		}

		const currentUser = Meteor.users.findOne({ _id: this.userId });
		discussionComment.user = currentUser;
		discussionComment.votes = 1;
		discussionComment.voters = [currentUser._id];

		// check(discussionComment.user, Schemas.User);
		check(discussionComment.content, String);
		check(discussionComment.votes, Number);
		check(discussionComment.commentId, String);

		console.log('Inserting new comment', discussionComment);
		try {
			DiscussionComments.insert(discussionComment);
		} catch (err) {
			console.log(err);
		}
	},

	'discussionComments.update': function updateDiscussionComment(discussionCommentData) {
		check(discussionCommentData, Object);
		console.log('Discussion comment update:', discussionCommentData);

		const discussionComment = DiscussionComments.findOne({
			_id: discussionCommentData._id,
		});

		// Make sure the user has auth to edit
		if (this.userId !== discussionComment.user._id) {
			throw new Meteor.Error('not-authorized');
		}

		check(discussionCommentData.content, String);
		console.log('Updating comment', discussionComment);
		try {
			DiscussionComments.update({
				_id: discussionComment._id,
			}, {
				$set: {
					content: discussionCommentData.content,
				},
			});
		} catch (err) {
			console.log(err);
		}
	},

	'discussionComments.upvote': function upvoteDiscussionComment(discussionCommentId) {
		check(discussionCommentId, Number);

		const discussionComment = DiscussionComments.findOne(discussionCommentId);

		// Make sure the user has not already upvoted
		if (discussionComment.voters.indexOf(this.userId) >= 0) {
			throw new Meteor.Error('not-authorized');
		}

		try {
			DiscussionComments.update({
				_id: discussionCommentId,
			}, {
				$push: { voters: this.userId },
				$inc: { votes: 1 },
			});
		} catch (err) {
			console.log(err);
		}
	},

});
<commit_msg>discussionComments.upvote fix<commit_after>Meteor.methods({
	'discussionComments.insert': function insertDiscussionComment(discussionCommentCandidate) {
		check(discussionCommentCandidate, Object);
		const discussionComment = discussionCommentCandidate;
		// Make sure the user is logged in before inserting
		if (!this.userId) {
			throw new Meteor.Error('not-authorized');
		}

		const currentUser = Meteor.users.findOne({ _id: this.userId });
		discussionComment.user = currentUser;
		discussionComment.votes = 1;
		discussionComment.voters = [currentUser._id];

		// check(discussionComment.user, Schemas.User);
		check(discussionComment.content, String);
		check(discussionComment.votes, Number);
		check(discussionComment.commentId, String);

		console.log('Inserting new comment', discussionComment);
		try {
			DiscussionComments.insert(discussionComment);
		} catch (err) {
			console.log(err);
		}
	},

	'discussionComments.update': function updateDiscussionComment(discussionCommentData) {
		check(discussionCommentData, Object);
		console.log('Discussion comment update:', discussionCommentData);

		const discussionComment = DiscussionComments.findOne({
			_id: discussionCommentData._id,
		});

		// Make sure the user has auth to edit
		if (this.userId !== discussionComment.user._id) {
			throw new Meteor.Error('not-authorized');
		}

		check(discussionCommentData.content, String);
		console.log('Updating comment', discussionComment);
		try {
			DiscussionComments.update({
				_id: discussionComment._id,
			}, {
				$set: {
					content: discussionCommentData.content,
				},
			});
		} catch (err) {
			console.log(err);
		}
	},

	'discussionComments.upvote': function upvoteDiscussionComment(discussionCommentId) {
		check(discussionCommentId, String);

		const discussionComment = DiscussionComments.findOne(discussionCommentId);

		// Make sure the user has not already upvoted
		if (discussionComment.voters.indexOf(this.userId) >= 0) {
			throw new Meteor.Error('not-authorized');
		}

		try {
			DiscussionComments.update({
				_id: discussionCommentId,
			}, {
				$push: { voters: this.userId },
				$inc: { votes: 1 },
			});
		} catch (err) {
			console.log(err);
		}
	},

});
<|endoftext|>


<commit_before>import React, { createElement, Component } from 'react'
import PropTypes from 'prop-types'
import { Transition } from 'react-transition-group'
import CurrentTransition, { timeout } from './Fade'

const historyExitingEventType = `history::exiting`

/* globals window CustomEvent */
export const getUserConfirmation = (pathname, callback) => {
  const event = new CustomEvent(historyExitingEventType, { detail: { pathname } })
  window.dispatchEvent(event)
  setTimeout(() => {
    callback(true)
  }, timeout)
}

export default class PageTransition extends Component {
  static propTypes = {
    loader: PropTypes.any,
    location: PropTypes.any,
    pageResources: PropTypes.any,
  }

  constructor(props) {
    super(props)
    this.state = { exiting: false, nextPageResources: {} }
    this.listenerHandler = this.listenerHandler.bind(this)
  }

  listenerHandler(event) {
    const nextPageResources = this.props.loader.getResourcesForPathname(
      event.detail.pathname,
      nextPageResources => this.setState({ nextPageResources })
    ) || {}
    this.setState({ exiting: true, nextPageResources })
  }

  componentDidMount() {
    window.addEventListener(historyExitingEventType, this.listenerHandler)
  }

  componentWillUnmount() {
    window.removeEventListener(historyExitingEventType, this.listenerHandler)
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.location.key !== nextProps.location.key) {
      this.setState({ exiting: false, nextPageResources: {} })
    }
  }

  refNode = (c) => { this.node = c }

  render() {
    const transitionProps = {
      timeout: {
        enter: 0,
        exit: timeout,
      },
      onEntering: () => this.node.onEntering(),
      onEntered: () => this.node.onEntered(),
      onExiting: () => this.node.onExiting(),
      appear: true,
      in: !this.state.exiting,
      key: this.props.location.key,
    }
    return (
      <Transition {...transitionProps}>
        {
          (status) => {
            return (

                {<CurrentTransition ref={this.refNode} timeout={timeout} />}
                {
                  createElement(this.props.pageResources.component, {
                    ...this.props,
                    ...this.props.pageResources.json,
                  })
                }

            )
          }
        }
      </Transition>
    )
  }
}
<commit_msg>prevent blocking page transition on same path<commit_after>import React, { createElement, Component } from 'react'
import PropTypes from 'prop-types'
import { Transition } from 'react-transition-group'
import CurrentTransition, { timeout } from './Fade'

const historyExitingEventType = `history::exiting`

/* globals window CustomEvent */
export const getUserConfirmation = (pathname, callback) => {
  const event = new CustomEvent(historyExitingEventType, { detail: { pathname } })
  window.dispatchEvent(event)
  setTimeout(() => {
    callback(true)
  }, timeout)
}

export default class PageTransition extends Component {
  static propTypes = {
    loader: PropTypes.any,
    location: PropTypes.any,
    pageResources: PropTypes.any,
  }

  constructor(props) {
    super(props)
    this.state = { exiting: false, nextPageResources: {} }
    this.listenerHandler = this.listenerHandler.bind(this)
  }

  listenerHandler(event) {
    if (this.props.location.pathname === event.detail.pathname) return false
    const nextPageResources = this.props.loader.getResourcesForPathname(
      event.detail.pathname,
      nextPageResources => this.setState({ nextPageResources })
    ) || {}
    this.setState({ exiting: true, nextPageResources })
  }

  componentDidMount() {
    window.addEventListener(historyExitingEventType, this.listenerHandler)
  }

  componentWillUnmount() {
    window.removeEventListener(historyExitingEventType, this.listenerHandler)
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.location.key !== nextProps.location.key) {
      this.setState({ exiting: false, nextPageResources: {} })
    }
  }

  refNode = (c) => { this.node = c }

  render() {
    const transitionProps = {
      timeout: {
        enter: 0,
        exit: timeout,
      },
      onEntering: () => this.node.onEntering(),
      onEntered: () => this.node.onEntered(),
      onExiting: () => this.node.onExiting(),
      appear: true,
      in: !this.state.exiting,
      key: this.props.location.key,
    }
    return (
      <Transition {...transitionProps}>
        {
          (status) => {
            return (

                {<CurrentTransition ref={this.refNode} timeout={timeout} />}
                {
                  createElement(this.props.pageResources.component, {
                    ...this.props,
                    ...this.props.pageResources.json,
                  })
                }

            )
          }
        }
      </Transition>
    )
  }
}
<|endoftext|>


<commit_before>// Fill last user medias info
//
'use strict';

var _ = require('lodash');

module.exports = function (N) {

  var MEDIA_LIMIT = 7;

  // Fetch last user medias
  //
  N.wire.after('server:users.member', function fetch_last_photos(env, callback) {
    var mTypes = N.models.users.MediaInfo.types;

    N.models.users.MediaInfo
      .find({ user_id: env.data.user._id, type: { $in: mTypes.LIST_VISIBLE } })
      .lean(true)
      .sort('-media_id')
      .limit(MEDIA_LIMIT)
      .exec(function (err, medias) {
        if (err) {
          callback(err);
          return;
        }

        if (medias.length === 0) {
          callback();
          return;
        }

        env.res.blocks = env.res.blocks || {};
        _.set(env.res, 'blocks.medias', { list: medias });

        callback();
      });
  });

  // Fetch user medias count
  //
  N.wire.after('server:users.member', function fetch_photos_count(env, callback) {
    var mTypes = N.models.users.MediaInfo.types;

    if (!_.get(env.res.blocks.medias)) {
      callback();
      return;
    }

    N.models.users.MediaInfo
      .find({ user_id: env.data.user._id, type: { $in: mTypes.LIST_VISIBLE } })
      .count(function (err, count) {
        if (err) {
          callback(err);
          return;
        }

        env.res.blocks.medias.count = count;
        callback();
      });
  });
};
<commit_msg>Fix media counter in profile page<commit_after>// Fill last user medias info
//
'use strict';

var _ = require('lodash');

module.exports = function (N) {

  var MEDIA_LIMIT = 7;

  // Fetch last user medias
  //
  N.wire.after('server:users.member', function fetch_last_photos(env, callback) {
    var mTypes = N.models.users.MediaInfo.types;

    N.models.users.MediaInfo
      .find({ user_id: env.data.user._id, type: { $in: mTypes.LIST_VISIBLE } })
      .lean(true)
      .sort('-media_id')
      .limit(MEDIA_LIMIT)
      .exec(function (err, medias) {
        if (err) {
          callback(err);
          return;
        }

        if (medias.length === 0) {
          callback();
          return;
        }

        env.res.blocks = env.res.blocks || {};
        _.set(env.res, 'blocks.medias', { list: medias });

        callback();
      });
  });

  // Fetch user medias count
  //
  N.wire.after('server:users.member', function fetch_photos_count(env, callback) {
    var mTypes = N.models.users.MediaInfo.types;

    if (!_.get(env.res, 'blocks.medias')) {
      callback();
      return;
    }

    N.models.users.MediaInfo
      .find({ user_id: env.data.user._id, type: { $in: mTypes.LIST_VISIBLE } })
      .count(function (err, count) {
        if (err) {
          callback(err);
          return;
        }

        env.res.blocks.medias.count = count;
        callback();
      });
  });
};
<|endoftext|>


<commit_before>'use strict';

angular.module('spotmop.services.pusher', [
])

.factory("PusherService", function($rootScope, $http, $q, $localStorage, $cacheFactory, $templateCache, SettingsService, NotifyService){

	// make sure we have a local storage container
	if( typeof( $localStorage.pusher ) === 'undefined' )
		$localStorage.pusher = {};

	// build the endpoint string
	var mopidyhost = SettingsService.getSetting("mopidy.host");
	if( !mopidyhost ) mopidyhost = window.location.hostname;
	var mopidyport = SettingsService.getSetting("mopidy.port");
	if( !mopidyport ) mopidyport = "6680";

	var urlBase = 'http://'+ mopidyhost +':'+ mopidyport +'/spotmop/';

	var service = {
		pusher: {},

		isConnected: false,

		start: function(){
            var self = this;

            // Get mopidy/pusher ip and port from settigns
			var pusherhost = SettingsService.getSetting("mopidy.host");
			if( !pusherhost ) pusherhost = window.location.hostname;
			var pusherport = SettingsService.getSetting("pusher.port");
			if( !pusherport ) pusherport = "6681";

            try{
				var host = 'ws://'+pusherhost+':'+pusherport+'/pusher';

                var id = Math.random().toString(36).substr(2, 9);
                SettingsService.setSetting('pusher.id', id);
                var name = "User";
                if( SettingsService.getSetting('pusher.name') ) name = encodeURI(SettingsService.getSetting('pusher.name'));

				var pusher = new WebSocket(host, id+'_'+name );

				pusher.onopen = function(){
					$rootScope.$broadcast('spotmop:pusher:online');
					this.isConnected = true;
				}

				pusher.onmessage = function( response ){

					var message = JSON.parse(response.data);
					console.log(message);

                    $rootScope.$broadcast('spotmop:pusher:'+message.type, message.data);

					switch( message.type ){

						// initial connection status message, just parse it through quietly
						case 'client_connected':

							// if the new connection is mine
							if( message.data.id == SettingsService.getSetting('pusher.id') ){
								console.info('Pusher connection '+message.data.id+' accepted');

								// detect if the core has been updated
								if( message.data.version != SettingsService.getSetting('version.installed') ){
									NotifyService.notify('New version detected, clearing caches...');
									$cacheFactory.get('$http').removeAll();
									$templateCache.removeAll();
									SettingsService.setSetting('version.installed', message.data.version);
									SettingsService.runUpgrade();
								}
							}
							break;

						case 'notification':

							// respect notifications disabled setting
							if( !SettingsService.getSetting('pusher.disabled') ){
                                var title = '';
                                var body = '';
                                var icon = '';
                                if( typeof(message.data.title) !== 'undefined' ) title = message.data.title;
                                if( typeof(message.data.body) !== 'undefined' ) body = message.data.body;
                                if( typeof(message.data.icon) !== 'undefined' ) icon = message.data.icon;
								NotifyService.browserNotify( title, body, icon );
							}
							break;
					}
				}

				pusher.onclose = function(){
					$rootScope.$broadcast('spotmop:pusher:offline');
					service.isConnected = false;
                    setTimeout(function(){ service.start() }, 5000);
				}

				service.pusher = pusher;
            }catch(e){
                // need to re-initiate notifier
				console.log( "Connecting with Pusher failed with the following error message: " + e);
            }
		},

		stop: function() {
			this.pusher = null;
			this.isConnected = false;
		},

		send: function( data ){

			// make sure we have a recipients array, even if empty
			if( typeof(data.recipients) === 'undefined' ) data.recipients = [];

            // set the origin of this notification as the current client/connection
			var name = SettingsService.getSetting('pusher.name');
			if( !name ) name = 'User';
			data.origin = {
				ip: SettingsService.getSetting('pusher.ip'),
				id: SettingsService.getSetting('pusher.id'),
				name: name
			};

            // send off the notification to the websocket
			service.pusher.send( JSON.stringify(data) );
		},

        /**
         * Notify the Pusher service of our name
         * @param name (string)
         * @return deferred promise
         **/
        setMe: function( name ){
            var id = SettingsService.getSetting('pusher.id');
            $.ajax({
                method: 'GET',
                cache: false,
                url: urlBase+'pusher/me?id='+id+'&name='+name
            });
        },

        /**
         * Get a list of all active connections
         **/
        getConnections: function(){
            var deferred = $q.defer();
            $http({
                    method: 'GET',
                    cache: false,
                    url: urlBase+'pusher/connections'
				})
                .success(function( response ){
                    deferred.resolve( response );
                })
                .error(function( response ){
					NotifyService.error( response.error.message );
                    deferred.reject( response.error.message );
                });
            return deferred.promise;
        }
	};

    return service;
});
<commit_msg>Enforced refreshing<commit_after>'use strict';

angular.module('spotmop.services.pusher', [
])

.factory("PusherService", function($rootScope, $http, $q, $localStorage, $cacheFactory, $templateCache, SettingsService, NotifyService){

	// make sure we have a local storage container
	if( typeof( $localStorage.pusher ) === 'undefined' )
		$localStorage.pusher = {};

	// build the endpoint string
	var mopidyhost = SettingsService.getSetting("mopidy.host");
	if( !mopidyhost ) mopidyhost = window.location.hostname;
	var mopidyport = SettingsService.getSetting("mopidy.port");
	if( !mopidyport ) mopidyport = "6680";

	var urlBase = 'http://'+ mopidyhost +':'+ mopidyport +'/spotmop/';

	var service = {
		pusher: {},

		isConnected: false,

		start: function(){
            var self = this;

            // Get mopidy/pusher ip and port from settigns
			var pusherhost = SettingsService.getSetting("mopidy.host");
			if( !pusherhost ) pusherhost = window.location.hostname;
			var pusherport = SettingsService.getSetting("pusher.port");
			if( !pusherport ) pusherport = "6681";

            try{
				var host = 'ws://'+pusherhost+':'+pusherport+'/pusher';

                var id = Math.random().toString(36).substr(2, 9);
                SettingsService.setSetting('pusher.id', id);
                var name = "User";
                if( SettingsService.getSetting('pusher.name') ) name = encodeURI(SettingsService.getSetting('pusher.name'));

				var pusher = new WebSocket(host, id+'_'+name );

				pusher.onopen = function(){
					$rootScope.$broadcast('spotmop:pusher:online');
					this.isConnected = true;
				}

				pusher.onmessage = function( response ){

					var message = JSON.parse(response.data);
					console.log(message);

                    $rootScope.$broadcast('spotmop:pusher:'+message.type, message.data);

					switch( message.type ){

						// initial connection status message, just parse it through quietly
						case 'client_connected':

							// if the new connection is mine
							if( message.data.id == SettingsService.getSetting('pusher.id') ){
								console.info('Pusher connection '+message.data.id+' accepted');

								// detect if the core has been updated
								if( message.data.version != SettingsService.getSetting('version.installed') ){
									NotifyService.notify('New version detected, clearing caches...');
									$cacheFactory.get('$http').removeAll();
									$templateCache.removeAll();
									SettingsService.setSetting('version.installed', message.data.version);
									SettingsService.runUpgrade();
								}
							}
							break;

						case 'notification':

							// respect notifications disabled setting
							if( !SettingsService.getSetting('pusher.disabled') ){
                                var title = '';
                                var body = '';
                                var icon = '';
                                if( typeof(message.data.title) !== 'undefined' ) title = message.data.title;
                                if( typeof(message.data.body) !== 'undefined' ) body = message.data.body;
                                if( typeof(message.data.icon) !== 'undefined' ) icon = message.data.icon;
								NotifyService.browserNotify( title, body, icon );
							}
							break;

						case 'enforced_refresh':
							location.reload();
							NotifyService.notify('System updating...');
							$cacheFactory.get('$http').removeAll();
							$templateCache.removeAll();
							break;
					}
				}

				pusher.onclose = function(){
					$rootScope.$broadcast('spotmop:pusher:offline');
					service.isConnected = false;
                    setTimeout(function(){ service.start() }, 5000);
				}

				service.pusher = pusher;
            }catch(e){
                // need to re-initiate notifier
				console.log( "Connecting with Pusher failed with the following error message: " + e);
            }
		},

		stop: function() {
			this.pusher = null;
			this.isConnected = false;
		},

		send: function( data ){

			// make sure we have a recipients array, even if empty
			if( typeof(data.recipients) === 'undefined' ) data.recipients = [];

            // set the origin of this notification as the current client/connection
			var name = SettingsService.getSetting('pusher.name');
			if( !name ) name = 'User';
			data.origin = {
				ip: SettingsService.getSetting('pusher.ip'),
				id: SettingsService.getSetting('pusher.id'),
				name: name
			};

            // send off the notification to the websocket
			service.pusher.send( JSON.stringify(data) );
		},

        /**
         * Notify the Pusher service of our name
         * @param name (string)
         * @return deferred promise
         **/
        setMe: function( name ){
            var id = SettingsService.getSetting('pusher.id');
            $.ajax({
                method: 'GET',
                cache: false,
                url: urlBase+'pusher/me?id='+id+'&name='+name
            });
        },

        /**
         * Get a list of all active connections
         **/
        getConnections: function(){
            var deferred = $q.defer();
            $http({
                    method: 'GET',
                    cache: false,
                    url: urlBase+'pusher/connections'
				})
                .success(function( response ){
                    deferred.resolve( response );
                })
                .error(function( response ){
					NotifyService.error( response.error.message );
                    deferred.reject( response.error.message );
                });
            return deferred.promise;
        }
	};

    return service;
});
<|endoftext|>


<commit_before>/*
 * Copyright 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

addScriptToInject(function() {

chrome_comp.CompDetect.declareDetector(

'shrink_to_fit_percentage_child',

chrome_comp.CompDetect.ScanDomBaseDetector,

null, // constructor

function checkNode(node, context) {
  if (Node.ELEMENT_NODE != node.nodeType || context.isDisplayNone())
    return;

  var isShrinkToFit = context.getValueInBlockStack('isShrinkToFit');
  if (isShrinkToFit === undefined) {
    var block = context.getParentBlock();
    if (block) {
      isShrinkToFit = chrome_comp.isShrinkToFit(block, true);
      context.putValueInBlockStack('isShrinkToFit', isShrinkToFit);
    }
  }
  if (!isShrinkToFit)
    return;

  // This algorithm only applies to non-replaced elements.
  if (chrome_comp.isReplacedElement(node))
    return;

  var style = chrome_comp.getComputedStyle(node);
  if (style.display == 'inline')
    return;
  var position = style.position;
  if (position == 'fixed' || position == 'absolute')
    return;

  var width = chrome_comp.getDefinedStylePropertyByName(node, false, 'width');
  if (width && width.match(/[0-9]+\%$/) && parseInt(width) > 0)
    this.addProblem('RX8017', [node]);
}
); // declareDetector

});
<commit_msg>fixed by luyuan<commit_after>/*
 * Copyright 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

addScriptToInject(function() {

chrome_comp.CompDetect.declareDetector(

'shrink_to_fit_percentage_child',

chrome_comp.CompDetect.ScanDomBaseDetector,

null, // constructor

function checkNode(node, context) {

  function isAutoWidth(element) {
    var inlineDisplay = element.style.display;
    element.style.display = 'none !important';
    var width = chrome_comp.getComputedStyle(element).width;
    element.style.display = null;
    element.style.display = (inlineDisplay) ? inlineDisplay : null;
    return width == 'auto';
  }

  function isShrinkToFit(element) {
    if (!isAutoWidth(element))
      return false;
    var style = chrome_comp.getComputedStyle(element);
    if ((style.float == 'none') && (style.display != 'inline-block') &&
        (style.position == 'static' || style.position == 'relative'))
      return false;
    return true;
  }

  function isPercentageWidth(element) {
    var inlineDisplay = element.style.display;
    element.style.display = 'none !important';
    var width = chrome_comp.getComputedStyle(element).width;
    element.style.display = null;
    element.style.display = (inlineDisplay) ? inlineDisplay : null;
    return width.substr(-1) == '%';
  }

  function getAllPercentageWidthDescendant(element) {
    var ch = element.children;
    var desceList = [];
    for (var i = 0, j = ch.length; i < j; i++) {
      if (isPercentageWidth(ch[i]))
        desceList.push(ch[i]);
    }
    return desceList;
  }

  if (Node.ELEMENT_NODE != node.nodeType || context.isDisplayNone())
    return;

  if (!isShrinkToFit(node))
    return;

  var descendantList = getAllPercentageWidthDescendant(node);
  if (descendantList.length < 1)
    return;

  for (var i = 0, j = descendantList.length; i < j; i++) {
    var style = chrome_comp.getComputedStyle(descendantList[i]);
    if (style.display == 'inline')
      continue;
    var position = style.position;
    if (position == 'fixed' || position == 'absolute')
      continue;
    var oldWidth = chrome_comp.getComputedStyle(descendantList[i]).width;
    var inlineWidth = descendantList[i].style.width;
    descendantList[i].style.width = 'auto !important';
    var newWidth = chrome_comp.getComputedStyle(descendantList[i]).width;
    descendantList[i].style.width = null;
    descendantList[i].style.width = (inlineWidth) ? inlineWidth : null;
    if (oldWidth != newWidth) {
      this.addProblem('RX8017', [descendantList[i]]);
    }
  }
}
); // declareDetector

});
<|endoftext|>


<commit_before>'use strict';

var _ = require('underscore');
var Backbone = require('backbone');

Backbone.Router.extend = function () {
  var child = _.extend.apply(this, arguments),
    childProto = child.prototype,
    parentProto = this.prototype;

  _.each(['before', 'after'], function (filter) {
    _.defaults(childProto[filter], parentProto[filter]);
  });

  return child;
};

_.extend(Backbone.Router.prototype, {

  /**
   * Override default route fn to call before/after filters
   *
   * @param {String} route
   * @param {String} name
   * @param {Function} [callback]
   * @return {*}
   */
  route: function (route, name, callback) {

    if (!_.isRegExp(route)) {
      route = this._routeToRegExp(route);
    }

    if (_.isFunction(name)) {
      callback = name;
      name = '';
    }

    if (!callback) {
      callback = this[name];
    }

    var router = this;

    // store all the before and after routes in a stack
    var beforeStack = [];
    var afterStack = [];

    _.each(router.before, function (value, key) {
      beforeStack.push({
        'filter': key,
        'filterFn': value
      });
    });

    _.each(router.after, function (value, key) {
      afterStack.push({
        'filter': key,
        'filterFn': value
      });
    });

    Backbone.history.route(route, function (fragment) {
      var args = router._extractParameters(route, fragment);

      var beforeStackClone = _.clone(beforeStack);
      var afterStackClone = _.clone(afterStack);

      function next(stack, runRoute) {
        var layer = stack.shift();

        if (layer) {
          var filter = _.isRegExp(layer.filter) ? layer.filter : router._routeToRegExp(layer.filter);

          if (filter.test(fragment)) {
            var fn = _.isFunction(layer.filterFn) ? layer.filterFn : router[layer.filterFn];

            fn.apply(router, [
                fragment,
                args,
                function () {
                  next(stack, runRoute);
                }
              ]);
          } else {
            next(stack, runRoute);
          }
        } else if (runRoute) {
          callback.apply(router, args);
        }
      }

      // start with top of the before stack
      next(beforeStackClone, true);

      router.trigger.apply(router, ['route:' + name].concat(args));
      router.trigger('route', name, args);

      Backbone.history.trigger('route', router, name, args);

      next(afterStackClone);

    });

    return this;
  }

});

module.exports = Backbone;
<commit_msg>[fix] remove custom extend<commit_after>'use strict';

var _ = require('underscore');
var Backbone = require('backbone');

_.extend(Backbone.Router.prototype, {

  /**
   * Override default route fn to call before/after filters
   *
   * @param {String} route
   * @param {String} name
   * @param {Function} [callback]
   * @return {*}
   */
  route: function (route, name, callback) {

    if (!_.isRegExp(route)) {
      route = this._routeToRegExp(route);
    }

    if (_.isFunction(name)) {
      callback = name;
      name = '';
    }

    if (!callback) {
      callback = this[name];
    }

    var router = this;

    // store all the before and after routes in a stack
    var beforeStack = [];
    var afterStack = [];

    _.each(router.before, function (value, key) {
      beforeStack.push({
        'filter': key,
        'filterFn': value
      });
    });

    _.each(router.after, function (value, key) {
      afterStack.push({
        'filter': key,
        'filterFn': value
      });
    });

    Backbone.history.route(route, function (fragment) {
      var args = router._extractParameters(route, fragment);

      var beforeStackClone = _.clone(beforeStack);
      var afterStackClone = _.clone(afterStack);

      function next(stack, runRoute) {
        var layer = stack.shift();

        if (layer) {
          var filter = _.isRegExp(layer.filter) ? layer.filter : router._routeToRegExp(layer.filter);

          if (filter.test(fragment)) {
            var fn = _.isFunction(layer.filterFn) ? layer.filterFn : router[layer.filterFn];

            fn.apply(router, [
                fragment,
                args,
                function () {
                  next(stack, runRoute);
                }
              ]);
          } else {
            next(stack, runRoute);
          }
        } else if (runRoute) {
          callback.apply(router, args);
        }
      }

      // start with top of the before stack
      next(beforeStackClone, true);

      router.trigger.apply(router, ['route:' + name].concat(args));
      router.trigger('route', name, args);

      Backbone.history.trigger('route', router, name, args);

      next(afterStackClone);

    });

    return this;
  }

});

module.exports = Backbone;
<|endoftext|>


<commit_before>const images = require("../../modules/images/main");
module.exports = {
	process: async message => {
		let { buffer, ext } = await images(message.args[0]);
		return ["", {
			file: buffer,
			name: `byemom.${ext}`
		}];
	},
	description: "Create a \"ok bye mom\" meme",
	args: [{ type: "text" }]
};
<commit_msg>fix byemom<commit_after>const images = require("../../modules/images/main");
module.exports = {
	process: async message => {
		let { buffer, ext } = await images.byemom(message.args[0]);
		return ["", {
			file: buffer,
			name: `byemom.${ext}`
		}];
	},
	description: "Create a \"ok bye mom\" meme",
	args: [{ type: "text" }]
};
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */
foam.CLASS({
  package: 'foam.nanos.crunch.ui',
  name: 'CapabilityWizardlet',
  extends: 'foam.u2.wizard.BaseWizardlet',

  imports: [
    'crunchController'
  ],

  requires: [
    'foam.core.Action',
    'foam.core.Property',
    'foam.layout.Section',
    'foam.layout.SectionAxiom'
  ],

  properties: [
    // Properties specific to CapabilityWizardSection
    {
      name: 'capability'
    },
    {
      name: 'ucj'
    },

    // Properties for WizardSection interface
    {
      name: 'of',
      class: 'Class',
      expression: function (capability) {
        return capability.of;
      }
    },
    {
      name: 'data',
      factory: function () {
        if ( ! this.of ) return null;

        var ret = this.of.create({}, this);
        if ( this.ucj === null ) return ret;

        ret = Object.assign(ret, this.ucj.data);
        return ret;
      }
    },
    {
      name: 'title',
      expression: function(capability) {
        return capability.name;
      }
    }
  ],

  methods: [
    {
      name: 'save',
      code: function() {
        return this.crunchController.save(this);
      }
    }
  ]
});
<commit_msg>updated data factory so that capability is set for wizardlet data<commit_after>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */
foam.CLASS({
  package: 'foam.nanos.crunch.ui',
  name: 'CapabilityWizardlet',
  extends: 'foam.u2.wizard.BaseWizardlet',

  imports: [
    'crunchController'
  ],

  requires: [
    'foam.core.Action',
    'foam.core.Property',
    'foam.layout.Section',
    'foam.layout.SectionAxiom'
  ],

  properties: [
    // Properties specific to CapabilityWizardSection
    {
      name: 'capability'
    },
    {
      name: 'ucj'
    },

    // Properties for WizardSection interface
    {
      name: 'of',
      class: 'Class',
      expression: function (capability) {
        return capability.of;
      }
    },
    {
      name: 'data',
      factory: function () {
        if ( ! this.of ) return null;

        var ret = this.of.create({ capability: this.capability }, this);
        if ( this.ucj === null ) return ret;

        ret = Object.assign(ret, this.ucj.data);
        return ret;
      }
    },
    {
      name: 'title',
      expression: function(capability) {
        return capability.name;
      }
    }
  ],

  methods: [
    {
      name: 'save',
      code: function() {
        return this.crunchController.save(this);
      }
    }
  ]
});
<|endoftext|>