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

/**
 * entry point for your application, makes some assumptions, (e.g. that you
 * always want fullscreen) and shortcuts some fancy stuff like a gradient
 * background.
 *
 * @class Application
 * @extends GOWN.Control
 * @memberof GOWN
 * @constructor
 * @param config {Object} - equals the renderer config for pixi with an
 *  exception: the backgroundColor is an Array a of colors it will drawn as
 *  vertical gradient
 *  (default: {backgroundColor: 0xffffff})
 * @param fullscreen {Boolean}
 *  (default: true)
 * @param renderer {WebGLRenderer|CanvasRenderer}
 *  (default: null - will create a new renderer)
 * @param stage {Stage}
 *  (default null - will use a new PIXI.Container)
 */
function Application(config, fullscreen, renderer, stage) {
    var width = 800;
    var height = 600;
    if (fullscreen) {
        width = window.innerWidth;
        height = window.innerHeight;
    }

    if (!config) {
        config = {
            backgroundColor: 0xffffff
        };
    }

    var _background; // to store background if it is an array because we want
                     // to set the backgroundColor in config to a hex value
    if (!stage || !renderer) {
        stage = new PIXI.Container();
        if (config.backgroundColor && config.backgroundColor instanceof Array) {
            _background = config.backgroundColor;
            config.backgroundColor = 0xffffff;
        }
        this._background = config.backgroundColor;
        renderer = PIXI.autoDetectRenderer(width, height, config);
        document.body.appendChild(renderer.view);
    }
    /* jshint ignore:start */
    this._stage = stage;
    this._renderer = renderer;
    /* jshint ignore:end */
    this._width = renderer.width;
    this._height = renderer.height;

    Control.call(this);
    stage.addChild(this);

    if (_background) {
        this.background = _background;
    }
    this.fullscreen = fullscreen === undefined || fullscreen;

    this.animate();
}

Application.prototype = Object.create( Control.prototype );
Application.prototype.constructor = Application;
module.exports = Application;

/**
 * call requestAnimationFrame to render the application at max. FPS
 *
 * @method animate
 */
/* jshint ignore:start */
Application.prototype.animate = function() {
    var scope = this;
    var animate = function() {
        if (scope._stage) {
            scope._renderer.render(scope._stage);
            requestAnimationFrame(animate);
        }
    };
    requestAnimationFrame(animate);
};
/* jshint ignore:end */

/**
 * creates a gradient rect that can be used as background
 * (uses a separate canvas to create a new Texture)
 *
 * @method _createGradientRect
 * @private
 */
Application.prototype._createGradientRect = function(gradient, width, height) {
    var bgCanvas = document.createElement('canvas');
    bgCanvas.width = width || 256;
    bgCanvas.height = height || 256;
    var ctx = bgCanvas.getContext('2d');
    var linearGradient = ctx.createLinearGradient(0, 0, 0, bgCanvas.height);
    for (var i = 0; i < gradient.length; i++) {
        var color = gradient[i];
        if (typeof(color) === 'number') {
            color = '#' +  gradient[i].toString(16);
        }
        linearGradient.addColorStop(i, color);
    }
    ctx.fillStyle = linearGradient;
    ctx.fillRect(0, 0, bgCanvas.width, bgCanvas.height);
    return PIXI.Texture.fromCanvas(bgCanvas);
};

/**
 * clean application: remove event listener, free memory
 * (can also remove the canvas from the DOM tree if wanted)
 *
 * @method destroy
 * @param [destroyChildren=false] {boolean} if set to true, all the children will have their destroy method called as well
 * @param [removeCanvas=true] {boolean} destroys the canvas and remove it from the dom tree
 */
Application.prototype.destroy = function(destroyChildren, removeCanvas) {
    removeCanvas = removeCanvas === undefined || removeCanvas;
    this._removeBackground();
    this.fullscreen = false; // remove event listener on resize using setter
    PIXI.Container.prototype.destroy.call(this, destroyChildren);
    if (removeCanvas) {
        document.body.removeChild(this._renderer.view);
    }
    this._stage = null;
    this._renderer = null;
};

/**
 * called when the browser window / the application is resized
 *
 * @method onresize
 */
Application.prototype.onresize = function() {
    this._width = window.innerWidth;
    this._height = window.innerHeight;
    this._renderer.resize(this._width, this._height);
    if (this.bg) {
        this.bg.width = this._width;
        this.bg.height = this._height;
    }
    this.emit('resize', this._width, this._height);
};

/**
 * remove background
 * @method _removeBackground
 * @private
 */
Application.prototype._removeBackground = function() {
    if (this.bg) {
        this.removeChild(this.bg);
        this.bg = null;
    }
};

/**
 * set fullscreen and resize to screen size
 *
 * @property enabled
 * @type Boolean
 */
Object.defineProperty(Application.prototype, 'fullscreen', {
    get: function() {
        return this._fullscreen;
    },
    set: function(value) {
        if (this._fullscreen && !value) {
            window.removeEventListener('resize', this._onresize);
        } else if (!this._fullscreen && value) {
            this._renderer.view.style.top = 0;
            this._renderer.view.style.left = 0;
            this._renderer.view.style.right = 0;
            this._renderer.view.style.bottom = 0;
            this._renderer.view.style.position = 'absolute';
            this._onresize = this.onresize.bind(this);
            window.addEventListener('resize', this._onresize);
        }
        this._fullscreen = value;
    }
});

/**
 * set and draw background
 *
 * @property enabled
 * @type Boolean
 */
Object.defineProperty(Application.prototype, 'background', {
    get: function() {
        return this._background;
    },
    set: function(value) {
        if (value === this._background) {
            return;
        }
        this._removeBackground();
        if (value instanceof Array) {
            this.bg = new PIXI.Sprite(this._createGradientRect(value));
            this.bg.width = this._width;
            this.bg.height = this._height;
            this.addChildAt(this.bg, 0);
        } else {
            this._renderer.backgroundColor = value;
        }
        this._background = value;
    }
});
<commit_msg>Application listens to resize event from ResizeManager<commit_after>var Control = require('../core/Control');

/**
 * entry point for your application, makes some assumptions, (e.g. that you
 * always want fullscreen) and shortcuts some fancy stuff like a gradient
 * background.
 *
 * @class Application
 * @extends GOWN.Control
 * @memberof GOWN
 * @constructor
 * @param config {Object} - equals the renderer config for pixi with an
 *  exception: the backgroundColor is an Array a of colors it will drawn as
 *  vertical gradient
 *  (default: {backgroundColor: 0xffffff})
 * @param fullscreen {Boolean}
 *  (default: true)
 * @param width {Number} width of the canvas
 *  (default: 800)
 * @param height {Number} height of the canvas
 *  (default: 600)
 * @param renderer {WebGLRenderer|CanvasRenderer}
 *  (default: null - will create a new renderer)
 * @param stage {Stage}
 *  (default null - will use a new PIXI.Container)
 */
function Application(config, fullscreen, width, height, renderer, stage) {
    fullscreen = fullscreen === undefined ? true : fullscreen;
    if (fullscreen) {
        width = window.innerWidth;
        height = window.innerHeight;
    } else {
        width = width || 800;
        height = height || 600;
    }

    this.resizable = true;
    this.on('resize', this.onResize, this);

    if (!config) {
        config = {
            backgroundColor: 0xffffff
        };
    }

    var _background; // to store background if it is an array because we want
                     // to set the backgroundColor in config to a hex value
    if (!stage || !renderer) {
        stage = new PIXI.Container();
        if (config.backgroundColor && config.backgroundColor instanceof Array) {
            _background = config.backgroundColor;
            config.backgroundColor = 0xffffff;
        }
        this._background = config.backgroundColor;
        renderer = PIXI.autoDetectRenderer(width, height, config);
        document.body.appendChild(renderer.view);
    }
    /* jshint ignore:start */
    this._stage = stage;
    this._renderer = renderer;
    /* jshint ignore:end */
    this._width = renderer.width;
    this._height = renderer.height;

    Control.call(this);
    stage.addChild(this);

    if (_background) {
        this.background = _background;
    }
    this.fullscreen = fullscreen === undefined || fullscreen;

    this.animate();
}

Application.prototype = Object.create( Control.prototype );
Application.prototype.constructor = Application;
module.exports = Application;

/**
 * call requestAnimationFrame to render the application at max. FPS
 *
 * @method animate
 */
/* jshint ignore:start */
Application.prototype.animate = function() {
    var scope = this;
    var animate = function() {
        if (scope._stage) {
            scope._renderer.render(scope._stage);
            requestAnimationFrame(animate);
        }
    };
    requestAnimationFrame(animate);
};
/* jshint ignore:end */

/**
 * creates a gradient rect that can be used as background
 * (uses a separate canvas to create a new Texture)
 *
 * @method _createGradientRect
 * @private
 */
Application.prototype._createGradientRect = function(gradient, width, height) {
    var bgCanvas = document.createElement('canvas');
    bgCanvas.width = width || 256;
    bgCanvas.height = height || 256;
    var ctx = bgCanvas.getContext('2d');
    var linearGradient = ctx.createLinearGradient(0, 0, 0, bgCanvas.height);
    for (var i = 0; i < gradient.length; i++) {
        var color = gradient[i];
        if (typeof(color) === 'number') {
            color = '#' +  gradient[i].toString(16);
        }
        linearGradient.addColorStop(i, color);
    }
    ctx.fillStyle = linearGradient;
    ctx.fillRect(0, 0, bgCanvas.width, bgCanvas.height);
    return PIXI.Texture.fromCanvas(bgCanvas);
};

/**
 * clean application: remove event listener, free memory
 * (can also remove the canvas from the DOM tree if wanted)
 *
 * @method destroy
 * @param [destroyChildren=false] {boolean} if set to true, all the children will have their destroy method called as well
 * @param [removeCanvas=true] {boolean} destroys the canvas and remove it from the dom tree
 */
Application.prototype.destroy = function(destroyChildren, removeCanvas) {
    removeCanvas = removeCanvas === undefined || removeCanvas;
    this._removeBackground();
    PIXI.Container.prototype.destroy.call(this, destroyChildren);
    if (removeCanvas) {
        document.body.removeChild(this._renderer.view);
    }
    this._stage = null;
    this._renderer = null;
};

/**
 * called when the browser window / the application is resized
 *
 * @method resize
 */
Application.prototype.onResize = function(eventData) {
    this._width = eventData.data.width;
    this._height = eventData.data.height;
    this._renderer.resize(this._width, this._height);
    if (this.bg) {
        this.bg.width = this._width;
        this.bg.height = this._height;
    }
};

/**
 * remove background
 * @method _removeBackground
 * @private
 */
Application.prototype._removeBackground = function() {
    if (this.bg) {
        this.removeChild(this.bg);
        this.bg = null;
    }
};

/**
 * set fullscreen and resize to screen size
 *
 * @property enabled
 * @type Boolean
 */
Object.defineProperty(Application.prototype, 'fullscreen', {
    get: function() {
        return this._fullscreen;
    },
    set: function(value) {
        if (!this._fullscreen && value) {
            this._renderer.view.style.top = 0;
            this._renderer.view.style.left = 0;
            this._renderer.view.style.right = 0;
            this._renderer.view.style.bottom = 0;
            this._renderer.view.style.position = 'absolute';
        }
        this._fullscreen = value;
    }
});

/**
 * set and draw background
 *
 * @property enabled
 * @type Boolean
 */
Object.defineProperty(Application.prototype, 'background', {
    get: function() {
        return this._background;
    },
    set: function(value) {
        if (value === this._background) {
            return;
        }
        this._removeBackground();
        if (value instanceof Array) {
            this.bg = new PIXI.Sprite(this._createGradientRect(value));
            this.bg.width = this._width;
            this.bg.height = this._height;
            this.addChildAt(this.bg, 0);
        } else {
            this._renderer.backgroundColor = value;
        }
        this._background = value;
    }
});
<|endoftext|>


<commit_before>var pkg = require('./package.json');
/*
 To configure this file further, please see http://
 */

//use the following as defaults and remove the details from this config file?
//create a defualt.config.js in caddy that this merges from?
//javascriptsDir: ['js','scripts','javascript','javascripts']
//stylesheetsDir: ['css','sass','scss','styles','stylesheets']
//karmaConfigs: ['karma.config.js','karma.functional.js','karma.unit.js']
//unitCoverage:'coverage/summary.json'
//staticApp:{
//    server: { baseDir : '_site' }, //todo: use `target`
//    port: 3456 //todo: make this dynamic
//}
//
//module.exports = {
//    pkg: pkg,
//    paths: {
//        source: "./src",
//        demo: "./demo", //supporting code used to demonstrate
//        target: './_site',
//        tests: 'test'
//    },
//    copy: ['fonts', 'images'],
//    build: ['sass', 'mustache', 'browserify'],
//    test: 'karma',
//    release: ['git'],
//    serve: 'staticApp'
//};

module.exports = {
    build: ['fonts', 'images', 'sass', 'mustache', 'browserify', 'html-min'], //plus 'requirejs', 'jade'
    test: 'karma', // or false. mocha not yet available.
    release: ['git', 'gh-pages', 's3', 'bower'], // ['git', 'gh-pages','s3'] or false.
    serve: 'staticApp', // `staticApp` or `nodeApp`
    browserify: {
        insertGlobals : true,
        detectGlobals : false
    },
    karma:{
        functional: './test/karma.functional.js', // string or false. Karma config file.
        unit: './test/karma.unit.js', // string or false. Karma config file with coverage setup.
        unitCoverage: './test/coverage/summary.json'// code coverage summary for unit tests
    },
    s3: { // add your aws release config here.
        bucket: process.env.YOUR_AWS_BUCKET,
        region: process.env.YOUR_AWS_REGION,
        profile: pkg.name, // profile to be used in ~/.aws/credentials
        target: pkg.name + '/' + pkg.version + '/' //target release destination i.e. 'components/'
    },
    staticApp:{
        server: { baseDir : '_site' }, // '_site' or ['_site','bower_component'] etc using 'browserSync' api
        port: 3456 //todo: make this dynamic
    },
    paths: {
        /*
         All paths also have `script`, `styles`, `fonts`, `icons` and `images` properties
         Feel free to specify a custom path i.e.  add `scripts: './src/js'`
         */
        source: { //source files to build your component / site
            root: "./src"
        },
        "demo": { // files used to demo the source code or an accompanying site.
            root: "./demo"
        },
        "site": { // Final build (Compiled demo + source) code pushed to your chosen release cloud i.e. AWS
            root: './_site'
        }
    },
    pkg: pkg
};<commit_msg>update default caddy config for new config setup<commit_after>var pkg = require('./package.json');
/*
 To configure this file further, please see http://
 */

//use the following as defaults and remove the details from this config file?
//create a defualt.config.js in caddy that this merges from?
//javascriptsDir: ['js','scripts','javascript','javascripts']
//stylesheetsDir: ['css','sass','scss','styles','stylesheets']
//karmaConfigs: ['karma.config.js','karma.functional.js','karma.unit.js']
//unitCoverage:'coverage/summary.json'
//staticApp:{
//    server: { baseDir : '_site' }, //todo: use `target`
//    port: 3456 //todo: make this dynamic
//}
//
//module.exports = {
//    pkg: pkg,
//    paths: {
//        source: "./src",
//        demo: "./demo", //supporting code used to demonstrate
//        target: './_site',
//        tests: 'test'
//    },
//    copy: ['fonts', 'images'],
//    build: ['sass', 'mustache', 'browserify'],
//    test: 'karma',
//    release: ['git'],
//    serve: 'staticApp'
//};

module.exports = {
    pkg: pkg,
    paths: {
        source: "./src",
        "demo": "./demo",
        "target": './_site'
    },
    copy: ['fonts', 'images', 'server-config'],
    build: ['sass', 'mustache', 'browserify', 'html-min'], //plus 'requirejs', 'jade'
    test: 'karma', // or false. mocha not yet available.
    release: ['git', 'gh-pages', 's3', 'bower'], // ['git', 'gh-pages','s3'] or false.
    serve: 'staticApp', // `staticApp` or `nodeApp`
    browserify: {
        insertGlobals : true,
        detectGlobals : false
    },
    karma:{
        functional: './test/karma.functional.js', // string or false. Karma config file.
        unit: './test/karma.unit.js', // string or false. Karma config file with coverage setup.
        unitCoverage: './test/coverage/summary.json'// code coverage summary for unit tests
    },
    s3: { // add your aws release config here.
        bucket: process.env.YOUR_AWS_BUCKET,
        region: process.env.YOUR_AWS_REGION,
        profile: pkg.name, // profile to be used in ~/.aws/credentials
        target: pkg.name + '/' + pkg.version + '/' //target release destination i.e. 'components/'
    },
    staticApp:{
        server: { baseDir : '_site' }, // '_site' or ['_site','bower_component'] etc using 'browserSync' api
        port: 3456 //todo: make this dynamic
    }
};<|endoftext|>


<commit_before>//
//   Copyright 2014 Ilkka Oksanen <iao@iki.fi>
//
//   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.
//

'use strict';

var redis = require('./redis').createClient(),
    log = require('./log'),
    outbox = require('./outbox');

var roleMap = {
    OWNER: '*',
    OPER: '@',
    VOICE: '+',
    USER: 'u'
};

var MSG_BUFFER_SIZE = 200;

// TBD: split to conversation.js and conversationMembers.js

exports.create = function*(options) {
    var conversationId = yield redis.incr('nextGlobalConversationId');

    yield redis.hmset('conversation:' + conversationId, options);

    if (options.type === 'group') {
        // Update group index
        yield redis.hset('index:conversation',
            'group:' + options.network + ':' + options.name, conversationId);
    }

    log.info('Created conversation: ' + conversationId);
    return conversationId;
};

exports.get = function*(conversationId) {
    return yield redis.hgetall('conversation:' + conversationId);
};

exports.findGroup = function*(name, network) {
    var conversationId = yield redis.hget('index:conversation', 'group:' + network + ':' + name);
    return conversationId;
};

exports.find1on1 = function*(userId1, userId2, network) {
    var userIds = [ userId1, userId2 ].sort();
    var conversationId = yield redis.hget('index:conversation',
        '1on1:' + network + ':' + userIds[0] + ':' + userIds[1]);

    return conversationId;
};

exports.set1on1Members = function*(conversationId, userId1, userId2) {
    var network = yield redis.hget('conversation:' + conversationId, 'network');
    var userIds = [ userId1, userId2 ].sort();

    yield insertMember(conversationId, userId1, 'USER');
    yield insertMember(conversationId, userId2, 'USER');

    // Update 1on1 index
    yield redis.hset('index:conversation',
        '1on1:' + network + ':' + userIds[0] + ':' + userIds[1], conversationId);
};

exports.addGroupMember = function*(conversationId, userId, role) {
    yield addMessage(conversationId, 0, {
        userId: userId,
        cat: 'join',
        body: ''
    });

    yield streamAddMembers(conversationId, userId, role);
    yield insertMember(conversationId, userId, role); // Must be last
};

exports.removeGroupMember = function*(conversationId, userId) {
    yield addMessage(conversationId, 0, {
        userId: userId,
        cat: 'part',
        body: ''
    });

    yield streamRemoveMembers(conversationId, userId);
    yield removeMember(conversationId, userId);
};

exports.addMessage = function*(conversationId, excludeSession, msg) {
    yield addMessage(conversationId, excludeSession, msg);
};

exports.sendAddMembers = function*(userId, conversationId) {
    var members = yield redis.hgetall('conversationmembers:' + conversationId);
    var membersList = [];

    Object.keys(members).forEach(function(key) {
        membersList.push({
            userId: key,
            role: members[key]
        });
    });

    yield stream(conversationId, 0, {
        id: 'ADDMEMBERS',
        reset: true,
        members: membersList
    });
};

function *addMessage(conversationId, excludeSession, msg) {
    msg.gid = yield redis.incr('nextGlobalMsgId');
    msg.ts = Math.round(Date.now() / 1000);

    yield redis.lpush('conversationmsgs:' + conversationId, JSON.stringify(msg));
    yield redis.ltrim('conversationmsgs:' + conversationId, 0, MSG_BUFFER_SIZE - 1);

    yield streamAddText(conversationId, excludeSession, msg);
}

function *streamAddText(conversationId, excludeSession, msg) {
    msg.id = 'ADDTEXT';
    yield stream(conversationId, excludeSession, msg);
}

function *streamAddMembers(conversationId, userId, role) {
    yield stream(conversationId, 0, {
        id: 'ADDMEMBERS',
        reset: false,
        members: [ {
            userId: userId,
            role: role
        } ]
    });
}

function *streamRemoveMembers(conversationId, userId) {
    yield stream(conversationId, 0, {
        id: 'DELMEMBERS',
        members: [ {
            userId: userId
        } ]
    });
}

function *stream(conversationId, excludeSession, msg) {
    var members = yield getMembers(conversationId);

    for (var i = 0; i < members.length; i++) {
        var windowId = yield redis.hget('index:windowIds', members[i] + ':' + conversationId);
        msg.windowId = parseInt(windowId);

        yield outbox.queueAll(members[i], msg);
    }
}

function *insertMember(conversationId, userId, role) {
    yield redis.hset('conversationmembers:' + conversationId, userId, roleMap[role]);
}

function *removeMember(conversationId, userId) {
    yield redis.hdel('conversationmembers:' + conversationId, userId);
}

function *getMembers(conversationId) {
    return yield redis.hkeys('conversationmembers:' + conversationId);
}
<commit_msg>Fix sendAddMembers()<commit_after>//
//   Copyright 2014 Ilkka Oksanen <iao@iki.fi>
//
//   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.
//

'use strict';

var redis = require('./redis').createClient(),
    log = require('./log'),
    outbox = require('./outbox');

var roleMap = {
    OWNER: '*',
    OPER: '@',
    VOICE: '+',
    USER: 'u'
};

var MSG_BUFFER_SIZE = 200;

// TBD: split to conversation.js and conversationMembers.js

exports.create = function*(options) {
    var conversationId = yield redis.incr('nextGlobalConversationId');

    yield redis.hmset('conversation:' + conversationId, options);

    if (options.type === 'group') {
        // Update group index
        yield redis.hset('index:conversation',
            'group:' + options.network + ':' + options.name, conversationId);
    }

    log.info('Created conversation: ' + conversationId);
    return conversationId;
};

exports.get = function*(conversationId) {
    return yield redis.hgetall('conversation:' + conversationId);
};

exports.findGroup = function*(name, network) {
    var conversationId = yield redis.hget('index:conversation', 'group:' + network + ':' + name);
    return conversationId;
};

exports.find1on1 = function*(userId1, userId2, network) {
    var userIds = [ userId1, userId2 ].sort();
    var conversationId = yield redis.hget('index:conversation',
        '1on1:' + network + ':' + userIds[0] + ':' + userIds[1]);

    return conversationId;
};

exports.set1on1Members = function*(conversationId, userId1, userId2) {
    var network = yield redis.hget('conversation:' + conversationId, 'network');
    var userIds = [ userId1, userId2 ].sort();

    yield insertMember(conversationId, userId1, 'USER');
    yield insertMember(conversationId, userId2, 'USER');

    // Update 1on1 index
    yield redis.hset('index:conversation',
        '1on1:' + network + ':' + userIds[0] + ':' + userIds[1], conversationId);
};

exports.addGroupMember = function*(conversationId, userId, role) {
    yield addMessage(conversationId, 0, {
        userId: userId,
        cat: 'join',
        body: ''
    });

    yield streamAddMembers(conversationId, userId, role);
    yield insertMember(conversationId, userId, role); // Must be last
};

exports.removeGroupMember = function*(conversationId, userId) {
    yield addMessage(conversationId, 0, {
        userId: userId,
        cat: 'part',
        body: ''
    });

    yield streamRemoveMembers(conversationId, userId);
    yield removeMember(conversationId, userId);
};

exports.addMessage = function*(conversationId, excludeSession, msg) {
    yield addMessage(conversationId, excludeSession, msg);
};

exports.sendAddMembers = function*(userId, windowId, conversationId) {
    var members = yield redis.hgetall('conversationmembers:' + conversationId);
    var membersList = [];

    Object.keys(members).forEach(function(key) {
        membersList.push({
            userId: key,
            role: members[key]
        });
    });

    yield outbox.queueAll(userId, {
        id: 'ADDMEMBERS',
        windowId: windowId,
        reset: true,
        members: membersList
    });
};

function *addMessage(conversationId, excludeSession, msg) {
    msg.gid = yield redis.incr('nextGlobalMsgId');
    msg.ts = Math.round(Date.now() / 1000);

    yield redis.lpush('conversationmsgs:' + conversationId, JSON.stringify(msg));
    yield redis.ltrim('conversationmsgs:' + conversationId, 0, MSG_BUFFER_SIZE - 1);

    yield streamAddText(conversationId, excludeSession, msg);
}

function *streamAddText(conversationId, excludeSession, msg) {
    msg.id = 'ADDTEXT';
    yield stream(conversationId, excludeSession, msg);
}

function *streamAddMembers(conversationId, userId, role) {
    yield stream(conversationId, 0, {
        id: 'ADDMEMBERS',
        reset: false,
        members: [ {
            userId: userId,
            role: role
        } ]
    });
}

function *streamRemoveMembers(conversationId, userId) {
    yield stream(conversationId, 0, {
        id: 'DELMEMBERS',
        members: [ {
            userId: userId
        } ]
    });
}

function *stream(conversationId, excludeSession, msg) {
    var members = yield getMembers(conversationId);

    for (var i = 0; i < members.length; i++) {
        var windowId = yield redis.hget('index:windowIds', members[i] + ':' + conversationId);
        msg.windowId = parseInt(windowId);

        yield outbox.queueAll(members[i], msg);
    }
}

function *insertMember(conversationId, userId, role) {
    yield redis.hset('conversationmembers:' + conversationId, userId, roleMap[role]);
}

function *removeMember(conversationId, userId) {
    yield redis.hdel('conversationmembers:' + conversationId, userId);
}

function *getMembers(conversationId) {
    return yield redis.hkeys('conversationmembers:' + conversationId);
}
<|endoftext|>


<commit_before>Ext.namespace('Ext.Gemma');

Ext.onReady( function() {
	Ext.QuickTips.init();

	var admin = dwr.util.getValue("hasAdmin");

	var searchPanel = new Ext.Gemma.CoexpressionSearchPanel( {
		style : "margin-bottom: 1em;"
	} );
	searchPanel.render( "coexpression-form" );

	var knownGeneDatasetGrid = new Ext.Gemma.CoexpressionDatasetGrid( {
		renderTo : "coexpression-results"
	} );
	var knownGeneGrid = new Ext.Gemma.CoexpressionGrid( {
		renderTo : "coexpression-results",
		title : "Coexpressed genes",
		pageSize : 25
	} );
	var predictedGeneGrid;
	var probeAlignedGrid;
	if ( admin ) {
		predictedGeneGrid = new Ext.Gemma.CoexpressionGrid( {
			renderTo : "coexpression-results",
			title : "Coexpressed predicted genes",
			pageSize : 25,
			collapsed : true
		} );
		probeAlignedGrid = new Ext.Gemma.CoexpressionGrid( {
			renderTo : "coexpression-results",
			title : "Coexpressed probe-aligned regions",
			pageSize : 25,
			collapsed : true
		} );
	}

	searchPanel.processSearchResults = function ( result ) {
		var eeMap = {};
		if ( result.datasets ) {
			for ( var i=0; i<result.datasets.length; ++i ) {
				var ee = result.datasets[i];
				eeMap[ee.id] = ee;
			}
		}

		Ext.Gemma.CoexpressionDatasetGrid.updateDatasetInfo( result.knownGeneDatasets, eeMap );
		knownGeneDatasetGrid.loadData( result.isCannedAnalysis, result.queryGenes.length, result.knownGeneDatasets ) ;
		knownGeneGrid.loadData( result.knownGeneResults );

		if ( admin ) {
			Ext.Gemma.CoexpressionDatasetGrid.updateDatasetInfo( result.predictedGeneDatasets, eeMap );
			//predictedGeneDatasetGrid.loadData( result.isCannedAnalysis, result.queryGenes.length, result.knownGeneDatasets ) ;
			predictedGeneGrid.loadData( result.predictedGeneResults );
			Ext.Gemma.CoexpressionDatasetGrid.updateDatasetInfo( result.probeAlignedRegionDatasets, eeMap );
			//probeAlignedDatasetGrid.loadData( result.isCannedAnalysis, result.queryGenes.length, result.knownGeneDatasets ) ;
			probeAlignedGrid.loadData( result.probeAlignedRegionResults );
		}
	};

} );

/* Ext.Gemma.CoexpressionSearchPanel constructor...
 */
Ext.Gemma.CoexpressionSearchPanel = function ( config ) {

	var thisPanel = this;

	/* establish default config options...
	 */
	var superConfig = {
		autoWidth : true,
		autoHeight : true,
		frame : true,
		defaults : { }
	};

	/* apply user-defined config options and call the superclass constructor...
	 */
	for ( property in config ) {
		superConfig[property] = config[property];
	}
	Ext.Gemma.CoexpressionSearchPanel.superclass.constructor.call( this, superConfig );

	var geneChooserPanel = new Ext.Gemma.GeneChooserPanel( {
		showTaxon : true
	} );
	this.geneChooserPanel = geneChooserPanel;
	this.geneChooserPanel.taxonCombo.on( "taxonchanged", function ( combo, taxon ) {
		this.taxonChanged( taxon );
	}, this );

	var queryFs = new Ext.form.FieldSet( {
		title : 'Query gene(s)',
		autoHeight : true,
		collapsible : true
	} );
	queryFs.add( geneChooserPanel );

	var analysisCombo = new Ext.Gemma.AnalysisCombo( {
		fieldLabel : 'Limit search to',
		showCustomOption : true
	} );
	this.analysisCombo = analysisCombo;
	analysisCombo.on( "analysisChanged", function ( combo, analysis ) {
		if ( analysis ) {
			if ( analysis.id < 0 ) { // custom analysis
				thisPanel.customAnalysis = true;
				customFs.show();
				thisPanel.updateDatasetsToBeSearched( eeSearchField.getEeIds() );
				eeSearchField.findDatasets();
			} else {
				thisPanel.customAnalysis = false;
				customFs.hide();
				thisPanel.taxonChanged( analysis.taxon );
				thisPanel.updateDatasetsToBeSearched( analysis.numDatasets );
			}
		} else {
			thisPanel.customAnalysis = false;
			customFs.hide();
			thisPanel.analysisFs.setTitle( "" );
		}
	} );

	var stringencyField = new Ext.form.NumberField( {
		allowBlank : false,
		allowDecimals : false,
		allowNegative : false,
		fieldLabel : 'Stringency',
		validator : function ( value ) { return value >= Ext.Gemma.CoexpressionSearchPanel.MIN_STRINGENCY; },
		invalidText : "Minimum stringency is " + Ext.Gemma.CoexpressionSearchPanel.MIN_STRINGENCY,
		value : 2,
		width : 25
	} );
	this.stringencyField = stringencyField;

	var eeSearchField = new Ext.Gemma.DatasetSearchField( {
		fieldLabel : "Experiment keywords"
	} );
	this.eeSearchField = eeSearchField;
	this.eeSearchField.on( 'aftersearch', function ( field, results ) {
		if ( thisPanel.customAnalysis ) {
			thisPanel.updateDatasetsToBeSearched( results );
		}
	} );

	var customFs = new Ext.form.FieldSet( {
		title : 'Custom analysis options',
		hidden : true
	} );
	customFs.add( eeSearchField );

	var analysisFs = new Ext.form.FieldSet( {
		title : 'Analysis options',
		autoHeight : true,
		collapsible : true,
		defaults : {
			labelStyle : 'white-space: nowrap'
		},
		labelWidth : 150,
	} );
	this.analysisFs = analysisFs;
	analysisFs.add( stringencyField );
	analysisFs.add( analysisCombo );
	analysisFs.add( customFs );

	var submitButton = new Ext.Button( {
		text : "Find coexpressed genes",
		handler : function() {
			thisPanel.doSearch.call( thisPanel );
		}
	} );

	this.add( queryFs );
	this.add( analysisFs );
	this.addButton( submitButton );

};

Ext.Gemma.CoexpressionSearchPanel.MIN_STRINGENCY = 2;

/* other public methods...
 */
Ext.extend( Ext.Gemma.CoexpressionSearchPanel, Ext.FormPanel, {

	initComponent : function() {
        Ext.Gemma.CoexpressionSearchPanel.superclass.initComponent.call(this);

        this.addEvents(
            'beforesearch',
            'aftersearch'
        );
    },

	onRender : function ( ct, position ) {
		Ext.Gemma.CoexpressionSearchPanel.superclass.onRender.apply(this, arguments);

		if ( ! this.loadMask ) {
			this.createLoadMask();
		}
	},

	createLoadMask : function () {
		this.loadMask = new Ext.LoadMask( this.getEl() );
		this.eeSearchField.loadMask = this.loadMask;
	},

	doSearch : function () {
		/* validate the search here */
		var csc = this.getCoexpressionSearchCommand();
		var msg = this.validateSearch( csc )
		if ( msg.length == 0 ) {
			this.loadMask.show();
			ExtCoexpressionSearchController.doSearch( csc, this.returnFromSearch.bind( this ) );
		} else {
			Ext.MessageBox.show( {
				msg: msg,
				icon: Ext.MessageBox.ERROR
			} );
		}
	},

	validateSearch : function ( csc ) {
		if ( csc.geneIds.length < 1 ) {
			return "Please select at least one query gene";
		} else if ( csc.stringency < Ext.Gemma.CoexpressionSearchPanel.MIN_STRINGENCY ) {
			return "Minimum stringency is " + Ext.Gemma.CoexpressionSearchPanel.MIN_STRINGENCY;
		} else if ( csc.eeIds && csc.eeIds.length < 1 ) {
			return "There are no datasets that match your search terms";
		} else if ( ! csc.cannedAnalysisId ) {
			return "Please select an analysis";
		}
	},

	getCoexpressionSearchCommand : function () {
		var csc = {
			geneIds : this.geneChooserPanel.getGeneIds(),
			stringency : this.stringencyField.getValue()
		};
		var analysisId = this.analysisCombo.getValue();
		if ( analysisId < 0 ) {
			csc.eeIds = this.eeSearchField.getEeIds();
		} else {
			csc.cannedAnalysisId = analysisId;
		}
		return csc;
	},

	returnFromSearch : function ( result ) {
		this.processSearchResults( result );
		this.loadMask.hide();
	},

	processSearchResults : function ( result ) {
		// this is going to be overridden in the main method above; this should be cleaned up...
	},

	updateDatasetsToBeSearched : function ( datasets ) {
		var numDatasets = datasets instanceof Array ? datasets.length : datasets;
		this.analysisFs.setTitle( String.format( "Analysis options ({0} dataset{1} will be analyzed)", numDatasets, numDatasets != 1 ? "s" : "" ) );
	},

	taxonChanged : function ( taxon ) {
		this.analysisCombo.taxonChanged( taxon );
		this.eeSearchField.taxonChanged( taxon, this.analysisCombo.selectedIndex >= 0 );
		this.geneChooserPanel.taxonChanged( taxon );
	}

} );

/* Ext.Gemma.DatasetSearchField constructor...
 */
Ext.Gemma.DatasetSearchField = function ( config ) {

	this.loadMask = config.loadMask; delete config.loadMask;
	this.eeIds = [];

	Ext.Gemma.DatasetSearchField.superclass.constructor.call( this, config );

	this.on( 'beforesearch', function( field, query ) {
		if ( this.loadMask ) {
			this.loadMask.show();
		}
	} );
	this.on( 'aftersearch', function( field, results ) {
		if ( this.loadMask ) {
			this.loadMask.hide();
		}
	} );
};

/* other public methods...
 */
Ext.extend( Ext.Gemma.DatasetSearchField, Ext.form.TextField, {

	initComponent : function() {
        Ext.Gemma.DatasetSearchField.superclass.initComponent.call(this);

        this.addEvents(
            'beforesearch',
            'aftersearch'
        );
    },

	initEvents : function() {
		Ext.Gemma.DatasetSearchField.superclass.initEvents.call(this);

		var queryTask = new Ext.util.DelayedTask( this.findDatasets, this );
		this.el.on( "keyup", function( e ) { queryTask.delay( 500 ) } );
	},

	findDatasets : function () {
		var params = [ this.getValue(), this.taxon ? this.taxon.id : -1 ];
		if ( params == this.lastParams ) {
			return;
		}
		if ( this.fireEvent('beforesearch', this, params ) !== false ) {
			this.lastParams = params;
			ExtCoexpressionSearchController.findExpressionExperiments( params[0], params[1], this.foundDatasets.bind( this ) );
        }
	},

	foundDatasets : function ( results ) {
		this.eeIds = results;
		this.fireEvent( 'aftersearch', this, results );
	},

	getEeIds : function () {
		return this.eeIds;
	},

	taxonChanged : function ( taxon, doSearch ) {
		this.taxon = taxon;
		if ( doSearch ) {
			this.findDatasets();
		}
	}

} );

/* Ext.Gemma.AnalysisCombo constructor...
 */
Ext.Gemma.AnalysisCombo = function ( config ) {
	Ext.QuickTips.init();

	this.showCustomOption = config.showCustomOption; delete config.showCustomOption;

	/* establish default config options...
	 */
	var superConfig = {
		displayField : 'name',
		valueField : 'id',
		editable : false,
		lazyInit : false,
		lazyRender : false,
		mode : 'local',
		selectOnFocus : true,
		triggerAction : 'all',
		store : new Ext.data.Store( {
			proxy : new Ext.data.DWRProxy( ExtCoexpressionSearchController.getCannedAnalyses ),
			reader : new Ext.data.ListRangeReader( {id:"id"}, Ext.Gemma.AnalysisCombo.getRecord() ),
			remoteSort : true
		} ),
		tpl : Ext.Gemma.AnalysisCombo.getTemplate()
	};
	var options = { params : [] };
	if ( this.showCustomOption ) {
		options.callback = function () {
			var constructor = Ext.Gemma.AnalysisCombo.getRecord();
			var record = new constructor( {
				id : -1,
				name : "Custom analysis",
				description : "Select specific datasets to search against"
			} );
			superConfig.store.add( record );
		};
	}
	superConfig.store.load( options );

	/* apply user-defined config options and call the superclass constructor...
	 */
	for ( var property in config ) {
		superConfig[property] = config[property];
	}
	Ext.Gemma.AnalysisCombo.superclass.constructor.call( this, superConfig );

	// call doQuery or the record filtering done in taxonChanged() below doesn't work...
	this.doQuery();
};

/* static methods
 */
Ext.Gemma.AnalysisCombo.getRecord = function() {
	if ( Ext.Gemma.AnalysisCombo.record === undefined ) {
		Ext.Gemma.AnalysisCombo.record = Ext.data.Record.create( [
			{ name: "id", type: "int" },
			{ name: "name", type: "string" },
			{ name: "description", type: "string" },
			{ name: "taxon" },
			{ name: "numDatasets", type: "int" }
		] );
	}
	return Ext.Gemma.AnalysisCombo.record;
};

Ext.Gemma.AnalysisCombo.getTemplate = function() {
	if ( Ext.Gemma.AnalysisCombo.template === undefined ) {
		Ext.Gemma.AnalysisCombo.template = new Ext.XTemplate(
			'<tpl for="."><div ext:qtip="{description}" class="x-combo-list-item">{name}{[ values.taxon ? " (" + values.taxon.scientificName + ")" : "" ]}</tpl></div></tpl>'
		);
	}
	return Ext.Gemma.AnalysisCombo.template;
};

/* other public methods...
 */
Ext.extend( Ext.Gemma.AnalysisCombo, Ext.form.ComboBox, {

	initComponent : function() {
        Ext.Gemma.AnalysisCombo.superclass.initComponent.call(this);

        this.addEvents(
            'analysischanged'
        );
    },

	onSelect : function ( record, index ) {
		Ext.Gemma.AnalysisCombo.superclass.onSelect.call( this, record, index );

		if ( record.data != this.selectedAnalysis ) {
			this.selectedAnalysis = record.data;
			this.fireEvent( 'analysischanged', this, this.selectedAnalysis );
		}
	},

	reset : function() {
		Ext.Gemma.AnalysisCombo.superclass.reset.call(this);

		if ( this.selectedAnalysis != null ) {
			this.selectedAnalysis = null;
			this.fireEvent( 'analysischanged', this, this.selectedAnalysis );
		}
	},

	taxonChanged : function ( taxon ) {
		if ( this.selectedAnalysis && this.selectedAnalysis.taxon.id != taxon.id ) {
			this.reset();
		}
		this.store.filterBy( function( record, id ) {
			if ( ! record.data.taxon ) {
				return true;
			} else if ( record.data.taxon.id == taxon.id ) {
				return true;
			}
		} );
	}

} );

<commit_msg>Fixed a tiny but critical bug.<commit_after>Ext.namespace('Ext.Gemma');

Ext.onReady( function() {
	Ext.QuickTips.init();

	var admin = dwr.util.getValue("hasAdmin");

	var searchPanel = new Ext.Gemma.CoexpressionSearchPanel( {
		style : "margin-bottom: 1em;"
	} );
	searchPanel.render( "coexpression-form" );

	var knownGeneDatasetGrid = new Ext.Gemma.CoexpressionDatasetGrid( {
		renderTo : "coexpression-results"
	} );
	var knownGeneGrid = new Ext.Gemma.CoexpressionGrid( {
		renderTo : "coexpression-results",
		title : "Coexpressed genes",
		pageSize : 25
	} );
	var predictedGeneGrid;
	var probeAlignedGrid;
	if ( admin ) {
		predictedGeneGrid = new Ext.Gemma.CoexpressionGrid( {
			renderTo : "coexpression-results",
			title : "Coexpressed predicted genes",
			pageSize : 25,
			collapsed : true
		} );
		probeAlignedGrid = new Ext.Gemma.CoexpressionGrid( {
			renderTo : "coexpression-results",
			title : "Coexpressed probe-aligned regions",
			pageSize : 25,
			collapsed : true
		} );
	}

	searchPanel.processSearchResults = function ( result ) {
		var eeMap = {};
		if ( result.datasets ) {
			for ( var i=0; i<result.datasets.length; ++i ) {
				var ee = result.datasets[i];
				eeMap[ee.id] = ee;
			}
		}

		Ext.Gemma.CoexpressionDatasetGrid.updateDatasetInfo( result.knownGeneDatasets, eeMap );
		knownGeneDatasetGrid.loadData( result.isCannedAnalysis, result.queryGenes.length, result.knownGeneDatasets ) ;
		knownGeneGrid.loadData( result.knownGeneResults );

		if ( admin ) {
			Ext.Gemma.CoexpressionDatasetGrid.updateDatasetInfo( result.predictedGeneDatasets, eeMap );
			//predictedGeneDatasetGrid.loadData( result.isCannedAnalysis, result.queryGenes.length, result.knownGeneDatasets ) ;
			predictedGeneGrid.loadData( result.predictedGeneResults );
			Ext.Gemma.CoexpressionDatasetGrid.updateDatasetInfo( result.probeAlignedRegionDatasets, eeMap );
			//probeAlignedDatasetGrid.loadData( result.isCannedAnalysis, result.queryGenes.length, result.knownGeneDatasets ) ;
			probeAlignedGrid.loadData( result.probeAlignedRegionResults );
		}
	};

} );

/* Ext.Gemma.CoexpressionSearchPanel constructor...
 */
Ext.Gemma.CoexpressionSearchPanel = function ( config ) {

	var thisPanel = this;

	/* establish default config options...
	 */
	var superConfig = {
		autoWidth : true,
		autoHeight : true,
		frame : true,
		defaults : { }
	};

	/* apply user-defined config options and call the superclass constructor...
	 */
	for ( property in config ) {
		superConfig[property] = config[property];
	}
	Ext.Gemma.CoexpressionSearchPanel.superclass.constructor.call( this, superConfig );

	var geneChooserPanel = new Ext.Gemma.GeneChooserPanel( {
		showTaxon : true
	} );
	this.geneChooserPanel = geneChooserPanel;
	this.geneChooserPanel.taxonCombo.on( "taxonchanged", function ( combo, taxon ) {
		this.taxonChanged( taxon );
	}, this );

	var queryFs = new Ext.form.FieldSet( {
		title : 'Query gene(s)',
		autoHeight : true,
		collapsible : true
	} );
	queryFs.add( geneChooserPanel );

	var analysisCombo = new Ext.Gemma.AnalysisCombo( {
		fieldLabel : 'Limit search to',
		showCustomOption : true
	} );
	this.analysisCombo = analysisCombo;
	analysisCombo.on( "analysisChanged", function ( combo, analysis ) {
		if ( analysis ) {
			if ( analysis.id < 0 ) { // custom analysis
				thisPanel.customAnalysis = true;
				customFs.show();
				thisPanel.updateDatasetsToBeSearched( eeSearchField.getEeIds() );
				eeSearchField.findDatasets();
			} else {
				thisPanel.customAnalysis = false;
				customFs.hide();
				thisPanel.taxonChanged( analysis.taxon );
				thisPanel.updateDatasetsToBeSearched( analysis.numDatasets );
			}
		} else {
			thisPanel.customAnalysis = false;
			customFs.hide();
			thisPanel.analysisFs.setTitle( "" );
		}
	} );

	var stringencyField = new Ext.form.NumberField( {
		allowBlank : false,
		allowDecimals : false,
		allowNegative : false,
		fieldLabel : 'Stringency',
		validator : function ( value ) { return value >= Ext.Gemma.CoexpressionSearchPanel.MIN_STRINGENCY; },
		invalidText : "Minimum stringency is " + Ext.Gemma.CoexpressionSearchPanel.MIN_STRINGENCY,
		value : 2,
		width : 25
	} );
	this.stringencyField = stringencyField;

	var eeSearchField = new Ext.Gemma.DatasetSearchField( {
		fieldLabel : "Experiment keywords"
	} );
	this.eeSearchField = eeSearchField;
	this.eeSearchField.on( 'aftersearch', function ( field, results ) {
		if ( thisPanel.customAnalysis ) {
			thisPanel.updateDatasetsToBeSearched( results );
		}
	} );

	var customFs = new Ext.form.FieldSet( {
		title : 'Custom analysis options',
		hidden : true
	} );
	customFs.add( eeSearchField );

	var analysisFs = new Ext.form.FieldSet( {
		title : 'Analysis options',
		autoHeight : true,
		collapsible : true,
		defaults : {
			labelStyle : 'white-space: nowrap'
		},
		labelWidth : 150,
	} );
	this.analysisFs = analysisFs;
	analysisFs.add( stringencyField );
	analysisFs.add( analysisCombo );
	analysisFs.add( customFs );

	var submitButton = new Ext.Button( {
		text : "Find coexpressed genes",
		handler : function() {
			thisPanel.doSearch.call( thisPanel );
		}
	} );

	this.add( queryFs );
	this.add( analysisFs );
	this.addButton( submitButton );

};

Ext.Gemma.CoexpressionSearchPanel.MIN_STRINGENCY = 2;

/* other public methods...
 */
Ext.extend( Ext.Gemma.CoexpressionSearchPanel, Ext.FormPanel, {

	initComponent : function() {
        Ext.Gemma.CoexpressionSearchPanel.superclass.initComponent.call(this);

        this.addEvents(
            'beforesearch',
            'aftersearch'
        );
    },

	onRender : function ( ct, position ) {
		Ext.Gemma.CoexpressionSearchPanel.superclass.onRender.apply(this, arguments);

		if ( ! this.loadMask ) {
			this.createLoadMask();
		}
	},

	createLoadMask : function () {
		this.loadMask = new Ext.LoadMask( this.getEl() );
		this.eeSearchField.loadMask = this.loadMask;
	},

	doSearch : function () {
		/* validate the search here */
		var csc = this.getCoexpressionSearchCommand();
		var msg = this.validateSearch( csc )
		if ( msg.length == 0 ) {
			this.loadMask.show();
			ExtCoexpressionSearchController.doSearch( csc, this.returnFromSearch.bind( this ) );
		} else {
			Ext.MessageBox.show( {
				msg: msg,
				icon: Ext.MessageBox.ERROR
			} );
		}
	},

	validateSearch : function ( csc ) {
		if ( csc.geneIds.length < 1 ) {
			return "Please select at least one query gene";
		} else if ( csc.stringency < Ext.Gemma.CoexpressionSearchPanel.MIN_STRINGENCY ) {
			return "Minimum stringency is " + Ext.Gemma.CoexpressionSearchPanel.MIN_STRINGENCY;
		} else if ( csc.eeIds && csc.eeIds.length < 1 ) {
			return "There are no datasets that match your search terms";
		} else if ( ! csc.cannedAnalysisId ) {
			return "Please select an analysis";
		} else {
			return "";
		}
	},

	getCoexpressionSearchCommand : function () {
		var csc = {
			geneIds : this.geneChooserPanel.getGeneIds(),
			stringency : this.stringencyField.getValue()
		};
		var analysisId = this.analysisCombo.getValue();
		if ( analysisId < 0 ) {
			csc.eeIds = this.eeSearchField.getEeIds();
		} else {
			csc.cannedAnalysisId = analysisId;
		}
		return csc;
	},

	returnFromSearch : function ( result ) {
		this.processSearchResults( result );
		this.loadMask.hide();
	},

	processSearchResults : function ( result ) {
		// this is going to be overridden in the main method above; this should be cleaned up...
	},

	updateDatasetsToBeSearched : function ( datasets ) {
		var numDatasets = datasets instanceof Array ? datasets.length : datasets;
		this.analysisFs.setTitle( String.format( "Analysis options ({0} dataset{1} will be analyzed)", numDatasets, numDatasets != 1 ? "s" : "" ) );
	},

	taxonChanged : function ( taxon ) {
		this.analysisCombo.taxonChanged( taxon );
		this.eeSearchField.taxonChanged( taxon, this.analysisCombo.selectedIndex >= 0 );
		this.geneChooserPanel.taxonChanged( taxon );
	}

} );

/* Ext.Gemma.DatasetSearchField constructor...
 */
Ext.Gemma.DatasetSearchField = function ( config ) {

	this.loadMask = config.loadMask; delete config.loadMask;
	this.eeIds = [];

	Ext.Gemma.DatasetSearchField.superclass.constructor.call( this, config );

	this.on( 'beforesearch', function( field, query ) {
		if ( this.loadMask ) {
			this.loadMask.show();
		}
	} );
	this.on( 'aftersearch', function( field, results ) {
		if ( this.loadMask ) {
			this.loadMask.hide();
		}
	} );
};

/* other public methods...
 */
Ext.extend( Ext.Gemma.DatasetSearchField, Ext.form.TextField, {

	initComponent : function() {
        Ext.Gemma.DatasetSearchField.superclass.initComponent.call(this);

        this.addEvents(
            'beforesearch',
            'aftersearch'
        );
    },

	initEvents : function() {
		Ext.Gemma.DatasetSearchField.superclass.initEvents.call(this);

		var queryTask = new Ext.util.DelayedTask( this.findDatasets, this );
		this.el.on( "keyup", function( e ) { queryTask.delay( 500 ) } );
	},

	findDatasets : function () {
		var params = [ this.getValue(), this.taxon ? this.taxon.id : -1 ];
		if ( params == this.lastParams ) {
			return;
		}
		if ( this.fireEvent('beforesearch', this, params ) !== false ) {
			this.lastParams = params;
			ExtCoexpressionSearchController.findExpressionExperiments( params[0], params[1], this.foundDatasets.bind( this ) );
        }
	},

	foundDatasets : function ( results ) {
		this.eeIds = results;
		this.fireEvent( 'aftersearch', this, results );
	},

	getEeIds : function () {
		return this.eeIds;
	},

	taxonChanged : function ( taxon, doSearch ) {
		this.taxon = taxon;
		if ( doSearch ) {
			this.findDatasets();
		}
	}

} );

/* Ext.Gemma.AnalysisCombo constructor...
 */
Ext.Gemma.AnalysisCombo = function ( config ) {
	Ext.QuickTips.init();

	this.showCustomOption = config.showCustomOption; delete config.showCustomOption;

	/* establish default config options...
	 */
	var superConfig = {
		displayField : 'name',
		valueField : 'id',
		editable : false,
		lazyInit : false,
		lazyRender : false,
		mode : 'local',
		selectOnFocus : true,
		triggerAction : 'all',
		store : new Ext.data.Store( {
			proxy : new Ext.data.DWRProxy( ExtCoexpressionSearchController.getCannedAnalyses ),
			reader : new Ext.data.ListRangeReader( {id:"id"}, Ext.Gemma.AnalysisCombo.getRecord() ),
			remoteSort : true
		} ),
		tpl : Ext.Gemma.AnalysisCombo.getTemplate()
	};
	var options = { params : [] };
	if ( this.showCustomOption ) {
		options.callback = function () {
			var constructor = Ext.Gemma.AnalysisCombo.getRecord();
			var record = new constructor( {
				id : -1,
				name : "Custom analysis",
				description : "Select specific datasets to search against"
			} );
			superConfig.store.add( record );
		};
	}
	superConfig.store.load( options );

	/* apply user-defined config options and call the superclass constructor...
	 */
	for ( var property in config ) {
		superConfig[property] = config[property];
	}
	Ext.Gemma.AnalysisCombo.superclass.constructor.call( this, superConfig );

	// call doQuery or the record filtering done in taxonChanged() below doesn't work...
	this.doQuery();
};

/* static methods
 */
Ext.Gemma.AnalysisCombo.getRecord = function() {
	if ( Ext.Gemma.AnalysisCombo.record === undefined ) {
		Ext.Gemma.AnalysisCombo.record = Ext.data.Record.create( [
			{ name: "id", type: "int" },
			{ name: "name", type: "string" },
			{ name: "description", type: "string" },
			{ name: "taxon" },
			{ name: "numDatasets", type: "int" }
		] );
	}
	return Ext.Gemma.AnalysisCombo.record;
};

Ext.Gemma.AnalysisCombo.getTemplate = function() {
	if ( Ext.Gemma.AnalysisCombo.template === undefined ) {
		Ext.Gemma.AnalysisCombo.template = new Ext.XTemplate(
			'<tpl for="."><div ext:qtip="{description}" class="x-combo-list-item">{name}{[ values.taxon ? " (" + values.taxon.scientificName + ")" : "" ]}</tpl></div></tpl>'
		);
	}
	return Ext.Gemma.AnalysisCombo.template;
};

/* other public methods...
 */
Ext.extend( Ext.Gemma.AnalysisCombo, Ext.form.ComboBox, {

	initComponent : function() {
        Ext.Gemma.AnalysisCombo.superclass.initComponent.call(this);

        this.addEvents(
            'analysischanged'
        );
    },

	onSelect : function ( record, index ) {
		Ext.Gemma.AnalysisCombo.superclass.onSelect.call( this, record, index );

		if ( record.data != this.selectedAnalysis ) {
			this.selectedAnalysis = record.data;
			this.fireEvent( 'analysischanged', this, this.selectedAnalysis );
		}
	},

	reset : function() {
		Ext.Gemma.AnalysisCombo.superclass.reset.call(this);

		if ( this.selectedAnalysis != null ) {
			this.selectedAnalysis = null;
			this.fireEvent( 'analysischanged', this, this.selectedAnalysis );
		}
	},

	taxonChanged : function ( taxon ) {
		if ( this.selectedAnalysis && this.selectedAnalysis.taxon.id != taxon.id ) {
			this.reset();
		}
		this.store.filterBy( function( record, id ) {
			if ( ! record.data.taxon ) {
				return true;
			} else if ( record.data.taxon.id == taxon.id ) {
				return true;
			}
		} );
	}

} );

<|endoftext|>


<commit_before>import * as types from '../constants';
import API from '../utils/api.js';
import logErrorMessage from '../utils/log_error_message';
import request from '../utils/request';

// This action uses the Thunk middleware pattern: instead of returning a plain
// action object, it returns a function that takes the store dispatch fucntion —
// which Thunk automatically provides — and can then dispatch a series of plain
// actions to be handled by the store.
// This is how actions with side effects — such as API calls — are handled in
// Redux.
export function submitBadWorkAlert(data) {
  return function (dispatch) {
    dispatch({ type: types.NEED_HELP_ALERT_SUBMITTED });
    return API.createBadWorkAlert(data)
      .then(() => (dispatch({ type: types.NEED_HELP_ALERT_CREATED })))
      .catch(response => (dispatch({ type: types.API_FAIL, data: response })));
  };
}

export function submitNeedHelpAlert(data) {
  return function (dispatch, getState) {
    // Don't double-submit.
    if (getState().needHelpAlert.submitting) { return; }

    dispatch({ type: types.NEED_HELP_ALERT_SUBMITTED });
    return API.createNeedHelpAlert(data)
      .then(() => (dispatch({ type: types.NEED_HELP_ALERT_CREATED })))
      .catch(response => (dispatch({ type: types.API_FAIL, data: response })));
  };
}

export const resetNeedHelpAlert = () => ({ type: types.RESET_NEED_HELP_ALERT });

const fetchResponseToJSON = (res) => {
  if (res.ok && res.status === 200) {
    return res.json();
  }
  return Promise.reject(res);
};

const resolveAlertPromise = (alertId) => {
  return request(`/alerts/${alertId}/resolve.json`, {
    credentials: 'include'
  }).then(fetchResponseToJSON)
    .catch((error) => {
      logErrorMessage(error);
      return error;
    });
};

export const handleResolveAlert = alertId => (dispatch) => {
  return (
    resolveAlertPromise(alertId)
      .then(() => {
        dispatch({
          type: types.RESOLVE_ALERT,
          alertId
        });
      })
      .catch(response => (dispatch({ type: types.API_FAIL, data: response })))
  );
};

const fetchAdminAlertsPromise = () => {
  return request('/alerts_list.json', {
    credentials: 'include'
  }).then(fetchResponseToJSON)
    .catch((error) => {
      logErrorMessage(error);
      return error;
    });
};

export const fetchAdminAlerts = () => (dispatch) => {
  return (
    fetchAdminAlertsPromise()
      .then((data) => {
        dispatch({
          type: types.RECEIVE_ALERTS,
          data
        });
      })
      .catch(response => (dispatch({ type: types.API_FAIL, data: response })))
  );
};

const fetchCampaignAlertsPromise = (campaignSlug) => {
  return request(`/campaigns/${campaignSlug}/alerts.json`, {
    credentials: 'include'
  }).then(fetchResponseToJSON)
    .catch((error) => {
      logErrorMessage(error);
      return error;
    });
};

export const fetchCampaignAlerts = campaignSlug => (dispatch) => {
  return (
    fetchCampaignAlertsPromise(campaignSlug)
      .then((data) => {
        dispatch({
          type: types.RECEIVE_ALERTS,
          data
        });
      })
      .catch(response => (dispatch({ type: types.API_FAIL, data: response })))
  );
};

export const sortAlerts = key => ({ type: types.SORT_ALERTS, key: key });

export const filterAlerts = selectedFilters => ({ type: types.FILTER_ALERTS, selectedFilters: selectedFilters });
<commit_msg>refactor alert_actions<commit_after>import * as types from '../constants';
import API from '../utils/api.js';
import logErrorMessage from '../utils/log_error_message';
import request from '../utils/request';

// This action uses the Thunk middleware pattern: instead of returning a plain
// action object, it returns a function that takes the store dispatch fucntion —
// which Thunk automatically provides — and can then dispatch a series of plain
// actions to be handled by the store.
// This is how actions with side effects — such as API calls — are handled in
// Redux.
export function submitBadWorkAlert(data) {
  return function (dispatch) {
    dispatch({ type: types.NEED_HELP_ALERT_SUBMITTED });
    return API.createBadWorkAlert(data)
      .then(() => (dispatch({ type: types.NEED_HELP_ALERT_CREATED })))
      .catch(response => (dispatch({ type: types.API_FAIL, data: response })));
  };
}

export function submitNeedHelpAlert(data) {
  return function (dispatch, getState) {
    // Don't double-submit.
    if (getState().needHelpAlert.submitting) { return; }

    dispatch({ type: types.NEED_HELP_ALERT_SUBMITTED });
    return API.createNeedHelpAlert(data)
      .then(() => (dispatch({ type: types.NEED_HELP_ALERT_CREATED })))
      .catch(response => (dispatch({ type: types.API_FAIL, data: response })));
  };
}

export const resetNeedHelpAlert = () => ({ type: types.RESET_NEED_HELP_ALERT });

const fetchResponseToJSON = (res) => {
  if (res.ok && res.status === 200) {
    return res.json();
  }
  return Promise.reject(res);
};

const resolveAlertPromise = (alertId) => {
  return request(`/alerts/${alertId}/resolve.json`, {
    credentials: 'include'
  }).then(fetchResponseToJSON)
    .catch((error) => {
      logErrorMessage(error);
      return error;
    });
};

export const handleResolveAlert = alertId => (dispatch) => {
  return (
    resolveAlertPromise(alertId)
      .then(() => {
        dispatch({
          type: types.RESOLVE_ALERT,
          alertId
        });
      })
      .catch(response => (dispatch({ type: types.API_FAIL, data: response })))
  );
};

const fetchAlertsPromise = (url) => {
  return request(url, {
    credentials: 'include'
  }).then(fetchResponseToJSON)
    .catch((error) => {
      logErrorMessage(error);
      return error;
    });
};

const fetchAdminAlertsPromise = () => fetchAlertsPromise('/alerts_list.json');

const fetchCampaignAlertsPromise = slug => fetchAlertsPromise(`/campaigns/${slug}/alerts.json`);

const receiveAlerts = dispatch => data => dispatch({ type: types.RECEIVE_ALERTS, data });

const apiFail = dispatch => response => dispatch({ type: types.API_FAIL, data: response });

export const fetchAdminAlerts = () => (dispatch) => {
  return (
    fetchAdminAlertsPromise()
      .then(receiveAlerts(dispatch))
      .catch(apiFail(dispatch))
  );
};

export const fetchCampaignAlerts = campaignSlug => (dispatch) => {
  return (
    fetchCampaignAlertsPromise(campaignSlug)
      .then(receiveAlerts(dispatch))
      .catch(apiFail(dispatch))
  );
};

export const sortAlerts = key => ({ type: types.SORT_ALERTS, key: key });

export const filterAlerts = selectedFilters => ({ type: types.FILTER_ALERTS, selectedFilters: selectedFilters });
<|endoftext|>


<commit_before>//= require admin/spree_backend

$(function(){
  console.log('Testing...');
});
<commit_msg>Just dump to Github what I've done.<commit_after>//= require admin/spree_backend
//= require markdown

$(function() {

  $('Live preview').appendTo('#product_description_field');

  $('#product_description_preview_button').click(function(e) {

    if($('#product_description_preview').is(":visible")) {
      $('#product_description_preview').slideUp();
      $('#product_description_preview_button')
        .addClass("icon-eye-close")
        .removeClass("icon-eye-open");
    } else {
      $('#product_description_preview').slideDown();
      $('#product_description_preview_button')
        .removeClass("icon-eye-close")
        .addClass("icon-eye-open");
    }

    e.preventDefault();

  });

  $('#product_description').bind('keyup', function() {
    $('#product_description_preview').html(markdown.toHTML($('#product_description').val()));
  });
});
<|endoftext|>


<commit_before>const bot = require('./../bot');
/**
 * This method should return the response directly to the channel
 * @param {*string array} params
 * @param {*message} message
 */
async function command(params, message) {
    let user = bot.users.random();
    //let ava = bot.user.displayAvatarURL;
    //await bot.user.setAvatar(user.displayAvatarURL);
    await message.guild.me.setNickname(user.username);
    await message.channel.send(params.join(" "));
    await message.guild.me.setNickname(bot.user.username);
    //await bot.user.setAvatar(ava);
}
/**
 * description of the command
 */
const description = "impersonate's some random person";
/**
 * Define Exports
 */
module.exports = {
    execute: command,
    description: description
};
<commit_msg>fix impersonate<commit_after>const bot = require('./../bot');
/**
 * This method should return the response directly to the channel
 * @param {*string array} params
 * @param {*message} message
 */
async function command(params, message) {
    let user = bot.users.random();
    //let ava = bot.user.displayAvatarURL;
    //await bot.user.setAvatar(user.displayAvatarURL);
    await message.guild.me.setNickname(user.username);
    if(params.length<1)
        await message.channel.send("No argument provided",{code:'error'});
    else
        await message.channel.send(params.join(" "));
    await message.guild.me.setNickname(bot.user.username);
    //await bot.user.setAvatar(ava);
}
/**
 * description of the command
 */
const description = "impersonate's some random person";
/**
 * Define Exports
 */
module.exports = {
    execute: command,
    description: description
};
<|endoftext|>


<commit_before>(function($) {
	"use strict";
	/**
	 * {{skin}} skin implementation for the {{component}} module.
	 *
	 * @author {{user}} <{{email}}>
	 * @namespace Tc.Module.{{pattern-js}}
	 * @class {{skin-js}}
	 * @extends Tc.Module
	 */
	Tc.Module.{{component-js}}.{{skin-js}} = function(parent) {

		this.on = function(callback) {
			var mod = this,
				$ctx = mod.$ctx;

			parent.on(callback);
		};

		this.after = function() {
			var mod = this,
				$ctx = mod.$ctx;

			parent.after();
		};

	};
}(Tc.$));
<commit_msg>fix template placeholder<commit_after>(function($) {
	"use strict";
	/**
	 * {{skin}} skin implementation for the {{component}} module.
	 *
	 * @author {{user}} <{{email}}>
	 * @namespace Tc.Module.{{component-js}}
	 * @class {{skin-js}}
	 * @extends Tc.Module
	 */
	Tc.Module.{{component-js}}.{{skin-js}} = function(parent) {

		this.on = function(callback) {
			var mod = this,
				$ctx = mod.$ctx;

			parent.on(callback);
		};

		this.after = function() {
			var mod = this,
				$ctx = mod.$ctx;

			parent.after();
		};

	};
}(Tc.$));
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2016-present, Nicolas Gallagher.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @noflow
 */

let id = 0;
const requests = {};

const ImageLoader = {
  abort(requestId: number) {
    let image = requests[`${requestId}`];
    if (image) {
      image.onerror = image.onload = image = null;
      delete requests[`${requestId}`];
    }
  },
  getSize(uri, success, failure) {
    let complete = false;
    const interval = setInterval(callback, 16);
    const requestId = ImageLoader.load(uri, callback, errorCallback);

    function callback() {
      const image = requests[`${requestId}`];
      if (image) {
        const { naturalHeight, naturalWidth } = image;
        if (naturalHeight && naturalWidth) {
          success(naturalWidth, naturalHeight);
          complete = true;
        }
      }
      if (complete) {
        ImageLoader.abort(requestId);
        clearInterval(interval);
      }
    }

    function errorCallback() {
      if (typeof failure === 'function') {
        failure();
      }
      ImageLoader.abort(requestId);
      clearInterval(interval);
    }
  },
  load(uri, onLoad, onError): number {
    id += 1;
    const image = new window.Image();
    image.onerror = onError;
    image.onload = e => {
      // avoid blocking the main thread
      if (typeof image.decode === 'function') {
        image.decode().then(() => { onLoad(e) });
      } else {
        setTimeout(() => {
          onLoad(e);
        }, 0);
      }
    };
    image.src = uri;
    requests[`${id}`] = image;
    return id;
  },
  prefetch(uri): Promise {
    return new Promise((resolve, reject) => {
      ImageLoader.load(uri, resolve, reject);
    });
  }
};

export default ImageLoader;
<commit_msg>[fix] Image EncodingError for SVG in Safari iOS 11<commit_after>/**
 * Copyright (c) 2016-present, Nicolas Gallagher.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @noflow
 */

let id = 0;
const requests = {};

const ImageLoader = {
  abort(requestId: number) {
    let image = requests[`${requestId}`];
    if (image) {
      image.onerror = image.onload = image = null;
      delete requests[`${requestId}`];
    }
  },
  getSize(uri, success, failure) {
    let complete = false;
    const interval = setInterval(callback, 16);
    const requestId = ImageLoader.load(uri, callback, errorCallback);

    function callback() {
      const image = requests[`${requestId}`];
      if (image) {
        const { naturalHeight, naturalWidth } = image;
        if (naturalHeight && naturalWidth) {
          success(naturalWidth, naturalHeight);
          complete = true;
        }
      }
      if (complete) {
        ImageLoader.abort(requestId);
        clearInterval(interval);
      }
    }

    function errorCallback() {
      if (typeof failure === 'function') {
        failure();
      }
      ImageLoader.abort(requestId);
      clearInterval(interval);
    }
  },
  load(uri, onLoad, onError): number {
    id += 1;
    const image = new window.Image();
    image.onerror = onError;
    image.onload = e => {
      // avoid blocking the main thread
      const onDecode = () => onLoad(e);
      if (typeof image.decode === 'function') {
        // Safari currently throws exceptions when decoding svgs.
        // We want to catch that error and allow the load handler
        // to be forwarded to the onLoad handler in this case
        image.decode().then(onDecode, onDecode);
      } else {
        setTimeout(onDecode, 0);
      }
    };
    image.src = uri;
    requests[`${id}`] = image;
    return id;
  },
  prefetch(uri): Promise {
    return new Promise((resolve, reject) => {
      ImageLoader.load(uri, resolve, reject);
    });
  }
};

export default ImageLoader;
<|endoftext|>


<commit_before>askaway.controller('QuestionsCtrl', ['$scope', '$http', function( $scope, $http ) {
  $scope.loadingQuestions = false;
  $scope.questionsInitialized = false;
  $scope.questionList = [];
  $scope.page = 1;

  $scope.toggleVote = toggleVote($http);

  $scope.shareQuestion = function(e, question) {
    var shareSelector = "#share-question-" + question.id
    if (question.sharesLoaded != true) {
      var twitterSelector = "#share-question-btn-twitter-" + question.id
      var facebookSelector = "#share-question-btn-facebook-" + question.id
      var questionUrl = document.URL + question.path
      var twitterLink = 'Tweet';
      var facebookLink = '

';
      $(twitterSelector).html(twitterLink);
      $(facebookSelector).html(facebookLink);
      twttr.widgets.load();
      FB.XFBML.parse(document.getElementById(shareSelector), function() {
        $(shareSelector + ' .share-question-buttons').removeClass('hide');
        $(shareSelector + ' .loading-spinner').addClass('hide');
      });
      question.sharesLoaded = true;
    }
  }

  $scope.toggleQuestion = function(e) {
    var $target = $(e.target),
      $veto = $target.closest('a[href], form');

    if (window.getSelection && window.getSelection().toString() !== '') return;

    if ($veto.length === 0) {
      this.question.expanded = !this.question.expanded;
    }
  };

  $scope.loadQuestion = function() {
    var me = this;

    $scope.loadingQuestions = true;

    $http.get(me.question.path + '.json').success(function(data) {
      $scope.loadingQuestions = false;

      me.question.error_message = undefined;
      me.question.answers = data.answers;
      me.question.answers_count = data.answers_count;
      me.question.can_answer = data.can_answer;
    });
  };

  // place data into questionList
  function loadData(data) {
    for (var i = 0; i < data.length; i++) {
      $scope.questionList.push(data[i]);
    }

    if (data.length === 0) {
      $scope.noMoreQuestions = true;
    }
  }

  $scope.loadQuestions = function() {
    var url = getUrl(), // always getUrl to advance page count sanely...
      $questions;

    // do initial loading
    if ($scope.questionList.length === 0) {
      $questions = angular.element('#question-data');
      loadData(JSON.parse($questions.html()));
      $questions.remove();
      return;
    } else if ($scope.noMoreQuestions) {
      return;
    }

    $scope.loadingQuestions = true;

    $http.get(url).success(function(data) {
      $scope.loadingQuestions = false;
      loadData(data);

      $scope.questionsInitialized = true;
    });
  };

  function getUrl() {
    var resource = window.location.pathname;
    var args = "&" + window.location.search.replace("?", "");

    if (resource === '/') {
      resource = '/trending';
    }

    return (resource + '.json?page=' + $scope.page++ + args);
  }
}]);

askaway.controller('QuestionCtrl', ['$scope', '$http', function( $scope, $http ) {
  $scope.loadingQuestions = false;
  $scope.question = undefined;

  $scope.toggleVote = toggleVote($http);

  loadQuestion();

  function loadQuestion() {
    var url = window.location.pathname + '.json';

    $scope.loadingQuestions = true;

    $http.get(url)
      .success(function(data) {
        $scope.loadingQuestions = false;
        $scope.question = data;
      });
  }
}]);

askaway.controller( 'QuestionFormCtrl', ['$scope', function( $scope ) {
  $scope.submit = function() {
    $scope.new_question.$setPristine();
  };
}]);

/**
 * Returns a function after the $http provider is in scope
 */
function toggleVote($http) {
  return function() {
    var question = this.question,
      vote_id = question.vote_id;

    if (question.togglingVote) {
      return;
    }

    question.togglingVote = true;

    if (vote_id) {
      // short circuit if we're trying to unvote for something still in progress.
      if (vote_id === 'voting-in-progress') {
        return;
      }

      question.votes_count--;
      question.vote_id = undefined;
      $http({
        method: 'DELETE', // IE8 fail. http://tech.pro/tutorial/1238/angularjs-and-ie8-gotcha-http-delete
        url: '/votes/' + vote_id
      })
        .success(function(vote) {
          question.togglingVote = undefined;
        })
        .error(function(data, status) {
          question.togglingVote = false;
          question.votes_count++;
          question.vote_id = vote_id;
        });
    } else {
      question.votes_count++;
      question.vote_id = 'voting-in-progress';
      $http.post(question.path + '/votes', null)
        .success(function(vote) {
          question.vote_id = vote.id;
          question.togglingVote = undefined;
        })
        .error(function(data, status) {
          question.togglingVote = false;
          question.vote_id = undefined;
          question.votes_count--;
        });
    }
  };
}
<commit_msg>Share buttons: fix up twitter text<commit_after>askaway.controller('QuestionsCtrl', ['$scope', '$http', function( $scope, $http ) {
  $scope.loadingQuestions = false;
  $scope.questionsInitialized = false;
  $scope.questionList = [];
  $scope.page = 1;

  $scope.toggleVote = toggleVote($http);

  $scope.shareQuestion = function(e, question) {
    var shareSelector = "#share-question-" + question.id
    if (question.sharesLoaded != true) {
      var twitterSelector = "#share-question-btn-twitter-" + question.id
      var facebookSelector = "#share-question-btn-facebook-" + question.id
      var questionUrl = document.URL.slice(0, -1) + question.path
      var twitterLink = 'Tweet';
      var facebookLink = '

';
      $(twitterSelector).html(twitterLink);
      $(facebookSelector).html(facebookLink);
      twttr.widgets.load();
      FB.XFBML.parse(document.getElementById(shareSelector), function() {
        $(shareSelector + ' .share-question-buttons').removeClass('hide');
        $(shareSelector + ' .loading-spinner').addClass('hide');
      });
      question.sharesLoaded = true;
    }
  }

  $scope.toggleQuestion = function(e) {
    var $target = $(e.target),
      $veto = $target.closest('a[href], form');

    if (window.getSelection && window.getSelection().toString() !== '') return;

    if ($veto.length === 0) {
      this.question.expanded = !this.question.expanded;
    }
  };

  $scope.loadQuestion = function() {
    var me = this;

    $scope.loadingQuestions = true;

    $http.get(me.question.path + '.json').success(function(data) {
      $scope.loadingQuestions = false;

      me.question.error_message = undefined;
      me.question.answers = data.answers;
      me.question.answers_count = data.answers_count;
      me.question.can_answer = data.can_answer;
    });
  };

  // place data into questionList
  function loadData(data) {
    for (var i = 0; i < data.length; i++) {
      $scope.questionList.push(data[i]);
    }

    if (data.length === 0) {
      $scope.noMoreQuestions = true;
    }
  }

  $scope.loadQuestions = function() {
    var url = getUrl(), // always getUrl to advance page count sanely...
      $questions;

    // do initial loading
    if ($scope.questionList.length === 0) {
      $questions = angular.element('#question-data');
      loadData(JSON.parse($questions.html()));
      $questions.remove();
      return;
    } else if ($scope.noMoreQuestions) {
      return;
    }

    $scope.loadingQuestions = true;

    $http.get(url).success(function(data) {
      $scope.loadingQuestions = false;
      loadData(data);

      $scope.questionsInitialized = true;
    });
  };

  function getUrl() {
    var resource = window.location.pathname;
    var args = "&" + window.location.search.replace("?", "");

    if (resource === '/') {
      resource = '/trending';
    }

    return (resource + '.json?page=' + $scope.page++ + args);
  }
}]);

askaway.controller('QuestionCtrl', ['$scope', '$http', function( $scope, $http ) {
  $scope.loadingQuestions = false;
  $scope.question = undefined;

  $scope.toggleVote = toggleVote($http);

  loadQuestion();

  function loadQuestion() {
    var url = window.location.pathname + '.json';

    $scope.loadingQuestions = true;

    $http.get(url)
      .success(function(data) {
        $scope.loadingQuestions = false;
        $scope.question = data;
      });
  }
}]);

askaway.controller( 'QuestionFormCtrl', ['$scope', function( $scope ) {
  $scope.submit = function() {
    $scope.new_question.$setPristine();
  };
}]);

/**
 * Returns a function after the $http provider is in scope
 */
function toggleVote($http) {
  return function() {
    var question = this.question,
      vote_id = question.vote_id;

    if (question.togglingVote) {
      return;
    }

    question.togglingVote = true;

    if (vote_id) {
      // short circuit if we're trying to unvote for something still in progress.
      if (vote_id === 'voting-in-progress') {
        return;
      }

      question.votes_count--;
      question.vote_id = undefined;
      $http({
        method: 'DELETE', // IE8 fail. http://tech.pro/tutorial/1238/angularjs-and-ie8-gotcha-http-delete
        url: '/votes/' + vote_id
      })
        .success(function(vote) {
          question.togglingVote = undefined;
        })
        .error(function(data, status) {
          question.togglingVote = false;
          question.votes_count++;
          question.vote_id = vote_id;
        });
    } else {
      question.votes_count++;
      question.vote_id = 'voting-in-progress';
      $http.post(question.path + '/votes', null)
        .success(function(vote) {
          question.vote_id = vote.id;
          question.togglingVote = undefined;
        })
        .error(function(data, status) {
          question.togglingVote = false;
          question.vote_id = undefined;
          question.votes_count--;
        });
    }
  };
}
<|endoftext|>


<commit_before>/*global jQuery,cyberDojo*/
'use strict';
var cyberDojo = (function(cd, $) {

  cd.setupTrafficLightTip = ($light, light, kataId, wasIndex, nowIndex) => {
    cd.setTip($light, () => {
      const args = { id:kataId, was_index:wasIndex, now_index:nowIndex };
      $.getJSON('/differ/diff_summary', args, (data) => {
        const diff = data.diff_summary;
        const $tip = $trafficLightTip(light, nowIndex, diff);
        cd.showHoverTip($light, $tip);
      });
    });
  };

  const $trafficLightTip = (light, index, diff) => {
    const $holder = $(document.createDocumentFragment());
    $holder.append($trafficLightSummary(light, index));
    $holder.append($diffLinesTable(diff));
    return $holder;
  };

  const $trafficLightSummary = (light, index) => {
    const $tr = $('
');
    $tr.append($trafficLightIndexTd(light, index));
    $tr.append($trafficLightImageTd(light));
    $tr.append($('').html(miniTextInfo(light)));
    return $('
').append($tr);
  };

  const $trafficLightIndexTd = (light, index) => {
    const $count = $('', {
      class:`traffic-light-count ${light.colour}`
    }).text(index);
    return $('').append($count);
  };

  const $trafficLightImageTd = (light) => {
    const $img = $('', {
        src:`/images/traffic-light/${light.colour}.png`,
      class:'diff-hover-tip'
    });
    return $('').append($img);
  };

  const miniTextInfo = (light) => {
    if (light.colour === 'pulling') {
      return 'image being prepared';
    }
    else if (light.colour === 'faulty') {
      return 'fault! not red, amber, or green';
    }
    else if (cd.lib.hasPrediction(light)) {
      return trafficLightPredictInfo(light);
    }
    else if (cd.lib.isRevert(light)) {
      return trafficLightRevertInfo(light);
    }
    else if (cd.lib.isCheckout(light)) {
      return trafficLightCheckoutInfo(light);
    }
    else {
      return `<span class="${light.colour}">${light.colour}</span>`;
    }
  };

  const trafficLightPredictInfo = (light) => {
    const colour = light.colour
    const predicted = light.predicted;
    return [
       `predicted <span class="${predicted}">${predicted}</span>`,
       `was <span class="${colour}">${colour}</span>`
     ].join(', ');
  };

  const trafficLightRevertInfo = (light) => {
    return `auto-revert back to <span class="${light.colour}">${light.index - 2}</span>`;
  };

  const trafficLightCheckoutInfo = (light) => {
    const name = cd.lib.avatarName(light.checkout.avatarIndex);
    const index = light.checkout.index;
    return `checkout ${name} ${index}`;
  };

  const $diffLinesTable = (diffs) => {
    const $table = $('
', { class:'filenames' });
    const $tr = $('
');
    // column icons
    $tr.append($linesCountIconTd('deleted', '—'));
    $tr.append($linesCountIconTd('added', '+'));
    $tr.append($linesCountIconTd('same', '='));
    $tr.append($(''));
    $tr.append($(''));
    $table.append($tr);
    // cyber-dojo.sh cannot be deleted so there is always at least one file
    const filenames = diffs.map(diff => diffFilename(diff));
    cd.sortedFilenames(filenames).forEach(filename => {
      const fileDiff = diffs.find(diff => diffFilename(diff) === filename);
      const $tr = $('
');
      $tr.append($lineCountTd('deleted', fileDiff));
      $tr.append($lineCountTd('added', fileDiff));
      $tr.append($lineCountTd('same', fileDiff));
      $tr.append($diffTypeTd(fileDiff));
      $tr.append($diffFilenameTd(fileDiff));
      $table.append($tr);
    });
    return $table;
  };

  const $linesCountIconTd = (type, glyph) => {
    const $icon = $('
', {
      class:`diff-line-count-icon ${type}`
    }).html(glyph);
    return $('').append($icon);
  };

  const $lineCountTd = (type, file) => {
    const lineCount = file.line_counts[type];
    const css = lineCount > 0 ? type : '';
    const $count = $('
', {
      class:`diff-line-count ${css}`,
      disabled:'disabled'
    });
    $count.html(lineCount > 0 ? lineCount : ' ');
    return $('').append($count);
  };

  const $diffTypeTd = (diff) => {
    const $type = $('
', {
      class:`diff-type-marker ${diff.type}`
    });
    return $('').append($type);
  };

  const $diffFilenameTd = (diff) => {
    const $filename = $('
', { class:`diff-filename ${diff.type}` });
    $filename.text(diffFilename(diff));
    return $('').append($filename);
  };

  const diffFilename = (diff) => {
    if (diff.type === 'deleted') {
      return diff.old_filename;
    } else {
      return diff.new_filename;
    }
  };

  const hoverTipContainer = () => {
    return $('#hover-tip-container');
  };

  cd.removeTip = ($node) => {
    hoverTipContainer().empty();
  };

  cd.createTip = (node, tip, where) => {
    node.off('mouseenter mouseleave');
    cd.setTip(node, () => {
      cd.showHoverTip(node, tip, where);
    });
  };

  cd.setTip = (node, setTipCallBack) => {
    // The speed of the mouse could easily exceed
    // the speed of the getJSON callback...
    // The mouse-has-left attribute caters for this.
    node.mouseenter(() => {
      node.removeClass('mouse-has-left');
      setTipCallBack(node);
    });
    node.mouseleave(() => {
      node.addClass('mouse-has-left');
      cd.removeTip();
    });
  };

  cd.showHoverTip = (node, tip, where) => {
    if (where === undefined) {
      where = {};
    }
    if (where.my === undefined) { where.my = 'top'; }
    if (where.at === undefined) { where.at = 'bottom'; }
    if (where.of === undefined) { where.of = node; }

    if (!node.attr('disabled')) {
      if (!node.hasClass('mouse-has-left')) {
        // position() is the jQuery UI plug-in
        // https://jqueryui.com/position/
        const hoverTip = $('
', {
          'class': 'hover-tip'
        }).html(tip).position({
          my: where.my,
          at: where.at,
          of: where.of,
          collision: 'fit'
        });
        hoverTipContainer().html(hoverTip);
      }
    }
  };

  return cd;

})(cyberDojo || {}, jQuery);
<commit_msg>Add missing timed-out mini-text to traffic-light hover-tip.<commit_after>/*global jQuery,cyberDojo*/
'use strict';
var cyberDojo = (function(cd, $) {

  cd.setupTrafficLightTip = ($light, light, kataId, wasIndex, nowIndex) => {
    cd.setTip($light, () => {
      const args = { id:kataId, was_index:wasIndex, now_index:nowIndex };
      $.getJSON('/differ/diff_summary', args, (data) => {
        const diff = data.diff_summary;
        const $tip = $trafficLightTip(light, nowIndex, diff);
        cd.showHoverTip($light, $tip);
      });
    });
  };

  const $trafficLightTip = (light, index, diff) => {
    const $holder = $(document.createDocumentFragment());
    $holder.append($trafficLightSummary(light, index));
    $holder.append($diffLinesTable(diff));
    return $holder;
  };

  const $trafficLightSummary = (light, index) => {
    const $tr = $('
');
    $tr.append($trafficLightIndexTd(light, index));
    $tr.append($trafficLightImageTd(light));
    $tr.append($('').html(miniTextInfo(light)));
    return $('
').append($tr);
  };

  const $trafficLightIndexTd = (light, index) => {
    const $count = $('', {
      class:`traffic-light-count ${light.colour}`
    }).text(index);
    return $('').append($count);
  };

  const $trafficLightImageTd = (light) => {
    const $img = $('', {
        src:`/images/traffic-light/${light.colour}.png`,
      class:'diff-hover-tip'
    });
    return $('').append($img);
  };

  const miniTextInfo = (light) => {
    if (light.colour === 'pulling') {
      return 'image being prepared';
    }
    else if (light.colour === 'timed_out') {
      return 'timed out';
    }
    else if (light.colour === 'faulty') {
      return 'fault! not red, amber, or green';
    }
    else if (cd.lib.hasPrediction(light)) {
      return trafficLightPredictInfo(light);
    }
    else if (cd.lib.isRevert(light)) {
      return trafficLightRevertInfo(light);
    }
    else if (cd.lib.isCheckout(light)) {
      return trafficLightCheckoutInfo(light);
    }
    else {
      return spanColour(light.colour);
    }
  };

  const trafficLightPredictInfo = (light) => {
    const colour = light.colour
    const predicted = light.predicted;
    return `predicted ${spanColour(predicted)}, was ${spanColour(colour)}</span>`;
  };

  const trafficLightRevertInfo = (light) => {
    return `auto-revert back to ${spanColour(light.colour, light.index - 2)}</span>`;
  };

  const spanColour = (colour, text = colour) => {
    return `<span class="${colour}">${text}</span>`;
  };

  const trafficLightCheckoutInfo = (light) => {
    const name = cd.lib.avatarName(light.checkout.avatarIndex);
    const index = light.checkout.index;
    return `checkout ${name} ${index}`;
  };

  const $diffLinesTable = (diffs) => {
    const $table = $('
', { class:'filenames' });
    const $tr = $('
');
    // column icons
    $tr.append($linesCountIconTd('deleted', '—'));
    $tr.append($linesCountIconTd('added', '+'));
    $tr.append($linesCountIconTd('same', '='));
    $tr.append($(''));
    $tr.append($(''));
    $table.append($tr);
    // cyber-dojo.sh cannot be deleted so there is always at least one file
    const filenames = diffs.map(diff => diffFilename(diff));
    cd.sortedFilenames(filenames).forEach(filename => {
      const fileDiff = diffs.find(diff => diffFilename(diff) === filename);
      const $tr = $('
');
      $tr.append($lineCountTd('deleted', fileDiff));
      $tr.append($lineCountTd('added', fileDiff));
      $tr.append($lineCountTd('same', fileDiff));
      $tr.append($diffTypeTd(fileDiff));
      $tr.append($diffFilenameTd(fileDiff));
      $table.append($tr);
    });
    return $table;
  };

  const $linesCountIconTd = (type, glyph) => {
    const $icon = $('
', {
      class:`diff-line-count-icon ${type}`
    }).html(glyph);
    return $('').append($icon);
  };

  const $lineCountTd = (type, file) => {
    const lineCount = file.line_counts[type];
    const css = lineCount > 0 ? type : '';
    const $count = $('
', {
      class:`diff-line-count ${css}`,
      disabled:'disabled'
    });
    $count.html(lineCount > 0 ? lineCount : ' ');
    return $('').append($count);
  };

  const $diffTypeTd = (diff) => {
    const $type = $('
', {
      class:`diff-type-marker ${diff.type}`
    });
    return $('').append($type);
  };

  const $diffFilenameTd = (diff) => {
    const $filename = $('
', { class:`diff-filename ${diff.type}` });
    $filename.text(diffFilename(diff));
    return $('').append($filename);
  };

  const diffFilename = (diff) => {
    if (diff.type === 'deleted') {
      return diff.old_filename;
    } else {
      return diff.new_filename;
    }
  };

  const hoverTipContainer = () => {
    return $('#hover-tip-container');
  };

  cd.removeTip = ($node) => {
    hoverTipContainer().empty();
  };

  cd.createTip = (node, tip, where) => {
    node.off('mouseenter mouseleave');
    cd.setTip(node, () => {
      cd.showHoverTip(node, tip, where);
    });
  };

  cd.setTip = (node, setTipCallBack) => {
    // The speed of the mouse could easily exceed
    // the speed of the getJSON callback...
    // The mouse-has-left attribute caters for this.
    node.mouseenter(() => {
      node.removeClass('mouse-has-left');
      setTipCallBack(node);
    });
    node.mouseleave(() => {
      node.addClass('mouse-has-left');
      cd.removeTip();
    });
  };

  cd.showHoverTip = (node, tip, where) => {
    if (where === undefined) {
      where = {};
    }
    if (where.my === undefined) { where.my = 'top'; }
    if (where.at === undefined) { where.at = 'bottom'; }
    if (where.of === undefined) { where.of = node; }

    if (!node.attr('disabled')) {
      if (!node.hasClass('mouse-has-left')) {
        // position() is the jQuery UI plug-in
        // https://jqueryui.com/position/
        const hoverTip = $('
', {
          'class': 'hover-tip'
        }).html(tip).position({
          my: where.my,
          at: where.at,
          of: where.of,
          collision: 'fit'
        });
        hoverTipContainer().html(hoverTip);
      }
    }
  };

  return cd;

})(cyberDojo || {}, jQuery);
<|endoftext|>


<commit_before>const bodyParser = require('body-parser');
const cors = require('cors');
const { Router } = require('express');
const { downloadDocx } = require('../services/download.docx');
const { downloadTxt } = require('../services/download.txt');
const { getIpid } = require('../services/ipid');

const model = require('../model');
const config = require('../config');
const { generateChallenge } = require('../services/challenge');

const router = Router();
router.use(bodyParser.json());
router.use(bodyParser.urlencoded({ extended: true }));
if (config.get('allowCORS')) router.use(cors());

router.post('/rp.json', (req, res, next) => {
    const roomOptions = req.body;
    const ipid = getIpid(req.ip);

    model.createRp(roomOptions, ipid)
        .then(data => res.status(201).json(data))
        .catch(err => next(err));
});

router.get('/challenge.json', (req, res, next) => {
    generateChallenge()
        .then(data => res.status(200).json(data))
        .catch(err => next(err));
});

router.get('/rp/:rpCode([0-9a-zA-Z]+)/page/:pageNum([1-9][0-9]{0,})', (req, res, next) => {
    model.getPage(req.params.rpCode, +req.params.pageNum)
        .then(data => res.status(200).json(data))
        .catch(err => next(err));
});

router.get('/rp/:rpCode([0-9a-zA-Z]+)/download.txt', async (req, res, next) => {
    const { includeOOC } = req.query;

    let rp;
    try {
        ({ rp } = await model.getWholeRp(req.params.rpCode));
    } catch (err) {
        return next(err);
    }

    const body = downloadTxt(rp, { includeOOC });
    return res.attachment(`${rp.title}.txt`).type('.txt').send(body);
});

router.get('/rp/:rpCode([0-9a-zA-Z]+)/download.docx', async (req, res, next) => {
    const { includeOOC } = req.query;

    let rp;
    try {
        ({ rp } = await model.getWholeRp(req.params.rpCode));
    } catch (err) {
        return next(err);
    }

    const body = downloadDocx(rp, { includeOOC });
    return res.attachment(`${rp.title}.docx`).type('.docx').send(body);
});

router.all('*', (req, res, next) => {
    next({ code: 'UNKNOWN_REQUEST' });
});

router.use((err, req, res, next) => {
    res.status(400).json({ error: err });
});

module.exports = router;
<commit_msg>fix REST API for new rpCode style<commit_after>const bodyParser = require('body-parser');
const cors = require('cors');
const { Router } = require('express');
const { downloadDocx } = require('../services/download.docx');
const { downloadTxt } = require('../services/download.txt');
const { getIpid } = require('../services/ipid');

const model = require('../model');
const config = require('../config');
const { generateChallenge } = require('../services/challenge');

const router = Router();
router.use(bodyParser.json());
router.use(bodyParser.urlencoded({ extended: true }));
if (config.get('allowCORS')) router.use(cors());

router.post('/rp.json', (req, res, next) => {
    const roomOptions = req.body;
    const ipid = getIpid(req.ip);

    model.createRp(roomOptions, ipid)
        .then(data => res.status(201).json(data))
        .catch(err => next(err));
});

router.get('/challenge.json', (req, res, next) => {
    generateChallenge()
        .then(data => res.status(200).json(data))
        .catch(err => next(err));
});

router.get('/rp/:rpCode([-0-9a-zA-Z]+)/page/:pageNum([1-9][0-9]{0,})', (req, res, next) => {
    model.getPage(req.params.rpCode, +req.params.pageNum)
        .then(data => res.status(200).json(data))
        .catch(err => next(err));
});

router.get('/rp/:rpCode([-0-9a-zA-Z]+)/download.txt', async (req, res, next) => {
    const { includeOOC } = req.query;

    let rp;
    try {
        ({ rp } = await model.getWholeRp(req.params.rpCode));
    } catch (err) {
        return next(err);
    }

    const body = downloadTxt(rp, { includeOOC });
    return res.attachment(`${rp.title}.txt`).type('.txt').send(body);
});

router.get('/rp/:rpCode([-0-9a-zA-Z]+)/download.docx', async (req, res, next) => {
    const { includeOOC } = req.query;

    let rp;
    try {
        ({ rp } = await model.getWholeRp(req.params.rpCode));
    } catch (err) {
        return next(err);
    }

    const body = downloadDocx(rp, { includeOOC });
    return res.attachment(`${rp.title}.docx`).type('.docx').send(body);
});

router.all('*', (req, res, next) => {
    next({ code: 'UNKNOWN_REQUEST' });
});

router.use((err, req, res, next) => {
    res.status(400).json({ error: err });
});

module.exports = router;
<|endoftext|>