<commit_before>/*!
 * Copyright 2012 Sakai Foundation (SF) Licensed under the
 * Educational Community 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.osedu.org/licenses/ECL-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.
 */

var _ = require('underscore');
var amqp = require('amqp');
var events = require('events');

var log = require('oae-logger').logger('mq');
var OAE = require('./oae');

var connection = null;
var queueExchange = null;
var queues = {};

// Keeps track of how many submitted v.s. processed tasks by name this local node has processed. Intended for synchronization of
// MQ tasks in a single-node (dev) environment.
var submitCount = {};
var emitter = module.exports = new events.EventEmitter();

/**
 * MQ constants.
 *
 *  {String}    DEFAULT_TASK_EXCHANGE_NAME          The name of the AMQP task exchange
 *  {Object}    DEFAULT_TASK_EXCHANGE_OPTS          The default options to specify when creating the AMQP task exchange
 *  {Object}    DEFAULT_TASK_QUEUE_OPTS             The default options to specify when creating the AMQP task queue
 *  {Object}    DEFAULT_TASK_QUEUE_PUBLISH_OPTS     The default options to specify when publishing tasks on the AMQP task queue
 *  {Object}    DEFAULT_TASK_QUEUE_SUBSCRIBE_OPTS   The default options to specify when subscribing to a task queue.
 */
var Constants = module.exports.Constants = {
    'DEFAULT_TASK_EXCHANGE_NAME': 'oae-taskexchange',
    'DEFAULT_TASK_EXCHANGE_OPTS': {
        'type': 'direct',
        'durable': true,
        'autoDelete': false
    },
    'DEFAULT_TASK_QUEUE_OPTS': {
        'durable': true,
        'autoDelete': false
    },
    'DEFAULT_TASK_QUEUE_PUBLISH_OPTS': {
        'deliveryMode': 2                   // 2 indicates 'persistent'
    },
    'DEFAULT_TASK_QUEUE_SUBSCRIBE_OPTS': {
        'ack': true,
        'prefetchCount': 15
    }
};

var deferredTaskHandlers = {};
var ready = false;

OAE.on('ready', function() {
    ready = true;

    var numberToBind = _.keys(deferredTaskHandlers).length;
    var numberBound = 0;
    var returned = false;

    /*!
     * Monitors all the deferred task handlers that have been bound, emitting a 'ready' event
     * when all have been bound.
     */
    var _monitorBinding = function(err) {
        if (returned) {
            // Do nothing, we've called back
            return;
        } else if (err) {
            emitter.emit('ready', err);
            returned = true;
            return;
        }

        numberBound++;
        if (!returned && numberBound >= numberToBind) {
            emitter.emit('ready');
            returned = true;
        }
    };

    if (numberToBind > 0) {
        // Bind all the deferred task handlers now that the container is ready
        _.each(deferredTaskHandlers, function(handlerInfo, taskName) {
            bind(taskName, handlerInfo.listener, handlerInfo.options, _monitorBinding);
            delete deferredTaskHandlers[taskName];
        });
    } else {
        // No deferred task handlers, we're just immediately ready
        emitter.emit('ready');
    }
});

/**
 * Initialize the Message Queue system so that it can start sending and receiving messages.
 *
 * @param   {Object}    mqConfig        The MQ Configuration object
 * @param   {Function}  callback        Invoked when initialization completes
 * @param   {Object}    callback.err    An error that occurred, if any
 */
var init = module.exports.init = function(mqConfig, callback) {
    if (!connection) {
        log().info('Initializing RabbitMQ connector.');

        connection = amqp.createConnection(mqConfig.connection);

        connection.on('error', function(err) {
            log().error({'err': err}, 'Error connecting to RabbitMQ.');
            return callback(err);
        });

        connection.on('close', function() {
            log().info('Closed connection to RabbitMQ');
        });

        connection.on('ready', function() {
            log().info('Connection to RabbitMQ established.');

            // Initialize the exchange for the job queues
            connection.exchange(Constants.DEFAULT_TASK_EXCHANGE_NAME, Constants.DEFAULT_TASK_EXCHANGE_OPTS, function(exchange) {
                queueExchange = exchange;
                return callback();
            });
        });
    } else {
        log().warn('Attempted to initialize an existing RabbitMQ connector. Ignoring');
        callback();
    }
};

/**
 * Bind a function to listen for a particular type of task. When bound, the node effectively becomes a "worker" for that task,
 * meaning when it receives a particular job, no other node active in the cluster will receive it.
 *
 * @param   {String}    name                                The unique label that identifies the type of job this binding is listening to
 * @param   {Function}  listener                            The function that will handle tasks of the given type
 * @param   {Object}    listener.data                       The data that was sent in the job. This is different depending on the type of job
 * @param   {Object}    [options]                           A set of options that can override the default ones.
 * @param   {Object}    [options.queue]                     Holds the options that will override the `Constants.DEFAULT_TASK_QUEUE_OPTS` options.
 * @param   {Object}    [options.subscribe]                 Holds the options that will override the `Constants.DEFAULT_TASK_QUEUE_SUBSCRIBE_OPTS` options.
 * @param   {Number}    [options.subscribe.prefetchCount]   The maximum number of tasks that a listener should handle concurrently.
 * @param   {Function}  [callback]                          Invoked after the listener has been bound to the MQ
 * @param   {Object}    [callback.err]                      An error that occurred binding to the task queue, if any
 */
var bind = module.exports.bind = function(name, listener, options, callback) {
    callback = callback || function(err) { /* error is logged within the implementation */ };
    options = options || {};
    options.queue = options.queue || {};
    options.subscribe = options.subscribe || {};

    if (queues[name] || (!ready && deferredTaskHandlers[name])) {
        log().warn({
            'queueName': name,
            'listener': listener
        }, 'Attempted to bind duplicate listener for job queue. Aborting.');
        return callback();
    }

    // If the container is not ready to handle requests, defer the actual binding of these handlers until later
    if (!ready) {
        log().trace({'name': name}, 'Deferring registration of task handler until server is ready.');
        deferredTaskHandlers[name] = {'listener': listener, 'options': options};
        return callback();
    }

    // Create/get the queue.
    var queueOptions = _.defaults(options.queue, Constants.DEFAULT_TASK_QUEUE_OPTS);
    connection.queue(name, queueOptions, function(queue) {
        queues[name] = {'queue': queue};
        log().info({'queueName': name}, 'Created a RabbitMQ task queue');

        // Bind the queue to the task exchange. The routing key is the same as the name of the job queue
        queue.bind(Constants.DEFAULT_TASK_EXCHANGE_NAME, name, function() {

            // Bind the listener to the queue, to handle all jobs of the given `name` type
            var subscribeOptions = _.defaults(options.subscribe, Constants.DEFAULT_TASK_QUEUE_SUBSCRIBE_OPTS);
            queue.subscribe(subscribeOptions, function(data, headers, deliveryInfo, message) {
                log().trace({
                    'queueName': name,
                    'data': data,
                    'headers': headers
                }, 'Received an MQ message.');

                // Pass it to the listener.
                // We can only shift this element off the queue, once the listener has finished
                try {
                    listener(data, function() {
                        log().trace({
                            'queueName': name,
                            'data': data,
                            'headers': headers
                        }, 'MQ message has been processed by the listener.');

                        _decrementSubmitCount(name);

                        // Acknowledge that we've seen the message.
                        // Note: We can't use queue.shift() as that only acknowledges the last message that the queue handed to us.
                        // This message and the last message are not necessarily the same if the prefetchCount was higher than 1.
                        message.acknowledge();

                    });
                } catch (err) {
                    log().error({
                        'err': err,
                        'queueName': name,
                        'data': data
                    }, 'Exception raised while handling job.');

                    _decrementSubmitCount(name);

                    // Acknowledge that we've seen the message.
                    message.acknowledge();
                }
            }).addCallback(function(ok) {
                if (!ok) {
                    log().error({'err': err, 'queueName': name}, 'Error binding worker for task queue.');
                    _destroyQueue(name, function(err) {
                        // Don't overwrite the original error with any binding errors
                        return callback(new Error('Error binding worker for task queue.'));
                    });
                }

                // Keep the consumerTag so we can unsubscribe later.
                queues[name].consumerTag = ok.consumerTag;
                return callback();
            });
        });
    });
};

/**
 * Unbind this node as a worker for the specified type of task.
 *
 * @param   {String}    name            The name of the task to which to unbind this node
 * @param   {Function}  callback        Invoked when the task has been unbound
 * @param   {Object}    callback.err    An error that occurred, if any
 */
var unbind = module.exports.unbind = function(name, callback) {
    callback = callback || function(err) {
        if (err) {
            log().error({'err': err, 'queueName': name}, 'Error unbinding task queue handler.');
        }
    };
    _destroyQueue(name, callback);
};

/**
 * Submit a job to the task queue to be picked up by some arbitrary node in the cluster.
 *
 * @param   {String}    name        The type of job to submit
 * @param   {Object}    data        The data to send with the task. This will be received by the worker for this type of task
 * @param   {Function}  callback    Invoked when the job has been submitted
 */
var submit = module.exports.submit = function(name, data, callback) {
    callback = callback || function() {};

    _incrementSubmitCount(name);

    // We publish with the routing key `name`, which routes it to the queue (if any) that is listening on this key
    queueExchange.publish(name, data, Constants.DEFAULT_TASK_QUEUE_PUBLISH_OPTS);
    return callback();
};

/**
 * Invoke the given handler only if the local counter of tasks of the given name indicates that the task queue is completely
 * empty. If it is not empty now, then the handler will be invoked when it becomes empty.
 *
 * This is ONLY useful in a local development environment where one application node is firing and handling all tasks.
 *
 * @param  {String}     name        The name of the task to listen for empty events
 * @param  {Function}   handler     The handler to invoke when the task queue is empty
 */
var whenTasksEmpty = module.exports.whenTasksEmpty = function(name, handler) {
    if (!submitCount[name] || !queues[name]) {
        handler();
    } else {
        emitter.once('tasksEmpty-' + name, handler);
    }
};

/**
 * Destroy the managed task queue with the given name.
 *
 * @param   {String}    name            The type of the job message queue to destroy.
 * @param   {Function}  callback        Invoked when the process completes
 * @param   {Object}    callback.err    An error that occurred, if any
 * @api private
 */
var _destroyQueue = function(name, callback) {
    var queue = queues[name];
    if (!queue || !queue.queue) {
        log().warn({'queueName': name}, 'Attempted to unbind listener from non-existant job queue. Ignoring.');
        return callback();
    }

    var consumerTag = queue.consumerTag;
    queue = queue.queue;

    queue.unsubscribe(consumerTag).addCallback(function(ok) {
        delete queues[name];

        if (!ok) {
            log().error({'queueName': name}, 'An unknown error occurred unsubscribing a task queue.');
            return callback({'code': 500, 'msg': 'An unknown error occurred unsubscribing a task queue.'});
        }
        callback();
    });
};

/**
 * Increment the submitCount for a task of the given name.
 *
 * @param  {String}     name    The name of the task whose submitCount to increment
 * @api private
 */
var _incrementSubmitCount = function(name) {
    if (queues[name]) {
        submitCount[name] = submitCount[name] || 0;
        submitCount[name]++;
    }
};

/**
 * Decrement the submitCount for a task of the given name, and fire the `tasksEmpty` event for it if appropriate.
 *
 * @param  {String}     name    The name of the task whose submit count to decrement
 * @api private
 */
var _decrementSubmitCount = function(name) {
    submitCount[name] = submitCount[name] || 0;
    submitCount[name]--;

    if (submitCount[name] === 0) {
        emitter.emit('tasksEmpty-' + name);
    } else if (submitCount[name] < 0) {
        // This could happen if you have pending items in your task queue when the server starts for tests
        submitCount[name] = 0;
    }
};
<commit_msg>(MqRetry) Reconnect properly when RabbitMQ temporarily becomes unavailable<commit_after>/*!
 * Copyright 2012 Sakai Foundation (SF) Licensed under the
 * Educational Community 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.osedu.org/licenses/ECL-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.
 */

var _ = require('underscore');
var amqp = require('amqp');
var events = require('events');

var log = require('oae-logger').logger('mq');
var OAE = require('./oae');

var connection = null;
var queueExchange = null;
var queues = {};

var initialized = false;

// Keeps track of how many submitted v.s. processed tasks by name this local node has processed. Intended for synchronization of
// MQ tasks in a single-node (dev) environment.
var submitCount = {};
var emitter = module.exports = new events.EventEmitter();

/**
 * MQ constants.
 *
 *  {String}    DEFAULT_TASK_EXCHANGE_NAME          The name of the AMQP task exchange
 *  {Object}    DEFAULT_TASK_EXCHANGE_OPTS          The default options to specify when creating the AMQP task exchange
 *  {Object}    DEFAULT_TASK_QUEUE_OPTS             The default options to specify when creating the AMQP task queue
 *  {Object}    DEFAULT_TASK_QUEUE_PUBLISH_OPTS     The default options to specify when publishing tasks on the AMQP task queue
 *  {Object}    DEFAULT_TASK_QUEUE_SUBSCRIBE_OPTS   The default options to specify when subscribing to a task queue.
 */
var Constants = module.exports.Constants = {
    'DEFAULT_TASK_EXCHANGE_NAME': 'oae-taskexchange',
    'DEFAULT_TASK_EXCHANGE_OPTS': {
        'type': 'direct',
        'durable': true,
        'autoDelete': false
    },
    'DEFAULT_TASK_QUEUE_OPTS': {
        'durable': true,
        'autoDelete': false
    },
    'DEFAULT_TASK_QUEUE_PUBLISH_OPTS': {
        'deliveryMode': 2                   // 2 indicates 'persistent'
    },
    'DEFAULT_TASK_QUEUE_SUBSCRIBE_OPTS': {
        'ack': true,
        'prefetchCount': 15
    }
};

var deferredTaskHandlers = {};
var ready = false;

OAE.on('ready', function() {
    ready = true;

    var numberToBind = _.keys(deferredTaskHandlers).length;
    var numberBound = 0;
    var returned = false;

    /*!
     * Monitors all the deferred task handlers that have been bound, emitting a 'ready' event
     * when all have been bound.
     */
    var _monitorBinding = function(err) {
        if (returned) {
            // Do nothing, we've called back
            return;
        } else if (err) {
            emitter.emit('ready', err);
            returned = true;
            return;
        }

        numberBound++;
        if (!returned && numberBound >= numberToBind) {
            emitter.emit('ready');
            returned = true;
        }
    };

    if (numberToBind > 0) {
        // Bind all the deferred task handlers now that the container is ready
        _.each(deferredTaskHandlers, function(handlerInfo, taskName) {
            bind(taskName, handlerInfo.listener, handlerInfo.options, _monitorBinding);
            delete deferredTaskHandlers[taskName];
        });
    } else {
        // No deferred task handlers, we're just immediately ready
        emitter.emit('ready');
    }
});

/**
 * Initialize the Message Queue system so that it can start sending and receiving messages.
 *
 * @param   {Object}    mqConfig        The MQ Configuration object
 * @param   {Function}  callback        Invoked when initialization completes
 * @param   {Object}    callback.err    An error that occurred, if any
 */
var init = module.exports.init = function(mqConfig, callback) {
    if (!connection) {
        log().info('Initializing RabbitMQ connector.');

        connection = amqp.createConnection(mqConfig.connection);

        // While the connection always reconnects, this option ensures that all channels that were open before a
        // connection was closed / error are re-opened when connection is re-established.
        connection.setImplOptions({'reconnect': true});

        connection.on('error', function(err) {
            log().error({'err': err}, 'Error connecting to RabbitMQ.');

            // Since this connection can error, retry, and become ready again, ensure we only continue the
            // initialization cycle once.
            if (!initialized) {
                initialized = true;
                return callback();
            }
        });

        connection.on('close', function() {
            log().info('Closed connection to RabbitMQ');
        });

        connection.on('ready', function() {
            log().info('Connection to RabbitMQ established.');

            // Initialize the exchange for the job queues
            connection.exchange(Constants.DEFAULT_TASK_EXCHANGE_NAME, Constants.DEFAULT_TASK_EXCHANGE_OPTS, function(exchange) {
                queueExchange = exchange;

                // Since this connection can error, retry, and become ready again, ensure we only continue the
                // initialization cycle once.
                if (!initialized) {
                    initialized = true;
                    return callback();
                }
            });
        });
    } else {
        log().warn('Attempted to initialize an existing RabbitMQ connector. Ignoring');
        callback();
    }
};

/**
 * Bind a function to listen for a particular type of task. When bound, the node effectively becomes a "worker" for that task,
 * meaning when it receives a particular job, no other node active in the cluster will receive it.
 *
 * @param   {String}    name                                The unique label that identifies the type of job this binding is listening to
 * @param   {Function}  listener                            The function that will handle tasks of the given type
 * @param   {Object}    listener.data                       The data that was sent in the job. This is different depending on the type of job
 * @param   {Object}    [options]                           A set of options that can override the default ones.
 * @param   {Object}    [options.queue]                     Holds the options that will override the `Constants.DEFAULT_TASK_QUEUE_OPTS` options.
 * @param   {Object}    [options.subscribe]                 Holds the options that will override the `Constants.DEFAULT_TASK_QUEUE_SUBSCRIBE_OPTS` options.
 * @param   {Number}    [options.subscribe.prefetchCount]   The maximum number of tasks that a listener should handle concurrently.
 * @param   {Function}  [callback]                          Invoked after the listener has been bound to the MQ
 * @param   {Object}    [callback.err]                      An error that occurred binding to the task queue, if any
 */
var bind = module.exports.bind = function(name, listener, options, callback) {
    callback = callback || function(err) { /* error is logged within the implementation */ };
    options = options || {};
    options.queue = options.queue || {};
    options.subscribe = options.subscribe || {};

    if (queues[name] || (!ready && deferredTaskHandlers[name])) {
        log().warn({
            'queueName': name,
            'listener': listener
        }, 'Attempted to bind duplicate listener for job queue. Aborting.');
        return callback();
    }

    // If the container is not ready to handle requests, defer the actual binding of these handlers until later
    if (!ready) {
        log().trace({'name': name}, 'Deferring registration of task handler until server is ready.');
        deferredTaskHandlers[name] = {'listener': listener, 'options': options};
        return callback();
    }

    // Create/get the queue.
    var queueOptions = _.defaults(options.queue, Constants.DEFAULT_TASK_QUEUE_OPTS);
    connection.queue(name, queueOptions, function(queue) {
        queues[name] = {'queue': queue};
        log().info({'queueName': name}, 'Created a RabbitMQ task queue');

        // Bind the queue to the task exchange. The routing key is the same as the name of the job queue
        queue.bind(Constants.DEFAULT_TASK_EXCHANGE_NAME, name, function() {

            // Bind the listener to the queue, to handle all jobs of the given `name` type
            var subscribeOptions = _.defaults(options.subscribe, Constants.DEFAULT_TASK_QUEUE_SUBSCRIBE_OPTS);
            queue.subscribe(subscribeOptions, function(data, headers, deliveryInfo, message) {
                log().trace({
                    'queueName': name,
                    'data': data,
                    'headers': headers
                }, 'Received an MQ message.');

                // Pass it to the listener.
                // We can only shift this element off the queue, once the listener has finished
                try {
                    listener(data, function() {
                        log().trace({
                            'queueName': name,
                            'data': data,
                            'headers': headers
                        }, 'MQ message has been processed by the listener.');

                        _decrementSubmitCount(name);

                        // Acknowledge that we've seen the message.
                        // Note: We can't use queue.shift() as that only acknowledges the last message that the queue handed to us.
                        // This message and the last message are not necessarily the same if the prefetchCount was higher than 1.
                        message.acknowledge();

                    });
                } catch (err) {
                    log().error({
                        'err': err,
                        'queueName': name,
                        'data': data
                    }, 'Exception raised while handling job.');

                    _decrementSubmitCount(name);

                    // Acknowledge that we've seen the message.
                    message.acknowledge();
                }
            }).addCallback(function(ok) {
                if (!ok) {
                    log().error({'err': err, 'queueName': name}, 'Error binding worker for task queue.');
                    _destroyQueue(name, function(err) {
                        // Don't overwrite the original error with any binding errors
                        return callback(new Error('Error binding worker for task queue.'));
                    });
                }

                // Keep the consumerTag so we can unsubscribe later.
                queues[name].consumerTag = ok.consumerTag;
                return callback();
            });
        });
    });
};

/**
 * Unbind this node as a worker for the specified type of task.
 *
 * @param   {String}    name            The name of the task to which to unbind this node
 * @param   {Function}  callback        Invoked when the task has been unbound
 * @param   {Object}    callback.err    An error that occurred, if any
 */
var unbind = module.exports.unbind = function(name, callback) {
    callback = callback || function(err) {
        if (err) {
            log().error({'err': err, 'queueName': name}, 'Error unbinding task queue handler.');
        }
    };
    _destroyQueue(name, callback);
};

/**
 * Submit a job to the task queue to be picked up by some arbitrary node in the cluster.
 *
 * @param   {String}    name        The type of job to submit
 * @param   {Object}    data        The data to send with the task. This will be received by the worker for this type of task
 * @param   {Function}  callback    Invoked when the job has been submitted
 */
var submit = module.exports.submit = function(name, data, callback) {
    callback = callback || function() {};

    _incrementSubmitCount(name);

    // We publish with the routing key `name`, which routes it to the queue (if any) that is listening on this key
    queueExchange.publish(name, data, Constants.DEFAULT_TASK_QUEUE_PUBLISH_OPTS);
    return callback();
};

/**
 * Invoke the given handler only if the local counter of tasks of the given name indicates that the task queue is completely
 * empty. If it is not empty now, then the handler will be invoked when it becomes empty.
 *
 * This is ONLY useful in a local development environment where one application node is firing and handling all tasks.
 *
 * @param  {String}     name        The name of the task to listen for empty events
 * @param  {Function}   handler     The handler to invoke when the task queue is empty
 */
var whenTasksEmpty = module.exports.whenTasksEmpty = function(name, handler) {
    if (!submitCount[name] || !queues[name]) {
        handler();
    } else {
        emitter.once('tasksEmpty-' + name, handler);
    }
};

/**
 * Destroy the managed task queue with the given name.
 *
 * @param   {String}    name            The type of the job message queue to destroy.
 * @param   {Function}  callback        Invoked when the process completes
 * @param   {Object}    callback.err    An error that occurred, if any
 * @api private
 */
var _destroyQueue = function(name, callback) {
    var queue = queues[name];
    if (!queue || !queue.queue) {
        log().warn({'queueName': name}, 'Attempted to unbind listener from non-existant job queue. Ignoring.');
        return callback();
    }

    var consumerTag = queue.consumerTag;
    queue = queue.queue;

    queue.unsubscribe(consumerTag).addCallback(function(ok) {
        delete queues[name];

        if (!ok) {
            log().error({'queueName': name}, 'An unknown error occurred unsubscribing a task queue.');
            return callback({'code': 500, 'msg': 'An unknown error occurred unsubscribing a task queue.'});
        }
        callback();
    });
};

/**
 * Increment the submitCount for a task of the given name.
 *
 * @param  {String}     name    The name of the task whose submitCount to increment
 * @api private
 */
var _incrementSubmitCount = function(name) {
    if (queues[name]) {
        submitCount[name] = submitCount[name] || 0;
        submitCount[name]++;
    }
};

/**
 * Decrement the submitCount for a task of the given name, and fire the `tasksEmpty` event for it if appropriate.
 *
 * @param  {String}     name    The name of the task whose submit count to decrement
 * @api private
 */
var _decrementSubmitCount = function(name) {
    submitCount[name] = submitCount[name] || 0;
    submitCount[name]--;

    if (submitCount[name] === 0) {
        emitter.emit('tasksEmpty-' + name);
    } else if (submitCount[name] < 0) {
        // This could happen if you have pending items in your task queue when the server starts for tests
        submitCount[name] = 0;
    }
};
<|endoftext|>


<commit_before>'use strict'

const detailsPolyfill=require('crnx-base/details-polyfill')
const Matrix=require('./lhc-plot/matrix')

class LhcPlot {
	constructor() {
		const matrix=new Matrix
		const $numberInputs={}
		const $rangeInputs={}
		let $trDetCanvas,trDetCanvasContext
		let $equilibriumType
		const updateDetails=()=>{
			const updateEquilibriumType=()=>{
				const getEquilibriumType=()=>{
					// names are based on
					// http://ocw.mit.edu/courses/mathematics/18-03-differential-equations-spring-2010/readings/supp_notes/MIT18_03S10_chapter_26.pdf
					const D=matrix.det
					const T=matrix.tr
					const ssc=(T==0)?"center":((T<0)?"sink":"source")
					if (D<0) {
						return `saddle`
					} else if (D==0) {
						if (T==0) {
							if (matrix.b==0 && matrix.c==0) {
								return `degenerate everywhere fixed`
							} else {
								return `degenerate parallel lines`
							}
						} else {
							return `degenerate comb ${ssc}`
						}
					} else if (4*D<T*T) {
						return `nodal (real) ${ssc}`
					} else if (4*D==T*T) {
						if (matrix.b==0 && matrix.c==0) {
							return `star ${ssc}`
						} else {
							return `defective nodal (real) ${ssc}`
						}
					} else {
						if (T==0) {
							return ssc
						} else {
							return `spiral ${ssc}`
						}
					}
				}
				$equilibriumType.text(getEquilibriumType())
			}
			const redrawTrDetCanvas=()=>{
				const trRange=matrix.getRange('tr')
				const detRange=matrix.getRange('det')
				const ctx=trDetCanvasContext
				const width=$trDetCanvas[0].width
				const height=$trDetCanvas[0].height
				const xRange=width/2
				const yRange=height/2
				const drawRegions=()=>{
					ctx.save()
					ctx.lineWidth=2
					ctx.beginPath()
					ctx.moveTo(-xRange,0)
					ctx.lineTo(+xRange,0)
					ctx.moveTo(0,-yRange)
					ctx.lineTo(0,0)
					let first=true
					for (let x=-xRange;x<=+xRange;x++) {
						const T=x*trRange/xRange
						const D=T*T/4
						const y=-D*yRange/detRange
						ctx[first?'moveTo':'lineTo'](x,y)
						first=false
					}
					ctx.stroke()
					ctx.restore()
				}
				const drawPosition=()=>{
					ctx.save()
					const x=matrix.tr/trRange*xRange
					const y=-matrix.det/detRange*yRange
					const s=10
					ctx.strokeStyle='#F00'
					ctx.beginPath()
					ctx.moveTo(x-s,y)
					ctx.lineTo(x+s,y)
					ctx.moveTo(x,y-s)
					ctx.lineTo(x,y+s)
					ctx.stroke()
					ctx.restore()
				}
				ctx.save()
				ctx.fillStyle='#FFF'
				ctx.fillRect(0,0,width,height)
				ctx.translate(xRange,yRange)
				drawRegions()
				drawPosition()
				ctx.restore()
			}
			updateEquilibriumType()
			redrawTrDetCanvas()
		}
		const getCoefInputs=coef=>{
			const isMatrixElement=coef.length==1
			const valueRange=matrix.getRange(coef)
			const initCoefInput=(type,getValue,setValue)=>{
				const $input=$(`<input type='${type}'>`).attr('step','any').val(matrix[coef])
				if (type=='range') {
					$input.attr('min',-valueRange).attr('max',valueRange)
				}
				$input.on('input change',function(){
					if (this.checkValidity()) {
						const value=getValue()
						setValue(value)
						matrix[coef]=Number(value)
						matrix.forUpdated(cf=>{
							$numberInputs[cf].val(matrix[cf])
							$rangeInputs[cf].val(matrix[cf])
						})
						updateDetails()
					}
				})
				return $input
			}
			const $div=$("
")
			if (!isMatrixElement) {
				$div.addClass(coef)
			}
			let label=coef
			if (coef=='tr') {
				label=`\\operatorname{${coef}}(\\mathbf{A})`
			} else if (coef=='det') {
				label=`\\${coef}(\\mathbf{A})`
			}
			let $number,$range
			$div.append(
				$("").append(
					`<span class='label'>\\( ${label} \\)</span>`,
					`<span class='space'> </span>`,
					$number=initCoefInput('number',()=>$number.val(),(v)=>$range.val(v))
				),
				" ",
				$range=initCoefInput('range',()=>Number($range.val()).toFixed(2),(v)=>$number.val(v))
			)
			$numberInputs[coef]=$number
			$rangeInputs[coef]=$range
			return $div
		}
		this.$output=$("
").append(
			$("").append(
				$("").append("equation coefficients"),
				$("
").append(
					$("
").append(
						$("").append(getCoefInputs('a')),
						$("").append(getCoefInputs('b'))
					),
					$("
").append(
						$("").append(getCoefInputs('c')),
						$("").append(getCoefInputs('d'))
					)
				),
				getCoefInputs('tr'),
				getCoefInputs('det'),
				$("
").append(
					$("
").append(
						$("").append(getCoefInputs('re1')),
						$("").append(getCoefInputs('im1'))
					),
					$("
").append(
						$("").append(getCoefInputs('re2')),
						$("").append(getCoefInputs('im2'))
					)
				)
			).each(detailsPolyfill),
			$("").append(
				$("").append("equilibrium point type"),
				$equilibriumType=$("
")
			).each(detailsPolyfill),
			$("").append(
				$("").append("tr-det plane"),
				$trDetCanvas=$("<canvas width='240' height='240'>")
			).each(detailsPolyfill),
			$("").append(
				$("").append("phase plane"),
				$("
").append("not implemented yet, try to use MIT Mathlet instead")
			).each(detailsPolyfill)
		)
		trDetCanvasContext=$trDetCanvas[0].getContext('2d')
		updateDetails()
	}
}

module.exports=LhcPlot
<commit_msg>lhc-plot: eigenvalue tex labels<commit_after>'use strict'

const detailsPolyfill=require('crnx-base/details-polyfill')
const Matrix=require('./lhc-plot/matrix')

class LhcPlot {
	constructor() {
		const matrix=new Matrix
		const $numberInputs={}
		const $rangeInputs={}
		let $trDetCanvas,trDetCanvasContext
		let $equilibriumType
		const updateDetails=()=>{
			const updateEquilibriumType=()=>{
				const getEquilibriumType=()=>{
					// names are based on
					// http://ocw.mit.edu/courses/mathematics/18-03-differential-equations-spring-2010/readings/supp_notes/MIT18_03S10_chapter_26.pdf
					const D=matrix.det
					const T=matrix.tr
					const ssc=(T==0)?"center":((T<0)?"sink":"source")
					if (D<0) {
						return `saddle`
					} else if (D==0) {
						if (T==0) {
							if (matrix.b==0 && matrix.c==0) {
								return `degenerate everywhere fixed`
							} else {
								return `degenerate parallel lines`
							}
						} else {
							return `degenerate comb ${ssc}`
						}
					} else if (4*D<T*T) {
						return `nodal (real) ${ssc}`
					} else if (4*D==T*T) {
						if (matrix.b==0 && matrix.c==0) {
							return `star ${ssc}`
						} else {
							return `defective nodal (real) ${ssc}`
						}
					} else {
						if (T==0) {
							return ssc
						} else {
							return `spiral ${ssc}`
						}
					}
				}
				$equilibriumType.text(getEquilibriumType())
			}
			const redrawTrDetCanvas=()=>{
				const trRange=matrix.getRange('tr')
				const detRange=matrix.getRange('det')
				const ctx=trDetCanvasContext
				const width=$trDetCanvas[0].width
				const height=$trDetCanvas[0].height
				const xRange=width/2
				const yRange=height/2
				const drawRegions=()=>{
					ctx.save()
					ctx.lineWidth=2
					ctx.beginPath()
					ctx.moveTo(-xRange,0)
					ctx.lineTo(+xRange,0)
					ctx.moveTo(0,-yRange)
					ctx.lineTo(0,0)
					let first=true
					for (let x=-xRange;x<=+xRange;x++) {
						const T=x*trRange/xRange
						const D=T*T/4
						const y=-D*yRange/detRange
						ctx[first?'moveTo':'lineTo'](x,y)
						first=false
					}
					ctx.stroke()
					ctx.restore()
				}
				const drawPosition=()=>{
					ctx.save()
					const x=matrix.tr/trRange*xRange
					const y=-matrix.det/detRange*yRange
					const s=10
					ctx.strokeStyle='#F00'
					ctx.beginPath()
					ctx.moveTo(x-s,y)
					ctx.lineTo(x+s,y)
					ctx.moveTo(x,y-s)
					ctx.lineTo(x,y+s)
					ctx.stroke()
					ctx.restore()
				}
				ctx.save()
				ctx.fillStyle='#FFF'
				ctx.fillRect(0,0,width,height)
				ctx.translate(xRange,yRange)
				drawRegions()
				drawPosition()
				ctx.restore()
			}
			updateEquilibriumType()
			redrawTrDetCanvas()
		}
		const getCoefInputs=coef=>{
			const isMatrixElement=coef.length==1
			const valueRange=matrix.getRange(coef)
			const initCoefInput=(type,getValue,setValue)=>{
				const $input=$(`<input type='${type}'>`).attr('step','any').val(matrix[coef])
				if (type=='range') {
					$input.attr('min',-valueRange).attr('max',valueRange)
				}
				$input.on('input change',function(){
					if (this.checkValidity()) {
						const value=getValue()
						setValue(value)
						matrix[coef]=Number(value)
						matrix.forUpdated(cf=>{
							$numberInputs[cf].val(matrix[cf])
							$rangeInputs[cf].val(matrix[cf])
						})
						updateDetails()
					}
				})
				return $input
			}
			const $div=$("
")
			if (!isMatrixElement) {
				$div.addClass(coef)
			}
			let label=coef
			if (coef=='tr') {
				label=`\\operatorname{${coef}}(\\mathbf{A})`
			} else if (coef=='det') {
				label=`\\${coef}(\\mathbf{A})`
			} else if (coef.slice(0,2)=='re') {
				//label=`\\lambda_${coef.charAt(2)} =`
				label=`\\Re \\lambda_${coef.charAt(2)}`
			} else if (coef.slice(0,2)=='im') {
				//label=`+ i \\cdot`
				label=`\\Im \\lambda_${coef.charAt(2)}`
			}
			let $number,$range
			$div.append(
				$("").append(
					`<span class='label'>\\( ${label} \\)</span>`,
					`<span class='space'> </span>`,
					$number=initCoefInput('number',()=>$number.val(),(v)=>$range.val(v))
				),
				" ",
				$range=initCoefInput('range',()=>Number($range.val()).toFixed(2),(v)=>$number.val(v))
			)
			$numberInputs[coef]=$number
			$rangeInputs[coef]=$range
			return $div
		}
		this.$output=$("
").append(
			$("").append(
				$("").append("equation coefficients"),
				$("
").append(
					$("
").append(
						$("").append(getCoefInputs('a')),
						$("").append(getCoefInputs('b'))
					),
					$("
").append(
						$("").append(getCoefInputs('c')),
						$("").append(getCoefInputs('d'))
					)
				),
				getCoefInputs('tr'),
				getCoefInputs('det'),
				$("
").append(
					$("
").append(
						$("").append(getCoefInputs('re1')),
						$("").append(getCoefInputs('im1'))
					),
					$("
").append(
						$("").append(getCoefInputs('re2')),
						$("").append(getCoefInputs('im2'))
					)
				)
			).each(detailsPolyfill),
			$("").append(
				$("").append("equilibrium point type"),
				$equilibriumType=$("
")
			).each(detailsPolyfill),
			$("").append(
				$("").append("tr-det plane"),
				$trDetCanvas=$("<canvas width='240' height='240'>")
			).each(detailsPolyfill),
			$("").append(
				$("").append("phase plane"),
				$("
").append("not implemented yet, try to use MIT Mathlet instead")
			).each(detailsPolyfill)
		)
		trDetCanvasContext=$trDetCanvas[0].getContext('2d')
		updateDetails()
	}
}

module.exports=LhcPlot
<|endoftext|>


<commit_before>import firebase from 'firebase';
import Enum from 'es6-enum';

export const AUTH_PROVIDER = Enum(
  "FACEBOOK",
  "GOOGLE"
);

function getFirebaseAuthProvider(authProvider) {
  switch (authProvider) {
    case AUTH_PROVIDER.FACEBOOK:
      return new firebase.auth.FacebookAuthProvider();
    case AUTH_PROVIDER.GOOGLE:
        return new firebase.auth.GoogleAuthProvider();
    default:
        return null;
  }
}

export function login(authProvider) {
  // Setup Firebase Auth Provider
  const provider = getFirebaseAuthProvider(authProvider);

  firebase.auth().signInWithPopup(provider).then(function(result) {
    console.log('login success!');
    // This gives you a Facebook Access Token. You can use it to access the Facebook API.
    // var token = result.credential.accessToken;
    // The signed-in user info.
    // var user = result.user;
  }).catch(function(error) {
    console.log('login fail!');
    // Handle Errors here.
    // var errorCode = error.code;
    // var errorMessage = error.message;
    // The email of the user's account used.
    // var email = error.email;
    // The firebase.auth.AuthCredential type that was used.
    // var credential = error.credential;
  });
}

export function logout() {
  firebase.auth().signOut().then(function() {
    // Sign-out successful.
  }, function(error) {
    // An error happened.
  });
}
<commit_msg>updated login() method<commit_after>import firebase from 'firebase';
import Enum from 'es6-enum';
import { browserHistory } from 'react-router';

export const AUTH_PROVIDER = Enum(
  "FACEBOOK",
  "GOOGLE"
);

function getFirebaseAuthProvider(authProvider) {
  switch (authProvider) {
    case AUTH_PROVIDER.FACEBOOK:
      return new firebase.auth.FacebookAuthProvider();
    case AUTH_PROVIDER.GOOGLE:
        return new firebase.auth.GoogleAuthProvider();
    default:
        return null;
  }
}

export function login(authProvider) {
  // Setup Firebase Auth Provider
  const provider = getFirebaseAuthProvider(authProvider);

  firebase.auth().signInWithPopup(provider).then(function(result) {
    // Redirect to home
    browserHistory.push('/');
  }).catch(function(error) {
    // Present alert for error
    alert('Error: ' + error.code);
  });
}

export function logout() {
  firebase.auth().signOut().then(function() {
    // Sign-out successful.
  }, function(error) {
    // An error happened.
  });
}
<|endoftext|>


<commit_before>define([
	"require",
	"intern!object",
	"intern/chai!assert",
	"dojo/on",
	"delite/DisplayContainer",
	"delite/Widget",
	"delite/register",
	"requirejs-domready/domReady!"
], function (require, registerSuite, assert, on, DisplayContainer, Widget, register) {
	var container;
	registerSuite({
		name: "DisplayContainer",

		setup: function () {
			container = document.createElement("div");
			document.body.appendChild(container);
		},

		// test with no controller
		original: function () {
			this.timeout = 2500;
			register("test-default-display-container", [HTMLElement, Widget, DisplayContainer]);
			var dcontainer = register.createElement("test-default-display-container");

			function initView(view) {
				dcontainer.appendChild(view);
				view.style.visibility = "hidden";
				view.style.display = "none";
			}

			function testView(view) {
				assert.strictEqual(view.style.visibility, "visible", "visibility");
				assert.strictEqual(view.style.display, "", "display");
			}

			var view1 = document.createElement("div");
			initView(view1);
			var view2 = document.createElement("div");
			view2.setAttribute("id", "original-view");
			initView(view2);
			container.appendChild(dcontainer);
			dcontainer.startup();
			var beforeShowCalled = false;
			dcontainer.on("delite-before-show", function () {
				beforeShowCalled = true;
			});
			return dcontainer.show(view1).then(function () {
				assert(beforeShowCalled, "before-show event must be dispatched");
				testView(view1);
			}).then(function () {
				return dcontainer.show("original-view");
			}).then(function () {
				testView(view2);
			});
		},

		hide: function () {
			this.timeout = 2500;
			register("test-hide-display-container", [HTMLElement, Widget, DisplayContainer]);
			var dcontainer = register.createElement("test-hide-display-container");

			function initView(view) {
				dcontainer.appendChild(view);
				view.style.visibility = "visible";
				view.style.display = "";
			}

			function testView(view) {
				assert.strictEqual(view.style.visibility, "hidden", "visibility");
				assert.strictEqual(view.style.display, "none", "display");
			}

			var view1 = document.createElement("div");
			initView(view1);
			var view2 = document.createElement("div");
			view2.setAttribute("id", "hide-view");
			initView(view2);
			container.appendChild(dcontainer);
			dcontainer.startup();
			var beforeHideCalled = false;
			dcontainer.on("delite-before-hide", function () {
				beforeHideCalled = true;
			});
			// by node
			return dcontainer.hide(view1).then(function () {
				assert(beforeHideCalled, "before-hide event must be dispatched");
				testView(view1);
			}).then(function () {
				return dcontainer.hide("hide-view");
			}).then(function () {
				testView(view2);
			});
		},

		event: function () {
			this.timeout = 2500;
			var handler;

			function initView(view, id) {
				view.style.visibility = "hidden";
				view.style.display = "none";
				view.setAttribute("id", id + "-event");
			}

			document.addEventListener("delite-display-load", handler = function (event) {
				event.preventDefault();
				var view = document.createElement("div");
				initView(view, event.dest);
				event.setChild({
					child: view
				});
			});
			register("test-event-display-container", [HTMLElement, Widget, DisplayContainer]);
			var dcontainer = register.createElement("test-event-display-container");
			dcontainer.setAttribute("id", "dcontainer");
			container.appendChild(dcontainer);
			dcontainer.startup();
			var beforeDisplayCalled = false;
			dcontainer.on("delite-before-show", function () {
				beforeDisplayCalled = true;
			});
			// by node
			return dcontainer.show("view1-event").then(function () {
				assert(beforeDisplayCalled, "before show event must be dispatched");
				document.removeEventListener("delite-display-load", handler);
			});
		},

		// test with a controller
		custom: function () {
			this.tiemout = 2500;
			var handler;

			function initView(view, id) {
				view.style.visibility = "hidden";
				view.style.display = "none";
				view.setAttribute("id", id);
			}

			document.addEventListener("delite-display-load", handler = function (event) {
				event.preventDefault();
				var view = document.createElement("div");
				initView(view, event.dest);
				event.setChild({
					child: view
				});
			});
			register("test-custom-display-container", [HTMLElement, Widget, DisplayContainer]);
			var dcontainer = register.createElement("test-custom-display-container");
			dcontainer.setAttribute("id", "dcontainer");
			container.appendChild(dcontainer);
			dcontainer.startup();
			function testView(view) {
				assert.strictEqual(view.style.visibility, "visible", "visibility");
				assert.strictEqual(view.style.display, "", "display");
				assert.strictEqual(view.parentNode, dcontainer, "parentNode");
			}

			// by node
			return dcontainer.show("view1").then(function () {
				testView(document.getElementById("view1"));
			}).then(function () {
				return dcontainer.show("view2");
			}).then(function () {
				testView(document.getElementById("view2"));
				document.removeEventListener("delite-display-load", handler);
			});
		},

		teardown: function () {
			container.parentNode.removeChild(container);
		}
	});
});
<commit_msg>Test passing Promise to setChild(), refs #350.<commit_after>define([
	"require",
	"intern!object",
	"intern/chai!assert",
	"lie/dist/lie",
	"delite/DisplayContainer",
	"delite/Widget",
	"delite/register",
	"requirejs-domready/domReady!"
], function (require, registerSuite, assert, Promise, DisplayContainer, Widget, register) {
	var container;
	registerSuite({
		name: "DisplayContainer",

		setup: function () {
			container = document.createElement("div");
			document.body.appendChild(container);
		},

		// test with no controller
		original: function () {
			this.timeout = 2500;
			register("test-default-display-container", [HTMLElement, Widget, DisplayContainer]);
			var dcontainer = register.createElement("test-default-display-container");

			function initView(view) {
				dcontainer.appendChild(view);
				view.style.visibility = "hidden";
				view.style.display = "none";
			}

			function testView(view) {
				assert.strictEqual(view.style.visibility, "visible", "visibility");
				assert.strictEqual(view.style.display, "", "display");
			}

			var view1 = document.createElement("div");
			initView(view1);
			var view2 = document.createElement("div");
			view2.setAttribute("id", "original-view");
			initView(view2);
			container.appendChild(dcontainer);
			dcontainer.startup();
			var beforeShowCalled = false;
			dcontainer.on("delite-before-show", function () {
				beforeShowCalled = true;
			});
			return dcontainer.show(view1).then(function () {
				assert(beforeShowCalled, "before-show event must be dispatched");
				testView(view1);
			}).then(function () {
				return dcontainer.show("original-view");
			}).then(function () {
				testView(view2);
			});
		},

		hide: function () {
			this.timeout = 2500;
			register("test-hide-display-container", [HTMLElement, Widget, DisplayContainer]);
			var dcontainer = register.createElement("test-hide-display-container");

			function initView(view) {
				dcontainer.appendChild(view);
				view.style.visibility = "visible";
				view.style.display = "";
			}

			function testView(view) {
				assert.strictEqual(view.style.visibility, "hidden", "visibility");
				assert.strictEqual(view.style.display, "none", "display");
			}

			var view1 = document.createElement("div");
			initView(view1);
			var view2 = document.createElement("div");
			view2.setAttribute("id", "hide-view");
			initView(view2);
			container.appendChild(dcontainer);
			dcontainer.startup();
			var beforeHideCalled = false;
			dcontainer.on("delite-before-hide", function () {
				beforeHideCalled = true;
			});
			// by node
			return dcontainer.hide(view1).then(function () {
				assert(beforeHideCalled, "before-hide event must be dispatched");
				testView(view1);
			}).then(function () {
				return dcontainer.hide("hide-view");
			}).then(function () {
				testView(view2);
			});
		},

		event: function () {
			this.timeout = 2500;
			var handler;

			function initView(view, id) {
				view.style.visibility = "hidden";
				view.style.display = "none";
				view.setAttribute("id", id + "-event");
			}

			document.addEventListener("delite-display-load", handler = function (event) {
				var view = document.createElement("div");
				initView(view, event.dest);
				event.setChild({
					child: view
				});
			});
			register("test-event-display-container", [HTMLElement, Widget, DisplayContainer]);
			var dcontainer = register.createElement("test-event-display-container");
			dcontainer.setAttribute("id", "dcontainer");
			container.appendChild(dcontainer);
			dcontainer.startup();
			var beforeDisplayCalled = false;
			dcontainer.on("delite-before-show", function () {
				beforeDisplayCalled = true;
			});
			// by node
			return dcontainer.show("view1-event").then(function () {
				assert(beforeDisplayCalled, "before show event must be dispatched");
				document.removeEventListener("delite-display-load", handler);
			});
		},

		// test with a controller
		custom: function () {
			this.tiemout = 2500;
			var handler;

			function initView(view, id) {
				view.style.visibility = "hidden";
				view.style.display = "none";
				view.setAttribute("id", id);
			}

			document.addEventListener("delite-display-load", handler = function (event) {
				var view = document.createElement("div");
				initView(view, event.dest);
				event.setChild(new Promise(function (resolve) {	// test passing Promise to setChild()
					setTimeout(function () {
						resolve({child: view});
					}, 10);
				}));
			});
			register("test-custom-display-container", [HTMLElement, Widget, DisplayContainer]);
			var dcontainer = register.createElement("test-custom-display-container");
			dcontainer.setAttribute("id", "dcontainer");
			container.appendChild(dcontainer);
			dcontainer.startup();
			function testView(view) {
				assert.strictEqual(view.style.visibility, "visible", "visibility");
				assert.strictEqual(view.style.display, "", "display");
				assert.strictEqual(view.parentNode, dcontainer, "parentNode");
			}

			// by node
			return dcontainer.show("view1").then(function () {
				testView(document.getElementById("view1"));
			}).then(function () {
				return dcontainer.show("view2");
			}).then(function () {
				testView(document.getElementById("view2"));
				document.removeEventListener("delite-display-load", handler);
			});
		},

		teardown: function () {
			container.parentNode.removeChild(container);
		}
	});
});
<|endoftext|>


<commit_before>var workerMode = typeof importScripts === 'function';
var nodejs = typeof process == "object";

function getScriptName() {

  if (nodejs) {
    var scriptPath = getScriptPath();
    var scriptName = __filename;
    scriptName = scriptName.substr(scriptPath.length);
    return scriptName;
  }

  var scriptName
  , source
  , lastStackFrameRegex = new RegExp(/.+\/(.*?):\d+[:]*\d*.*$/)
  , currentStackFrameRegex = new RegExp('getScriptName \\(.+\\/(.*?):\\d+[:]*\\d*.*\\)');

  try {
    var x = v43x76fr.name;
  } catch(error) {

    if((source = lastStackFrameRegex.exec(error.stack.trim())))
      scriptName = source[1];
    else if((source = currentStackFrameRegex.exec(error.stack.trim())))
      scriptName = source[1];
    else if(error.fileName != undefined)
      scriptName = error.fileName;
  }

  if (scriptName && scriptName.indexOf('?r=') != -1) {
    scriptName = scriptName.substr(0, scriptName.indexOf('?r='));
  }

  return scriptName;
}

function getScriptPath() {

  if (nodejs) {
    return __dirname + '/';
  }

  var scriptName = getScriptName();

  if (workerMode) {
    var location = self.location;
  } else {
    var location = window.location;
  }

  var origin = location.origin;
  if (location.protocol == "file:") {
    origin = "file://";
  }

  var scriptPath
  , source
  , lastStackFrameRegex = new RegExp('.*' + origin + '(.*)' + scriptName + '.*:\\d+[:]*\\d*.*$')
  , currentStackFrameRegex = new RegExp('getScriptPath \\(.*' + origin + '(.*)' + scriptName + '.*:\\d+[:]*\\d*.*\\)');

  try {
    var x = v43x76fr.name;
  } catch(error) {

    if((source = lastStackFrameRegex.exec(error.stack.trim())))
      scriptPath = source[1];
    else if((source = currentStackFrameRegex.exec(error.stack.trim())))
      scriptPath = source[1];

    if (location.protocol == "file:") {
      var scriptPathTokens = scriptPath.split('/');
      var pathnameTokens = location.pathname.split('/');
      for (var i = 0 ; i < scriptPathTokens.length ; ++i) {
        if (scriptPathTokens[i] != pathnameTokens[i]) {
          break;
        }
      }
      scriptPath = '';
      for (var j = i ; j < scriptPathTokens.length ; ++j) {
        if (scriptPathTokens[j] == "") {
          break;
        }
        scriptPath += (scriptPathTokens[j] + '/');
      }
    }
  }

  return scriptPath;
}
<commit_msg>fix getScriptName function when url ends with ?v=...<commit_after>var workerMode = typeof importScripts === 'function';
var nodejs = typeof process == "object";

function getScriptName() {

  if (nodejs) {
    var scriptPath = getScriptPath();
    var scriptName = __filename;
    scriptName = scriptName.substr(scriptPath.length);
    return scriptName;
  }

  var scriptName
  , source
  , lastStackFrameRegex = new RegExp(/.+\/(.*?):\d+[:]*\d*.*$/)
  , currentStackFrameRegex = new RegExp('getScriptName \\(.+\\/(.*?):\\d+[:]*\\d*.*\\)');

  try {
    var x = v43x76fr.name;
  } catch(error) {

    if((source = lastStackFrameRegex.exec(error.stack.trim())))
      scriptName = source[1];
    else if((source = currentStackFrameRegex.exec(error.stack.trim())))
      scriptName = source[1];
    else if(error.fileName != undefined)
      scriptName = error.fileName;
  }

  if (scriptName && scriptName.indexOf('?r=') != -1) {
    scriptName = scriptName.substr(0, scriptName.indexOf('?r='));
  }

  if (scriptName && scriptName.indexOf('?v=') != -1) {
    scriptName = scriptName.substr(0, scriptName.indexOf('?v='));
  }

  return scriptName;
}

function getScriptPath() {

  if (nodejs) {
    return __dirname + '/';
  }

  var scriptName = getScriptName();

  if (workerMode) {
    var location = self.location;
  } else {
    var location = window.location;
  }

  var origin = location.origin;
  if (location.protocol == "file:") {
    origin = "file://";
  }

  var scriptPath
  , source
  , lastStackFrameRegex = new RegExp('.*' + origin + '(.*)' + scriptName + '.*:\\d+[:]*\\d*.*$')
  , currentStackFrameRegex = new RegExp('getScriptPath \\(.*' + origin + '(.*)' + scriptName + '.*:\\d+[:]*\\d*.*\\)');

  try {
    var x = v43x76fr.name;
  } catch(error) {

    if((source = lastStackFrameRegex.exec(error.stack.trim())))
      scriptPath = source[1];
    else if((source = currentStackFrameRegex.exec(error.stack.trim())))
      scriptPath = source[1];

    if (location.protocol == "file:") {
      var scriptPathTokens = scriptPath.split('/');
      var pathnameTokens = location.pathname.split('/');
      for (var i = 0 ; i < scriptPathTokens.length ; ++i) {
        if (scriptPathTokens[i] != pathnameTokens[i]) {
          break;
        }
      }
      scriptPath = '';
      for (var j = i ; j < scriptPathTokens.length ; ++j) {
        if (scriptPathTokens[j] == "") {
          break;
        }
        scriptPath += (scriptPathTokens[j] + '/');
      }
    }
  }

  return scriptPath;
}
<|endoftext|>


<commit_before>import { remote } from 'electron';

import { positionOnScreen } from '../../../_util';

const mainWindow = remote.getCurrentWindow();
const webContents = mainWindow.webContents;
const MINI_SIZE = 310;

let mini = false;

window.wait(() => {
  if (Settings.get('miniAlwaysShowSongInfo', false)) {
    document.body.setAttribute('controls', 'controls');
  }
  if (Settings.get('miniUseScrollVolume', false)) {
    window.GPM.mini.setScrollVolume(true);
  }

  window.GPM.mini.on('enable', () => {
    Emitter.fire('mini', { state: true });

	// Restore the mini size/position from settings, otherwise use default size and regular position.
    const miniSize = Settings.get('mini-size', [MINI_SIZE, MINI_SIZE]);
    const miniPosition = Settings.get('mini-position', mainWindow.getPosition());
    mainWindow.setContentSize(...miniSize);
    mainWindow.setSize(...mainWindow.getSize());
    if (positionOnScreen(miniPosition)) {
      mainWindow.setPosition(...miniPosition);
    } else {
      mainWindow.center();
    }

    mainWindow.setMaximumSize(MINI_SIZE, MINI_SIZE);
    mainWindow.setMinimumSize(50, 50);
    webContents.executeJavaScript('document.body.setAttribute("mini", "mini")');
    remote.getCurrentWebContents().setZoomFactor(1);
    remote.getCurrentWindow().setAlwaysOnTop(Settings.get('miniAlwaysOnTop', false));
    mini = true;
  });

  window.GPM.mini.on('disable', () => {
    Emitter.fire('mini', { state: false });
    // DEV: Set max size to be massive
    //      Same reason as specified in Electron src
    //        --> https://github.com/atom/electron/blob/master/atom/browser/native_window_views.cc
    mainWindow.setMaximumSize(99999999, 999999999);
    mainWindow.setMinimumSize(200, 200);

    // Restore the regular size/position from settings.
    const regularSize = Settings.get('size');
    const regularPosition = Settings.get('position');
    mainWindow.setSize(...regularSize);
    if (positionOnScreen(regularPosition)) {
      mainWindow.setPosition(...regularPosition);
    } else {
      mainWindow.center();
    }

    webContents.executeJavaScript('document.body.removeAttribute("mini", "mini")');
    remote.getCurrentWebContents().setZoomFactor(1);
    remote.getCurrentWindow().setAlwaysOnTop(false);
    mini = false;
  });
});

let nextZoom;
Emitter.on('set:zoom', (event, factor) => {
  if (nextZoom) {
    clearTimeout(nextZoom);
  }
  nextZoom = setTimeout(() => {
    remote.getCurrentWebContents().setZoomFactor(factor);
  }, 5);
});

Emitter.on('miniAlwaysShowSongInfo', (event, state) => {
  if (state.state) {
    document.body.setAttribute('controls', 'controls');
  } else {
    document.body.removeAttribute('controls');
  }
});
Emitter.on('miniAlwaysOnTop', (event, state) => {
  if (mini) {
    remote.getCurrentWindow().setAlwaysOnTop(state.state);
  }
});
Emitter.on('miniUseScrollVolume', (event, state) => {
  window.GPM.mini.setScrollVolume(state.state);
});
<commit_msg>Fixes mini player maximum size issue<commit_after>import { remote } from 'electron';

import { positionOnScreen } from '../../../_util';

const mainWindow = remote.getCurrentWindow();
const webContents = mainWindow.webContents;
const MINI_SIZE = 310;

let mini = false;

window.wait(() => {
  if (Settings.get('miniAlwaysShowSongInfo', false)) {
    document.body.setAttribute('controls', 'controls');
  }
  if (Settings.get('miniUseScrollVolume', false)) {
    window.GPM.mini.setScrollVolume(true);
  }

  window.GPM.mini.on('enable', () => {
    Emitter.fire('mini', { state: true });

	// Restore the mini size/position from settings, otherwise use default size and regular position.
    const miniSize = Settings.get('mini-size', [MINI_SIZE, MINI_SIZE]);
    const miniPosition = Settings.get('mini-position', mainWindow.getPosition());
    mainWindow.setContentSize(...miniSize);
    mainWindow.setSize(...mainWindow.getSize());
    if (positionOnScreen(miniPosition)) {
      mainWindow.setPosition(...miniPosition);
    } else {
      mainWindow.center();
    }

    setTimeout(() => remote.getCurrentWindow().setMaximumSize(MINI_SIZE, MINI_SIZE), 0);
    remote.getCurrentWindow().setMinimumSize(50, 50);
    webContents.executeJavaScript('document.body.setAttribute("mini", "mini")');
    remote.getCurrentWebContents().setZoomFactor(1);
    remote.getCurrentWindow().setAlwaysOnTop(Settings.get('miniAlwaysOnTop', false));
    mini = true;
  });

  window.GPM.mini.on('disable', () => {
    Emitter.fire('mini', { state: false });
    // DEV: Set max size to be massive
    //      Same reason as specified in Electron src
    //        --> https://github.com/atom/electron/blob/master/atom/browser/native_window_views.cc
    setTimeout(() => remote.getCurrentWindow().setMaximumSize(99999999, 999999999), 0);
    remote.getCurrentWindow().setMinimumSize(200, 200);

    // Restore the regular size/position from settings.
    const regularSize = Settings.get('size');
    const regularPosition = Settings.get('position');
    mainWindow.setSize(...regularSize);
    if (positionOnScreen(regularPosition)) {
      mainWindow.setPosition(...regularPosition);
    } else {
      mainWindow.center();
    }

    webContents.executeJavaScript('document.body.removeAttribute("mini", "mini")');
    remote.getCurrentWebContents().setZoomFactor(1);
    remote.getCurrentWindow().setAlwaysOnTop(false);
    mini = false;
  });
});

let nextZoom;
Emitter.on('set:zoom', (event, factor) => {
  if (nextZoom) {
    clearTimeout(nextZoom);
  }
  nextZoom = setTimeout(() => {
    remote.getCurrentWebContents().setZoomFactor(factor);
  }, 5);
});

Emitter.on('miniAlwaysShowSongInfo', (event, state) => {
  if (state.state) {
    document.body.setAttribute('controls', 'controls');
  } else {
    document.body.removeAttribute('controls');
  }
});
Emitter.on('miniAlwaysOnTop', (event, state) => {
  if (mini) {
    remote.getCurrentWindow().setAlwaysOnTop(state.state);
  }
});
Emitter.on('miniUseScrollVolume', (event, state) => {
  window.GPM.mini.setScrollVolume(state.state);
});
<|endoftext|>


<commit_before>import React from 'react';

import { Anomoly, Gao, Zerotorescue, Abelito75, niseko, blazyb, JeremyDwayne, FraunchToost, Tyndi } from 'CONTRIBUTORS';
import SPELLS from 'common/SPELLS';
import SpellLink from 'common/SpellLink';
import { change, date } from 'common/changelog';

export default [
  change(date(2020, 10, 1), <>Updated spell data so stat weights will be accurate. </>, Abelito75),
  change(date(2020, 10, 1), <>Added Statistics for Tear of Morning and Ancient Teachings of the Monastery. </>, Abelito75),
  change(date(2020, 9, 30), <>Integration Tests have been added for Mistweaver. </>, Anomoly),
  change(date(2020, 9, 30), <>Statistic created for the potency conduit Imbued Reflections. </>, Abelito75),
  change(date(2020, 9, 26), <>Added Yu'lon and Chi-ji to the cooldown tracker. </>, Abelito75),
  change(date(2020, 9, 26), <>Added Rising Sun Revival, Jade Bond, and Nourishing Chi. </>, Abelito75),
  change(date(2020, 9, 26), <>Updated Checklist to include Touch of Death, Yu'lon and Chi-ji.</>, Abelito75),
  change(date(2020, 9, 22), <>Updated pieces to the Mistweaver Core for Shadowlands.</>, Tyndi),
  change(date(2020, 9, 22), <>Mistweaver cleanup for shadowlands. </>, Abelito75),
  change(date(2020, 9, 21), <>Added all endurance conduits. </>, Abelito75),
  change(date(2020, 9, 1), <>Initial Shadowlands cleanup activities. </>, Anomoly),
  change(date(2020, 5, 24), <>Updated the Rising Mist modules and generally made tracking hots better. </>,Abelito75),
  change(date(2020, 4, 22), <>Corrected cooldown for Rising Sun Kick and Blackout Kick. </>,Abelito75),
  change(date(2020, 4, 1), <>Added average haste gained statistic from Mist Peaks azerite trait. </>,FraunchToost),
  change(date(2020, 3, 22), <>Fixed <SpellLink id={SPELLS.RISING_MIST_TALENT.id} /> healing for hot extensions. </>,Abelito75),
  change(date(2020, 3, 5), <>Added average time between <SpellLink id={SPELLS.RISING_SUN_KICK.id} /> casts statistic when <SpellLink id={SPELLS.RISING_MIST_TALENT.id} /> is talented. </>,FraunchToost),
  change(date(2020, 2, 27), <>Bugfix to only show <SpellLink id={SPELLS.RISING_SUN_KICK.id} /> cast efficiency suggestion when <SpellLink id={SPELLS.RISING_MIST_TALENT.id} /> is talented. </>,FraunchToost),
  change(date(2020, 1, 8), <>Updated for 8.3 </>,Abelito75),
  change(date(2020, 1, 8), <>Updated stat weight calculation for <SpellLink id={SPELLS.RISING_MIST_TALENT.id} />, added new essence font statistic, tft rsk with rm is a correct use of tft now. </>,Abelito75),
  change(date(2019, 12, 5), <>Updated stat weight calculation for Way of the Crane and Lucid Dreams. </>,Anomoly),
  change(date(2019, 11, 25), <>Added a Essence Font healing breakdown donut. </>,Abelito75),
  change(date(2019, 11, 25), <>Fixed upwelling randomly bugging out. </>,Abelito75),
  change(date(2019, 11, 20), <>Added Uplift revival reduction to rising mist tooltip. </>,Abelito75),
  change(date(2019, 11, 16), <>Updated a few spells to use new methods. </>,Abelito75),
  change(date(2019, 11, 12), <>Big Rising Mist overhaul. Now includes extra Vivify cleaves, Enveloping Mist, and Gusts Of mist hits from Essence Font's Hot. </>,Abelito75),
  change(date(2019, 11, 9), <>Added upwelling statistic. </>,Abelito75),
  change(date(2019, 11, 9), <>Fixed Tier45 comparison if you didn't play in a way where mana would be returned/saved. </>,Abelito75),
  change(date(2019, 10, 14), <>Fixed small typos for rising mist statistic box. </>,Abelito75),
  change(date(2019, 10, 14), <>Spelling and grammar corrections in Tier 45 comparison tool. </>,JeremyDwayne),
  change(date(2019, 10, 13), <>Fixed Tier 45 comparison tool. </>,Abelito75),
  change(date(2019, 10, 13), <>New Tier 45 comparison tool to see if you picked the best talent for your playstyle. </>,Abelito75),
  change(date(2019, 10, 4), <>Added missing array declaration for enemiesHitSCK <SpellLink id={SPELLS.SPINNING_CRANE_KICK.id} />.</>, JeremyDwayne),
  change(date(2019, 10, 1), <>Bugfix for Lifecycles adding the potential mana reduction from an empowered <SpellLink id={SPELLS.VIVIFY.id} /> </>,Anomoly),
  change(date(2019, 9, 27), <>Update to date for 8.2.5 </>,Abelito75),
  change(date(2019, 8, 17), <>Fixed bug where you could have NaN mana tea overhealing.</>, Abelito75),
  change(date(2019, 8, 14), <>Wotc infographic now checks for physical damage only.</>, Abelito75),
  change(date(2019, 8, 12), 'Added essence Lucid Dreams.', [blazyb]),
  change(date(2019, 8, 10), <>Fixed bug where if you end the fight with wotc buff it said everything was 0 hps. Added more info the the wotc infographic.</>, Abelito75),
  change(date(2019, 8, 6), <>Valid for 8.2</>, Abelito75),
  change(date(2019, 8, 2), <>Fixed trait calculations being unaffected by healing increases or decreases.</>, niseko),
  change(date(2019, 7, 23), <>Fixed strange bug with mana tea where if you didn't have it selected it crashed the Overview tab</>, Abelito75),
  change(date(2019, 7, 18), <>Made Way of the Crane infographic.</>, Abelito75),
  change(date(2019, 7, 2), <><SpellLink id={SPELLS.MANA_TEA_TALENT.id} /> rewrite. Includes all spells</>, Abelito75),
  change(date(2019, 7, 2), <>Added Way of the Crane to the cooldowns tab.</>, [Anomoly]),
  change(date(2019, 6, 1), <>Added a check to make sure you using <SpellLink id={SPELLS.SOOTHING_MIST.id} /> efficiency while channeling it.</>, [Abelito75]),
  change(date(2019, 3, 30), <>Added a check to make sure you cast <SpellLink id={SPELLS.SPINNING_CRANE_KICK.id} /> when there are enough targets around.</>, [Abelito75]),
  change(date(2019, 3, 23), <>Updated <SpellLink id={SPELLS.MANA_TEA_TALENT.id} /> to check if you have innervate before counting mana.</>, [Abelito75]),
  change(date(2019, 3, 21), <>Updated mastery tracking to more accurately reflect the spell that triggered the <SpellLink id={SPELLS.GUSTS_OF_MISTS.id} />. This includes updates to the statistic and healing efficiency sections.</>, [Abelito75]),
  change(date(2019, 3, 15), <>Updated Mistweaver Spreadsheet tab to include  <SpellLink id={SPELLS.REFRESHING_JADE_WIND_TALENT.id} /> efficiency.</>, [Abelito75]),
  change(date(2019, 3, 15), <>Added SI to buffs to buffs module to track  Secret Infusion.</>, [Abelito75]),
  change(date(2019, 3, 11), 'Added new Buffs module to track and highlight Mistweaver specific buffs on the timeline', [Anomoly]),
  change(date(2019, 3, 11), 'Updated look and feel of Mistweaver Azerite traits to conform to new 3.0 style.', [Anomoly]),
  change(date(2019, 3, 10), <>Added overhealing check for <SpellLink id={SPELLS.MANA_TEA_TALENT.id} />.</>, [Abelito75]),
  change(date(2019, 2, 25), <>Added statistics, suggestion, and checklist item for  <SpellLink id={SPELLS.SUMMON_JADE_SERPENT_STATUE_TALENT.id} /> casting uptime.</>, [Anomoly]),
  change(date(2019, 2, 21), <>Added statistics for tracking the average stat gain from Secrwet Infusion.</>, [Anomoly]),
  change(date(2019, 2, 7), <>Added statistics for tracking the number of <SpellLink id={SPELLS.RENEWING_MIST.id} /> during <SpellLink id={SPELLS.VIVIFY.id} /> casts. Also, did a quick bug fix for the Monk Spreadsheet import.</>, [Anomoly]),
  change(date(2019, 2, 7), <>Added statistics, suggestions, and checklist item for tracking the number of <SpellLink id={SPELLS.RENEWING_MIST.id} /> during <SpellLink id={SPELLS.MANA_TEA_TALENT.id} />. Also, adding some additional tooltips to the Healing Efficiency page.</>, [Anomoly]),
  change(date(2019, 1, 21), <>Ignore cooldown errors caused by Font of Life (it is not detectable in logs so we can't make it 100% accurate).</>, [Zerotorescue]),
  change(date(2019, 1, 21), <>Fixed a bug where <SpellLink id={SPELLS.SOOTHING_MIST.id} /> incorrectly triggered two GCDs, making downtime off.</>, [Zerotorescue]),
  change(date(2018, 11, 14), <>Added <SpellLink id={SPELLS.GUSTS_OF_MISTS.id}>Gusts of Mists</SpellLink> breakdown chart. Fixed bug with the ReM and Vivify mana efficiency.</>, [Gao]),
  change(date(2018, 11, 2), <>Added the mana efficiency tab. Updated the Env:Viv cast ratio picture sizes.</>, [Gao]),
  change(date(2018, 10, 30), <>Fixed an bug with <SpellLink id={SPELLS.MANA_TEA_TALENT.id} /> suggestion not displaying the correct mana saved amount.</>, [Anomoly]),
  change(date(2018, 9, 11), <>Added <SpellLink id={SPELLS.SUMMON_JADE_SERPENT_STATUE_TALENT.id} /> to spreadsheet tab for use importing into Monk spreadsheet.</>, [Anomoly]),
  change(date(2018, 9, 11), <>Fixed <SpellLink id={SPELLS.SOOTHING_MIST.id} /> channeling time to be taken into account with Downtime statistic and suggestion.</>, [Anomoly]),
  change(date(2018, 9, 11), 'Added healing contribution statistic box for the Mistweaver Specific Azerite traits.', [Anomoly]),
  change(date(2018, 9, 8), 'Updated Checklist to leverage new format and included updates to the suggestions and thresholds. Cleaned up unused legendary files also.', [Anomoly]),
  change(date(2018, 9, 7), 'Updated Player Log Data tab to include fixes and additional data needed to support the BfA Mistweaver Monk spreadsheet.', [Anomoly]),
  change(date(2018, 9, 5), <>Fixed mana cost of all spells. Updated suggestion for <SpellLink id={SPELLS.LIFECYCLES_TALENT.id} /> usage and removed <SpellLink id={SPELLS.SOOTHING_MIST.id} /> suggestion from the analyzer.</>, [Anomoly]),
  change(date(2018, 7, 22), <>Fix crash when Drape of Shame is used.</>, [Anomoly]),
  change(date(2018, 7, 20), 'Pre-Patch Updates: Removed Effuse from the analyzer as the ability was removed in BfA. Updated Mana Costs of spells to use fixed cost versus a percentage of max mana.', [Anomoly]),
  change(date(2018, 7, 19), 'Pre-Patch Updates: Removed Uplifting Trance, Updated Renewing Mist Cooldown, Removed Essence Font from Thunder Focus Tea statistics, Removed Thunder Focus Tea suggestion for now as correct use is being defined', [Anomoly]),
  change(date(2018, 7, 1), 'Trait and Artifact clean up along with GCD changes in Abilities for Battle for Azeroth', [Anomoly]),
  change(date(2018, 6, 15), 'Updated Vivify to incorporate new healing interaction with Renewing Mist. Updated Renewing Mist change to now be a 2 charge spell.', [Anomoly]),
];
<commit_msg>Update CHANGELOG.js<commit_after>import React from 'react';

import { Anomoly, Gao, Zerotorescue, Abelito75, niseko, blazyb, JeremyDwayne, FraunchToost, Tyndi, Moonrabbit } from 'CONTRIBUTORS';
import SPELLS from 'common/SPELLS';
import SpellLink from 'common/SpellLink';
import { change, date } from 'common/changelog';

export default [
  change(date(2020, 10, 1), <>Corrected Nourshing Chi to track all hots. </>, Moonrabbit),
  change(date(2020, 10, 1), <>Updated spell data so stat weights will be accurate. </>, Abelito75),
  change(date(2020, 10, 1), <>Added Statistics for Tear of Morning and Ancient Teachings of the Monastery. </>, Abelito75),
  change(date(2020, 9, 30), <>Integration Tests have been added for Mistweaver. </>, Anomoly),
  change(date(2020, 9, 30), <>Statistic created for the potency conduit Imbued Reflections. </>, Abelito75),
  change(date(2020, 9, 26), <>Added Yu'lon and Chi-ji to the cooldown tracker. </>, Abelito75),
  change(date(2020, 9, 26), <>Added Rising Sun Revival, Jade Bond, and Nourishing Chi. </>, Abelito75),
  change(date(2020, 9, 26), <>Updated Checklist to include Touch of Death, Yu'lon and Chi-ji.</>, Abelito75),
  change(date(2020, 9, 22), <>Updated pieces to the Mistweaver Core for Shadowlands.</>, Tyndi),
  change(date(2020, 9, 22), <>Mistweaver cleanup for shadowlands. </>, Abelito75),
  change(date(2020, 9, 21), <>Added all endurance conduits. </>, Abelito75),
  change(date(2020, 9, 1), <>Initial Shadowlands cleanup activities. </>, Anomoly),
  change(date(2020, 5, 24), <>Updated the Rising Mist modules and generally made tracking hots better. </>,Abelito75),
  change(date(2020, 4, 22), <>Corrected cooldown for Rising Sun Kick and Blackout Kick. </>,Abelito75),
  change(date(2020, 4, 1), <>Added average haste gained statistic from Mist Peaks azerite trait. </>,FraunchToost),
  change(date(2020, 3, 22), <>Fixed <SpellLink id={SPELLS.RISING_MIST_TALENT.id} /> healing for hot extensions. </>,Abelito75),
  change(date(2020, 3, 5), <>Added average time between <SpellLink id={SPELLS.RISING_SUN_KICK.id} /> casts statistic when <SpellLink id={SPELLS.RISING_MIST_TALENT.id} /> is talented. </>,FraunchToost),
  change(date(2020, 2, 27), <>Bugfix to only show <SpellLink id={SPELLS.RISING_SUN_KICK.id} /> cast efficiency suggestion when <SpellLink id={SPELLS.RISING_MIST_TALENT.id} /> is talented. </>,FraunchToost),
  change(date(2020, 1, 8), <>Updated for 8.3 </>,Abelito75),
  change(date(2020, 1, 8), <>Updated stat weight calculation for <SpellLink id={SPELLS.RISING_MIST_TALENT.id} />, added new essence font statistic, tft rsk with rm is a correct use of tft now. </>,Abelito75),
  change(date(2019, 12, 5), <>Updated stat weight calculation for Way of the Crane and Lucid Dreams. </>,Anomoly),
  change(date(2019, 11, 25), <>Added a Essence Font healing breakdown donut. </>,Abelito75),
  change(date(2019, 11, 25), <>Fixed upwelling randomly bugging out. </>,Abelito75),
  change(date(2019, 11, 20), <>Added Uplift revival reduction to rising mist tooltip. </>,Abelito75),
  change(date(2019, 11, 16), <>Updated a few spells to use new methods. </>,Abelito75),
  change(date(2019, 11, 12), <>Big Rising Mist overhaul. Now includes extra Vivify cleaves, Enveloping Mist, and Gusts Of mist hits from Essence Font's Hot. </>,Abelito75),
  change(date(2019, 11, 9), <>Added upwelling statistic. </>,Abelito75),
  change(date(2019, 11, 9), <>Fixed Tier45 comparison if you didn't play in a way where mana would be returned/saved. </>,Abelito75),
  change(date(2019, 10, 14), <>Fixed small typos for rising mist statistic box. </>,Abelito75),
  change(date(2019, 10, 14), <>Spelling and grammar corrections in Tier 45 comparison tool. </>,JeremyDwayne),
  change(date(2019, 10, 13), <>Fixed Tier 45 comparison tool. </>,Abelito75),
  change(date(2019, 10, 13), <>New Tier 45 comparison tool to see if you picked the best talent for your playstyle. </>,Abelito75),
  change(date(2019, 10, 4), <>Added missing array declaration for enemiesHitSCK <SpellLink id={SPELLS.SPINNING_CRANE_KICK.id} />.</>, JeremyDwayne),
  change(date(2019, 10, 1), <>Bugfix for Lifecycles adding the potential mana reduction from an empowered <SpellLink id={SPELLS.VIVIFY.id} /> </>,Anomoly),
  change(date(2019, 9, 27), <>Update to date for 8.2.5 </>,Abelito75),
  change(date(2019, 8, 17), <>Fixed bug where you could have NaN mana tea overhealing.</>, Abelito75),
  change(date(2019, 8, 14), <>Wotc infographic now checks for physical damage only.</>, Abelito75),
  change(date(2019, 8, 12), 'Added essence Lucid Dreams.', [blazyb]),
  change(date(2019, 8, 10), <>Fixed bug where if you end the fight with wotc buff it said everything was 0 hps. Added more info the the wotc infographic.</>, Abelito75),
  change(date(2019, 8, 6), <>Valid for 8.2</>, Abelito75),
  change(date(2019, 8, 2), <>Fixed trait calculations being unaffected by healing increases or decreases.</>, niseko),
  change(date(2019, 7, 23), <>Fixed strange bug with mana tea where if you didn't have it selected it crashed the Overview tab</>, Abelito75),
  change(date(2019, 7, 18), <>Made Way of the Crane infographic.</>, Abelito75),
  change(date(2019, 7, 2), <><SpellLink id={SPELLS.MANA_TEA_TALENT.id} /> rewrite. Includes all spells</>, Abelito75),
  change(date(2019, 7, 2), <>Added Way of the Crane to the cooldowns tab.</>, [Anomoly]),
  change(date(2019, 6, 1), <>Added a check to make sure you using <SpellLink id={SPELLS.SOOTHING_MIST.id} /> efficiency while channeling it.</>, [Abelito75]),
  change(date(2019, 3, 30), <>Added a check to make sure you cast <SpellLink id={SPELLS.SPINNING_CRANE_KICK.id} /> when there are enough targets around.</>, [Abelito75]),
  change(date(2019, 3, 23), <>Updated <SpellLink id={SPELLS.MANA_TEA_TALENT.id} /> to check if you have innervate before counting mana.</>, [Abelito75]),
  change(date(2019, 3, 21), <>Updated mastery tracking to more accurately reflect the spell that triggered the <SpellLink id={SPELLS.GUSTS_OF_MISTS.id} />. This includes updates to the statistic and healing efficiency sections.</>, [Abelito75]),
  change(date(2019, 3, 15), <>Updated Mistweaver Spreadsheet tab to include  <SpellLink id={SPELLS.REFRESHING_JADE_WIND_TALENT.id} /> efficiency.</>, [Abelito75]),
  change(date(2019, 3, 15), <>Added SI to buffs to buffs module to track  Secret Infusion.</>, [Abelito75]),
  change(date(2019, 3, 11), 'Added new Buffs module to track and highlight Mistweaver specific buffs on the timeline', [Anomoly]),
  change(date(2019, 3, 11), 'Updated look and feel of Mistweaver Azerite traits to conform to new 3.0 style.', [Anomoly]),
  change(date(2019, 3, 10), <>Added overhealing check for <SpellLink id={SPELLS.MANA_TEA_TALENT.id} />.</>, [Abelito75]),
  change(date(2019, 2, 25), <>Added statistics, suggestion, and checklist item for  <SpellLink id={SPELLS.SUMMON_JADE_SERPENT_STATUE_TALENT.id} /> casting uptime.</>, [Anomoly]),
  change(date(2019, 2, 21), <>Added statistics for tracking the average stat gain from Secrwet Infusion.</>, [Anomoly]),
  change(date(2019, 2, 7), <>Added statistics for tracking the number of <SpellLink id={SPELLS.RENEWING_MIST.id} /> during <SpellLink id={SPELLS.VIVIFY.id} /> casts. Also, did a quick bug fix for the Monk Spreadsheet import.</>, [Anomoly]),
  change(date(2019, 2, 7), <>Added statistics, suggestions, and checklist item for tracking the number of <SpellLink id={SPELLS.RENEWING_MIST.id} /> during <SpellLink id={SPELLS.MANA_TEA_TALENT.id} />. Also, adding some additional tooltips to the Healing Efficiency page.</>, [Anomoly]),
  change(date(2019, 1, 21), <>Ignore cooldown errors caused by Font of Life (it is not detectable in logs so we can't make it 100% accurate).</>, [Zerotorescue]),
  change(date(2019, 1, 21), <>Fixed a bug where <SpellLink id={SPELLS.SOOTHING_MIST.id} /> incorrectly triggered two GCDs, making downtime off.</>, [Zerotorescue]),
  change(date(2018, 11, 14), <>Added <SpellLink id={SPELLS.GUSTS_OF_MISTS.id}>Gusts of Mists</SpellLink> breakdown chart. Fixed bug with the ReM and Vivify mana efficiency.</>, [Gao]),
  change(date(2018, 11, 2), <>Added the mana efficiency tab. Updated the Env:Viv cast ratio picture sizes.</>, [Gao]),
  change(date(2018, 10, 30), <>Fixed an bug with <SpellLink id={SPELLS.MANA_TEA_TALENT.id} /> suggestion not displaying the correct mana saved amount.</>, [Anomoly]),
  change(date(2018, 9, 11), <>Added <SpellLink id={SPELLS.SUMMON_JADE_SERPENT_STATUE_TALENT.id} /> to spreadsheet tab for use importing into Monk spreadsheet.</>, [Anomoly]),
  change(date(2018, 9, 11), <>Fixed <SpellLink id={SPELLS.SOOTHING_MIST.id} /> channeling time to be taken into account with Downtime statistic and suggestion.</>, [Anomoly]),
  change(date(2018, 9, 11), 'Added healing contribution statistic box for the Mistweaver Specific Azerite traits.', [Anomoly]),
  change(date(2018, 9, 8), 'Updated Checklist to leverage new format and included updates to the suggestions and thresholds. Cleaned up unused legendary files also.', [Anomoly]),
  change(date(2018, 9, 7), 'Updated Player Log Data tab to include fixes and additional data needed to support the BfA Mistweaver Monk spreadsheet.', [Anomoly]),
  change(date(2018, 9, 5), <>Fixed mana cost of all spells. Updated suggestion for <SpellLink id={SPELLS.LIFECYCLES_TALENT.id} /> usage and removed <SpellLink id={SPELLS.SOOTHING_MIST.id} /> suggestion from the analyzer.</>, [Anomoly]),
  change(date(2018, 7, 22), <>Fix crash when Drape of Shame is used.</>, [Anomoly]),
  change(date(2018, 7, 20), 'Pre-Patch Updates: Removed Effuse from the analyzer as the ability was removed in BfA. Updated Mana Costs of spells to use fixed cost versus a percentage of max mana.', [Anomoly]),
  change(date(2018, 7, 19), 'Pre-Patch Updates: Removed Uplifting Trance, Updated Renewing Mist Cooldown, Removed Essence Font from Thunder Focus Tea statistics, Removed Thunder Focus Tea suggestion for now as correct use is being defined', [Anomoly]),
  change(date(2018, 7, 1), 'Trait and Artifact clean up along with GCD changes in Abilities for Battle for Azeroth', [Anomoly]),
  change(date(2018, 6, 15), 'Updated Vivify to incorporate new healing interaction with Renewing Mist. Updated Renewing Mist change to now be a 2 charge spell.', [Anomoly]),
];
<|endoftext|>


<commit_before>"use strict";

var FakeObjectDataListStore = require('../examples/FakeObjectDataListStore');
var FixedDataTable = require('fixed-data-table');
var React = require('react');
var Constants = require('../Constants');

var Table = FixedDataTable.Table;
var Column = FixedDataTable.Column;

// Require common FixedDataTable CSS.
require('fixed-data-table/css/layout/ScrollbarLayout.css');
require('fixed-data-table/css/layout/fixedDataTableLayout.css');
require('fixed-data-table/css/layout/fixedDataTableCellLayout.css');
require('fixed-data-table/css/layout/fixedDataTableCellGroupLayout.css');
require('fixed-data-table/css/layout/fixedDataTableColumnResizerLineLayout.css');
require('fixed-data-table/css/layout/fixedDataTableRowLayout.css');

require('fixed-data-table/css/style/fixedDataTable.css');
require('fixed-data-table/css/style/fixedDataTableCell.css');
require('fixed-data-table/css/style/fixedDataTableColumnResizerLine.css');
require('fixed-data-table/css/style/fixedDataTableRow.css');
require('fixed-data-table/css/style/Scrollbar.css');

var HeroTable = React.createClass({

  getInitialState() {
    return {
      dataList: new FakeObjectDataListStore()
    }
  },

  _rowGetter(index) {
    return this.state.dataList.getObjectAt(index);
  },

  render() {
    return (
      <Table
        scrollLeft={this.props.scrollLeft}
        scrollTop={this.props.scrollTop}
        overflowX="hidden"
        overflowY="hidden"
        rowHeight={50}
        headerHeight={50}
        rowGetter={this._rowGetter}
        rowsCount={this.state.dataList.getSize()}
        width={this.props.tableWidth}
        height={this.props.tableHeight}>
        <Column
          flexGrow={1}
          dataKey="firstName"
          fixed={true}
          label="First Name"
          width={150}
        />
        <Column
          flexGrow={1}
          dataKey="lastName"
          fixed={true}
          label="Last Name"
          width={120}
        />
        <Column
          flexGrow={1}
          dataKey="city"
          label="City"
          width={200}
        />
        <Column
          label="Street"
          width={200}
          dataKey="street"
        />
        <Column
          label="Zip Code"
          width={200}
          dataKey="zipCode"
        />
        <Column
          label="Email"
          width={200}
          dataKey="email"
        />
        <Column
          label="DOB"
          width={400}
          dataKey="date"
        />
        <Column
          flexGrow={1}
          dataKey="city"
          label="City"
          width={400}
        />
        <Column
          dataKey="bs"
          label="BS!"
          width={300}
        />
        <Column
          dataKey="catchPhrase"
          label="Catch Phrase"
          width={400}
        />
        <Column
          dataKey="companyName"
          label="Company Name"
          width={700}
        />
      </Table>
    );
  }
});

module.exports = HeroTable;
<commit_msg>herotable example<commit_after>"use strict";

var FakeObjectDataListStore = require('../examples/FakeObjectDataListStore');
var FixedDataTable = require('fixed-data-table');
var React = require('react');
var Constants = require('../Constants');

var Table = FixedDataTable.Table;
var Column = FixedDataTable.Column;
var Cell = FixedDataTable.Cell;

// Require common FixedDataTable CSS.
require('fixed-data-table/css/layout/ScrollbarLayout.css');
require('fixed-data-table/css/layout/fixedDataTableLayout.css');
require('fixed-data-table/css/layout/fixedDataTableCellLayout.css');
require('fixed-data-table/css/layout/fixedDataTableCellGroupLayout.css');
require('fixed-data-table/css/layout/fixedDataTableColumnResizerLineLayout.css');
require('fixed-data-table/css/layout/fixedDataTableRowLayout.css');

require('fixed-data-table/css/style/fixedDataTable.css');
require('fixed-data-table/css/style/fixedDataTableCell.css');
require('fixed-data-table/css/style/fixedDataTableColumnResizerLine.css');
require('fixed-data-table/css/style/fixedDataTableRow.css');
require('fixed-data-table/css/style/Scrollbar.css');

var TextCell = React.createClass({
  _getData() {
    return this.props.data.getObjectAt(this.props.rowIndex)[this.props.columnKey];
  },
  render() {
    return (
      <Cell
        {...this.props}>
        {this._getData()}
      </Cell>
    )
  }
})

var dataList = new FakeObjectDataListStore()

var HeroTable = React.createClass({

  render() {
    return (
      <Table
        scrollLeft={this.props.scrollLeft}
        scrollTop={this.props.scrollTop}
        overflowX="hidden"
        overflowY="hidden"
        rowHeight={50}
        headerHeight={50}
        rowsCount={dataList.getSize()}
        width={this.props.tableWidth}
        height={this.props.tableHeight}>
        <Column
          flexGrow={1}
          columnKey="firstName"
          fixed={true}
          header="First Name"
          cell={<TextCell data={dataList} />}
          width={150}
        />
        <Column
          flexGrow={1}
          columnKey="lastName"
          fixed={true}
          header="Last Name"
          cell={<TextCell data={dataList} />}
          width={120}
        />
        <Column
          flexGrow={1}
          columnKey="city"
          header="City"
          cell={<TextCell data={dataList} />}
          width={200}
        />
        <Column
          header="Street"
          width={200}
          columnKey="street"
          cell={<TextCell data={dataList} />}
        />
        <Column
          header="Zip Code"
          width={200}
          columnKey="zipCode"
          cell={<TextCell data={dataList} />}
        />
        <Column
          header="Email"
          width={200}
          columnKey="email"
          cell={<TextCell data={dataList} />}
        />
        <Column
          header="DOB"
          width={400}
          columnKey="date"
          cell={<TextCell data={dataList} />}
        />
        <Column
          flexGrow={1}
          columnKey="city"
          header="City"
          width={400}
          cell={<TextCell data={dataList} />}
        />
        <Column
          columnKey="bs"
          header="BS!"
          width={300}
          cell={<TextCell data={dataList} />}
        />
        <Column
          columnKey="catchPhrase"
          header="Catch Phrase"
          width={400}
          cell={<TextCell data={dataList} />}
        />
        <Column
          columnKey="companyName"
          header="Company Name"
          width={700}
          cell={<TextCell data={dataList} />}
        />
      </Table>
    );
  }
});

module.exports = HeroTable;
<|endoftext|>


<commit_before>import { getOwner } from '@ember/application';
import ENUMS from 'irene/enums';
import { moduleForModel, test } from 'ember-qunit';
import localeConfig from 'ember-i18n/config/en';
import { run } from '@ember/runloop';

moduleForModel('file', 'Unit | Model | file', {
  needs: [
    'model:project',
    'model:profile',
    'model:organization-project',
    'model:file',
    'model:user',
    'model:analysis',
    'service:i18n',
    'locale:en/translations',
    'locale:en/config',
    'util:i18n/missing-message',
    'util:i18n/compile-template',
    'config:environment'
  ],
  beforeEach() {
    // set the locale and the config
    getOwner(this).lookup('service:i18n').set('locale', 'en');
    this.register('locale:en/config', localeConfig);
  }
});

test('it passes', function(assert) {
  run(() => {
    const file = getOwner(this).lookup(
      'service:store'
    ).createRecord('file', {
      'id': 1
    });
    assert.equal(file.get('isManualRequested'), true, "Manual Requested");

    assert.equal(file.get('isRunningApiScan'), true, "API Scan");
    file.set('apiScanProgress', 100);
    assert.equal(file.get('isRunningApiScan'), false, "API Scan not done");

    assert.equal(file.scanProgressClass(), false, "Scan Progress Class");
    assert.equal(file.scanProgressClass(true), true, "Scan Progress Class");

    assert.equal(file.get('isStaticCompleted'), false, "Static Scan");

    assert.equal(file.get('isNoneStatus'), false, "None Status");

    assert.equal(file.get('isReady'), false, "Is Ready");

    assert.equal(file.get('isNeitherNoneNorReady'), true, "Is Not None Nor Ready");

    assert.equal(file.get('statusText'), "Unknown Status", 'Unknown Status');
    file.set('dynamicStatus', ENUMS.DYNAMIC_STATUS.BOOTING);
    assert.equal(file.get('statusText.string'), "Booting", 'Booting');
    file.set('dynamicStatus', ENUMS.DYNAMIC_STATUS.DOWNLOADING);
    assert.equal(file.get('statusText.string'), "Downloading", 'Downloading');
    file.set('dynamicStatus', ENUMS.DYNAMIC_STATUS.INSTALLING);
    assert.equal(file.get('statusText.string'), "Installing", 'Installing');
    file.set('dynamicStatus', ENUMS.DYNAMIC_STATUS.LAUNCHING);
    assert.equal(file.get('statusText.string'), "Launching", 'Launching');
    file.set('dynamicStatus', ENUMS.DYNAMIC_STATUS.HOOKING);
    assert.equal(file.get('statusText.string'), "Starting", 'Hooking');
    file.set('dynamicStatus', ENUMS.DYNAMIC_STATUS.SHUTTING_DOWN);
    assert.equal(file.get('statusText.string'), "Stopping", 'Shutting Down');

    assert.equal(file.setBootingStatus(), undefined, "Set Booting Status");

    assert.equal(file.setShuttingDown(), undefined, "Set Booting Status");

    assert.equal(file.setNone(), undefined, "Set Booting Status");

    assert.equal(file.setReady(), undefined, "Set Booting Status");

  });
});
<commit_msg>fix: test case<commit_after>import { getOwner } from '@ember/application';
import ENUMS from 'irene/enums';
import { moduleForModel, test } from 'ember-qunit';
import localeConfig from 'ember-i18n/config/en';
import { run } from '@ember/runloop';

moduleForModel('file', 'Unit | Model | file', {
  needs: [
    'model:project',
    'model:profile',
    'model:organization-project',
    'model:file',
    'model:user',
    'model:analysis',
    'service:i18n',
    'locale:en/translations',
    'locale:en/config',
    'util:i18n/missing-message',
    'util:i18n/compile-template',
    'config:environment'
  ],
  beforeEach() {
    // set the locale and the config
    getOwner(this).lookup('service:i18n').set('locale', 'en');
    this.register('locale:en/config', localeConfig);
  }
});

test('it passes', function(assert) {
  run(() => {
    const file = getOwner(this).lookup(
      'service:store'
    ).createRecord('file', {
      'id': 1
    });
    assert.equal(file.get('isManualRequested'), true, "Manual Requested");

    assert.equal(file.get('isRunningApiScan'), false, "API Scan");
    file.set('apiScanProgress', 100);
    assert.equal(file.get('isRunningApiScan'), false, "API Scan not done");

    assert.equal(file.scanProgressClass(), false, "Scan Progress Class");
    assert.equal(file.scanProgressClass(true), true, "Scan Progress Class");

    assert.equal(file.get('isStaticCompleted'), false, "Static Scan");

    assert.equal(file.get('isNoneStatus'), false, "None Status");

    assert.equal(file.get('isReady'), false, "Is Ready");

    assert.equal(file.get('isNeitherNoneNorReady'), true, "Is Not None Nor Ready");

    assert.equal(file.get('statusText'), "Unknown Status", 'Unknown Status');
    file.set('dynamicStatus', ENUMS.DYNAMIC_STATUS.BOOTING);
    assert.equal(file.get('statusText.string'), "Booting", 'Booting');
    file.set('dynamicStatus', ENUMS.DYNAMIC_STATUS.DOWNLOADING);
    assert.equal(file.get('statusText.string'), "Downloading", 'Downloading');
    file.set('dynamicStatus', ENUMS.DYNAMIC_STATUS.INSTALLING);
    assert.equal(file.get('statusText.string'), "Installing", 'Installing');
    file.set('dynamicStatus', ENUMS.DYNAMIC_STATUS.LAUNCHING);
    assert.equal(file.get('statusText.string'), "Launching", 'Launching');
    file.set('dynamicStatus', ENUMS.DYNAMIC_STATUS.HOOKING);
    assert.equal(file.get('statusText.string'), "Starting", 'Hooking');
    file.set('dynamicStatus', ENUMS.DYNAMIC_STATUS.SHUTTING_DOWN);
    assert.equal(file.get('statusText.string'), "Stopping", 'Shutting Down');

    assert.equal(file.setBootingStatus(), undefined, "Set Booting Status");

    assert.equal(file.setShuttingDown(), undefined, "Set Booting Status");

    assert.equal(file.setNone(), undefined, "Set Booting Status");

    assert.equal(file.setReady(), undefined, "Set Booting Status");

  });
});
<|endoftext|>


<commit_before><commit_msg>change the message to: 1) unusual spending should call fetch then categorize then email:      Error: Unsatisfied verification on test double .<commit_after>export const email = () => {};
<|endoftext|>