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

    angular
        .module('dashboard')
        .service('dashboardObject', DashboardObjectFunction);

    DashboardObjectFunction.$inject = ['$injector'];

    /**
     * @ngdoc service
     * @module dashboard
     * @name dashboardObject
     * @description
     *
     * This is a dashboard object. Allow you to create an object.
     */
    function DashboardObjectFunction($injector) {

        /* jshint maxdepth: 10 */

        // Return a function to be used as new User();
        return function(params) {
            var DEFAULT_DASHBOARD = {
                // ID : string to identify a dashboard
                id: null,
                // auto_increment when add a component. To generate unique ID
                nbComponent: 0,
                // Used in template to know if dashboard is extended or not
                isExtended: false,
                // List of all component
                components: [],
                // Array of columns. Contain all component
                grid: [],

                // Define if dashboard is in sortable state
                isStateSorting: false, // Activate disable shaking state

                // This array contain list of sortable columns
                sortable: null, // Array of columns objects

                // stored option to manage dashboard configuration.
                options: {
                    // Full width of entiere dashboard
                    'width': 'auto',
                    // Number of columns in dashboard
                    'columns': '2',
                    // Min widht of columns.
                    'columnsMinWidth': null,
                    // Enable/disable sorting
                    'sortable': true,
                    // Algorithm to define where to put component if no column
                    // can be shorter, or Random
                    'algo': 'shorter'
                },

                /**
                 * List of function to add
                 */
                add: add,
                setOptions: setOptions,
                toString: toString,
                fromString: fromString,
                drawGrid: drawGrid,
                toggleSortable: toggleSortable
            };

            var dashboardObject = DEFAULT_DASHBOARD;

            return dashboardObject;

            /**
             * Add a component in array
             * @param {Object} component    Dashboard component
             * @param {integer} column      Column number, starting at zero
             */
            function add(component, column) {

                // Define component ID
                component.id = dashboardObject.id + '-' + dashboardObject.nbComponent;
                dashboardObject.nbComponent++;
                // Add in list
                dashboardObject.components.push(component);

            }

            /**
             * Put all components in grid based on algo OR defined position saved in string.
             */
            function drawGrid() {
                // If is on sorting mode, we stop it
                if (dashboardObject.isStateSorting) {
                    dashboardObject.toggleSortable();
                }

                dashboardObject.grid = [];
                dashboardObject.sortable = null;
                // For each component, we define its position and inject it in our grid object.
                // Grid is displayed in DOM by dashboard.directive.js
                dashboardObject.components.forEach(function (component) {

                    var column = 0, position = 0;
                    var nbColumn = dashboardObject.options['columns'];

                    // Check if position is define
                    if (component.positions && component.positions[nbColumn]) {
                        column = component.positions[nbColumn]['column'];
                        position = component.positions[nbColumn]['position'];
                    } else {

                        // We use algo to define component position
                        if (dashboardObject.options['algo'] === 'shorter') {
                            // For each column starting by the end, we check size
                            for (var i = dashboardObject.options['columns'] - 1; i >= 0; i--) {
                                // if column i in grid does not exist
                                if (!dashboardObject.grid[i]) {
                                    column = i;
                                    dashboardObject.grid[i] = [];
                                } else {
                                    // Si it exist
                                    if (dashboardObject.grid[i].length <= dashboardObject.grid[column].length) {
                                        column = i;
                                    }
                                }
                            }
                        } else if (dashboardObject.options['algo'] === 'random'){
                            column = Math.floor(Math.random() * dashboardObject.options['columns']);
                        } else {
                            column = 0;
                        }

                        // define position of defined column. Get last position.
                        if (dashboardObject.grid[column]) {
                            position = dashboardObject.grid[column].length;
                        }

                        // We save new position in our
                        if (!component.positions) {
                            component.positions = {};
                        }
                        component.positions[nbColumn] = {};
                        component.positions[nbColumn]['column'] = column;
                        component.positions[nbColumn]['position'] = position;
                    }

                    // If grid never used this column before, create one.
                    if (!dashboardObject.grid[column]) {
                        dashboardObject.grid[column] = [];
                    }
                    // Add compoment in grid to defined position
                    dashboardObject.grid[column].splice(position, 0, component);

                });
            }

            /**
             * Set dashboard options.
             */
            function setOptions(newOptions) {
                // For each new option we override current one.
                Object.keys(newOptions).forEach(function(key) {
                    if (newOptions[key]) {
                        dashboardObject.options[key] = newOptions[key];
                    }
                });
            }

            /**
             * Convert the dashboard to a String
             * @return {String} Dashboard as a String
             */
            function toString() {
                var tmpColumns = [];
                var tmpColumn = [];

                // For each column in grid
                dashboardObject.grid.forEach(function(column) {
                    tmpColumn = [];
                    column.forEach(function(component) {
                        tmpColumn.push({
                            name: component.name,
                            params: component.params,
                            positions: component.positions
                        });
                    });
                    tmpColumns.push(tmpColumn);
                });

                return JSON.stringify(tmpColumns);
            }

            /**
             * Create a dashboard from a String
             * @param  {String} dashboardString Dashboard as a String
             */
            function fromString(dashboardString) {
                var tmpColumns = JSON.parse(dashboardString);
                var nbColumns = 0;

                tmpColumns.forEach(function(column) {
                    column.forEach(function(component) {
                        if ($injector.has(component.name)) {
                            add(new $injector.get(component.name)(component.params, component.positions), nbColumns);
                        }
                    });
                    nbColumns++;
                });
            }

            // Apply drag/drop to angular MVC (alias grid object)
            function sortAllComponents(evt) {

                // Identify columns
                var oldColumn = evt.from.id.replace('column', '');
                var newColumn = evt.to.id.replace('column', '');

                // Get component as tmp
                var component = dashboardObject.grid[oldColumn][evt.oldIndex];
                // Remove old component
                dashboardObject.grid[oldColumn].splice(evt.oldIndex, 1);

                if (evt.newIndex === 0) {
                    console.log(dashboardObject.grid[newColumn]);
                }
                // Add component to new location
                dashboardObject.grid[newColumn].splice(evt.newIndex, 0, component);

                if (evt.newIndex === 0) {
                    console.log(dashboardObject.grid[newColumn]);
                }

            }

            /**
             * Apply Sortable to HTML and make it draggable/droppable
             */
            function makeItSortable() {

                // If columns have already been initialize
                if (!dashboardObject.sortable) {
                    dashboardObject.sortable = [];

                    // apply sortable on each column.
                    for (var i = 0; i < dashboardObject.grid.length; i++) {

                        dashboardObject.sortable.push(
                            Sortable.create(document.getElementById('column' + i), {
                                group: dashboardObject.id,
                                draggable: '.component',
                                disabled: !dashboardObject.isStateSorting, // No databinding here, need to be updated
                                handle: '.sortable-handle',
                                onAdd: function(evt) {
                                    // Event triggered when add in column
                                    sortAllComponents(evt);
                                },
                                onUpdate: function(evt) {
                                    // event triggered when column is changed
                                    sortAllComponents(evt);
                                }
                            })
                        );
                    }
                }
            }

            /**
             * This function enable/disable sorting state of dashboard
             */
            function toggleSortable() {
                // If dashboard is sortable by user
                if (!dashboardObject.options['sortable']) {
                    console.log('This dashboard does not allow sorting (see options configuration).');
                } else {
                    makeItSortable();
                    // Toggle sorting state
                    dashboardObject.isStateSorting = !dashboardObject.isStateSorting;
                    // Change disable option for each column
                    dashboardObject.sortable.forEach(function(sort) {
                        sort.option('disabled', !dashboardObject.isStateSorting);
                    });
                }
            }
        };
    }
})();
<commit_msg>Implement a disableExtended method in dashboard Object.<commit_after>(function() {
    'use strict';

    angular
        .module('dashboard')
        .service('dashboardObject', DashboardObjectFunction);

    DashboardObjectFunction.$inject = ['$injector'];

    /**
     * @ngdoc service
     * @module dashboard
     * @name dashboardObject
     * @description
     *
     * This is a dashboard object. Allow you to create an object.
     */
    function DashboardObjectFunction($injector) {

        /* jshint maxdepth: 10 */

        // Return a function to be used as new User();
        return function(params) {
            var DEFAULT_DASHBOARD = {
                // ID : string to identify a dashboard
                id: null,
                // auto_increment when add a component. To generate unique ID
                nbComponent: 0,
                // Used in template to know if dashboard is extended or not
                isExtended: false,
                // List of all component
                components: [],
                // Array of columns. Contain all component
                grid: [],

                // Define if dashboard is in sortable state
                isStateSorting: false, // Activate disable shaking state

                // This array contain list of sortable columns
                sortable: null, // Array of columns objects

                // stored option to manage dashboard configuration.
                options: {
                    // Full width of entiere dashboard
                    'width': 'auto',
                    // Number of columns in dashboard
                    'columns': '2',
                    // Min widht of columns.
                    'columnsMinWidth': null,
                    // Enable/disable sorting
                    'sortable': true,
                    // Algorithm to define where to put component if no column
                    // can be shorter, or Random
                    'algo': 'shorter'
                },

                /**
                 * List of function to add
                 */
                add: add,
                setOptions: setOptions,
                toString: toString,
                fromString: fromString,
                disableExtended: disableExtended,
                drawGrid: drawGrid,
                toggleSortable: toggleSortable
            };

            var dashboardObject = DEFAULT_DASHBOARD;

            return dashboardObject;

            /**
             * Add a component in array
             * @param {Object} component    Dashboard component
             * @param {integer} column      Column number, starting at zero
             */
            function add(component, column) {

                // Define component ID
                component.id = dashboardObject.id + '-' + dashboardObject.nbComponent;
                dashboardObject.nbComponent++;
                // Add in list
                dashboardObject.components.push(component);

            }

            /**
             * Put all components in grid based on algo OR defined position saved in string.
             */
            function drawGrid() {
                // If is on sorting mode, we stop it
                if (dashboardObject.isStateSorting) {
                    dashboardObject.toggleSortable();
                }

                dashboardObject.grid = [];
                dashboardObject.sortable = null;
                // For each component, we define its position and inject it in our grid object.
                // Grid is displayed in DOM by dashboard.directive.js
                dashboardObject.components.forEach(function (component) {

                    var column = 0, position = 0;
                    var nbColumn = dashboardObject.options['columns'];

                    // Check if position is define
                    if (component.positions && component.positions[nbColumn]) {
                        column = component.positions[nbColumn]['column'];
                        position = component.positions[nbColumn]['position'];
                    } else {

                        // We use algo to define component position
                        if (dashboardObject.options['algo'] === 'shorter') {
                            // For each column starting by the end, we check size
                            for (var i = dashboardObject.options['columns'] - 1; i >= 0; i--) {
                                // if column i in grid does not exist
                                if (!dashboardObject.grid[i]) {
                                    column = i;
                                    dashboardObject.grid[i] = [];
                                } else {
                                    // Si it exist
                                    if (dashboardObject.grid[i].length <= dashboardObject.grid[column].length) {
                                        column = i;
                                    }
                                }
                            }
                        } else if (dashboardObject.options['algo'] === 'random'){
                            column = Math.floor(Math.random() * dashboardObject.options['columns']);
                        } else {
                            column = 0;
                        }

                        // define position of defined column. Get last position.
                        if (dashboardObject.grid[column]) {
                            position = dashboardObject.grid[column].length;
                        }

                        // We save new position in our
                        if (!component.positions) {
                            component.positions = {};
                        }
                        component.positions[nbColumn] = {};
                        component.positions[nbColumn]['column'] = column;
                        component.positions[nbColumn]['position'] = position;
                    }

                    // If grid never used this column before, create one.
                    if (!dashboardObject.grid[column]) {
                        dashboardObject.grid[column] = [];
                    }
                    // Add compoment in grid to defined position
                    dashboardObject.grid[column].splice(position, 0, component);

                });
            }

            /**
             * Set dashboard options.
             */
            function setOptions(newOptions) {
                // For each new option we override current one.
                Object.keys(newOptions).forEach(function(key) {
                    if (newOptions[key]) {
                        dashboardObject.options[key] = newOptions[key];
                    }
                });
            }

            /**
             * Convert the dashboard to a String
             * @return {String} Dashboard as a String
             */
            function toString() {
                var tmpColumns = [];
                var tmpColumn = [];

                // For each column in grid
                dashboardObject.grid.forEach(function(column) {
                    tmpColumn = [];
                    column.forEach(function(component) {
                        tmpColumn.push({
                            name: component.name,
                            params: component.params,
                            positions: component.positions
                        });
                    });
                    tmpColumns.push(tmpColumn);
                });

                return JSON.stringify(tmpColumns);
            }

            /**
             * Create a dashboard from a String
             * @param  {String} dashboardString Dashboard as a String
             */
            function fromString(dashboardString) {
                var tmpColumns = JSON.parse(dashboardString);
                var nbColumns = 0;

                tmpColumns.forEach(function(column) {
                    column.forEach(function(component) {
                        if ($injector.has(component.name)) {
                            add(new $injector.get(component.name)(component.params, component.positions), nbColumns);
                        }
                    });
                    nbColumns++;
                });
            }

            // Apply drag/drop to angular MVC (alias grid object)
            function sortAllComponents(evt) {

                // Identify columns
                var oldColumn = evt.from.id.replace('column', '');
                var newColumn = evt.to.id.replace('column', '');

                // Get component as tmp
                var component = dashboardObject.grid[oldColumn][evt.oldIndex];
                // Remove old component
                dashboardObject.grid[oldColumn].splice(evt.oldIndex, 1);

                if (evt.newIndex === 0) {
                    console.log(dashboardObject.grid[newColumn]);
                }
                // Add component to new location
                dashboardObject.grid[newColumn].splice(evt.newIndex, 0, component);

                if (evt.newIndex === 0) {
                    console.log(dashboardObject.grid[newColumn]);
                }

            }

            /**
             * Apply Sortable to HTML and make it draggable/droppable
             */
            function makeItSortable() {

                // If columns have already been initialize
                if (!dashboardObject.sortable) {
                    dashboardObject.sortable = [];

                    // apply sortable on each column.
                    for (var i = 0; i < dashboardObject.grid.length; i++) {

                        dashboardObject.sortable.push(
                            Sortable.create(document.getElementById('column' + i), {
                                group: dashboardObject.id,
                                draggable: '.component',
                                disabled: !dashboardObject.isStateSorting, // No databinding here, need to be updated
                                handle: '.sortable-handle',
                                onAdd: function(evt) {
                                    // Event triggered when add in column
                                    sortAllComponents(evt);
                                },
                                onUpdate: function(evt) {
                                    // event triggered when column is changed
                                    sortAllComponents(evt);
                                }
                            })
                        );
                    }
                }
            }

            /**
             * This function enable/disable sorting state of dashboard
             */
            function toggleSortable() {
                // If dashboard is sortable by user
                if (!dashboardObject.options['sortable']) {
                    console.log('This dashboard does not allow sorting (see options configuration).');
                } else {
                    makeItSortable();
                    // Toggle sorting state
                    dashboardObject.isStateSorting = !dashboardObject.isStateSorting;
                    // Change disable option for each column
                    dashboardObject.sortable.forEach(function(sort) {
                        sort.option('disabled', !dashboardObject.isStateSorting);
                    });
                }
            }

            /**
             * This function disable extended dashboard to make it as default
             */
            function disableExtended() {
                dashboardObject.isExtended = false;

                dashboardObject.components.forEach(function(component) {
                    if (component.isExtended) {
                        component.isExtended = false;
                    }
                });

            }
        };
    }
})();
<|endoftext|>


<commit_before>var utility = require('../utility');
var generateJob = utility.generateJob;
var async = require('async');
var getData = utility.getData;
var queries = require('../queries');
var insertMatch = queries.insertMatch;
var db = require('../db');
var queue = require('../queue');
var redis = require('../redis');
var args = process.argv.slice(2);
var match_seq_num = args[0] || 0;
getPage();
//match seq num 59622 has a 32-bit unsigned int max (4294967295) in one of the players' tower damage
//match seq num 239190 for hero_healing
//match seq num 542284 for hero_healing
//may need to cap values down to 2.1b if we encounter them
//postgres int type only supports up to 2.1b (signed int)
//enable add all matches
//delete fullhistory from sign-in flow
//track all matches start seq num
//run script to backfill matches
//update FAQ
function getPage()
{
    var job = generateJob("api_sequence",
    {
        start_at_match_seq_num: match_seq_num
    });
    var url = job.url;
    getData(
    {
        url: url,
        delay: 1
    }, function(err, body)
    {
        if (err)
        {
            throw err;
        }
        if (body.result)
        {
            var matches = body.result.matches;
            async.each(matches, function(m, cb)
            {
                insertMatch(db, redis, queue, m,
                {
                    type: "api",
                    skipCacheUpdate: true,
                    skipAbilityUpgrades: true
                }, cb);
            }, function(err)
            {
                if (err)
                {
                    throw err;
                }
                console.log(match_seq_num);
                match_seq_num = matches[matches.length - 1].match_seq_num + 1;
                return getPage();
            });
        }
        else
        {
            throw body;
        }
    });
}<commit_msg>implement bucketed allmatches script<commit_after>var utility = require('../utility');
var generateJob = utility.generateJob;
var async = require('async');
var getData = utility.getData;
var queries = require('../queries');
var insertMatch = queries.insertMatch;
var db = require('../db');
var queue = require('../queue');
var redis = require('../redis');
var args = process.argv.slice(2);
var end_seq_num = args[0] || 0;
//match seq num 59622 has a 32-bit unsigned int max (4294967295) in one of the players' tower damage
//match seq num 239190 for hero_healing
//match seq num 542284 for hero_healing
//may need to cap values down to 2.1b if we encounter them
//postgres int type only supports up to 2.1b (signed int)
//bucket idspace into groups of 100000000
//save progress to redis key complete_history:n
var bucket_size = 100000000;
var i = 0;
async.whilst(function()
{
    return i < end_seq_num;
}, function(cb)
{
    var bucket = i;
    redis.get('complete_history:' + bucket, function(err, result)
    {
        if (err)
        {
            throw err;
        }
        result = result ? Number(result) : bucket;
        getPage(result, bucket);
        cb(err);
    });
    i += bucket_size;
}, function(err)
{
    console.log("launched all scanners with err: %s", err);
});

function getPage(match_seq_num, bucket)
{
    if (match_seq_num > bucket + bucket_size)
    {
        return;
    }
    var job = generateJob("api_sequence",
    {
        start_at_match_seq_num: match_seq_num
    });
    var url = job.url;
    getData(
    {
        url: url,
        delay: 1
    }, function(err, body)
    {
        if (err)
        {
            throw err;
        }
        if (body.result)
        {
            var matches = body.result.matches;
            async.each(matches, function(m, cb)
            {
                insertMatch(db, redis, queue, m,
                {
                    type: "api",
                    skipCacheUpdate: true,
                    skipAbilityUpgrades: true
                }, cb);
            }, function(err)
            {
                if (err)
                {
                    throw err;
                }
                var next_seq_num = matches[matches.length - 1].match_seq_num + 1;
                redis.set('complete_history:' + bucket, next_seq_num);
                return getPage(next_seq_num, bucket);
            });
        }
        else
        {
            throw body;
        }
    });
}<|endoftext|>


<commit_before>/**
 * Device controller that produces test devices.
 *
 * Device controller that produces test devices and simulates device
 * interactions.
 *
 * @author A. Samuel Pottinger (LabJack, 2013)
**/

var async = require('async');
var dict = require('dict');
var q = require('q');
var labjack_nodejs = require('labjack-nodejs');
var labjack_driver = new labjack_nodejs.driver();

var LJM_DT_T7 = labjack_nodejs.driver_const.LJM_DT_T7.toString();
var DEVICE_TYPE_NAMES = dict({
    '7': 'T7'
});

var CONNECT_TYPE_USB = 1;
var CONNECTION_TYPE_NAMES = dict({
    '1': 'USB',
    '3': 'Ethernet',
    '4': 'Wifi'
});

/**
 * Object with information about a device.
 *
 * @param {String} serial The serial number for the device.
 * @param {String} connectionType The type of connection used to communicate
 *      with the device. Examples include "USB", "Ethernet", and "WiFi".
 * @param {String} deviceType The type of model this device is. Examples include
 *      T7 and Digit-TL.
**/
var Device = function (device, serial, connectionType, deviceType)
{
    this.device = device;
    this.cachedName = null;

    /**
     * Get the serial number for this device.
     *
     * @return {String} The serial number for this device.
    **/
    this.getSerial = function () {
        return serial;
    };

    /**
     * Get the type of connection being used to communicate with this device.
     *
     * Get the type of connection being used to communicate with this device.
     * Examples include "USB", "Ethernet", and "WiFi".
     *
     * @return {String} The type of connection being used to communicate with
     *      this device.
    **/
    this.getConnectionType = function () {
        return connectionType;
    };

    /**
     * Get the type of model this device is.
     *
     * Get a String describing what kind of device this is. Examples include
     * "T7" and "Digit-TL".
     *
     * @return {String} A description of the type of device this is.
    **/
    this.getDeviceType = function () {
        return DEVICE_TYPE_NAMES.get(String(deviceType), 'Other');
    };

    /**
     * Get the user-assigned name for this device.
     *
     * Get the name of this device as assigned by a previous authorized user
     * (name not set by default).
     *
     * @return {String} The name of this device.
    **/
    this.getName = function () {
        if (this.cachedName === null)
            this.cachedName = this.device.readSync('DEVICE_NAME_DEFAULT');
        return this.cachedName;
    };

    // TODO
    this.getFirmwareVersion = function () {
        return this.device.readSync('FIRMWARE_VERSION');
    };

    // TODO
    this.getBootloaderVersion = function () {
        return this.device.readSync('BOOTLOADER_VERSION');
    };

    this.writeMany = function (addresses, values) {
        var deferred = q.defer();

        this.device.writeMany(
            addresses,
            values,
            function (err) {
                deferred.reject(err);
            },
            function (results) {
                deferred.resolve(results);
            }
        );

        return deferred.promise;
    };

    this.readMany = function (addresses) {
        var deferred = q.defer();

        this.device.readMany(
            addresses,
            function (err) {
                deferred.reject(err);
            },
            function (results) {
                deferred.resolve(results);
            }
        );

        return deferred.promise;
    };

    this.writeAsync = function(address, value) {
        var deferred = q.defer();

        this.device.write(
            address,
            value,
            function (err) {
                deferred.reject(err);
            },
            function (results) {
                deferred.resolve(results);
            }
        );

        return deferred.promise;
    };

    this.write = function (address, value) {
        this.device.writeSync(address, value);
    };

    this.read = function (address) {
        return this.device.readSync(address);
    };

    this.close = function (onError, onSuccess) {
        device.close(onError, onSuccess);
    };

    this.setName = function (newName) {
        this.write('DEVICE_NAME_DEFAULT', newName);
        this.cachedName = newName;
    };
};

// TODO: Cannot rely on serial number alone.
// TODO: This should be in a seperate module.
/**
 * A record keeper that holds a list of devices.
 *
 * A record keeper that holds a list of devices. Example usages include holding
 * a list of devices that are currently open.
**/
function DeviceKeeper()
{
    var devices = dict();

    /**
     * Add a new device to this keeper.
     *
     * @param {Device} device The device to add to this keeper.
    **/
    this.addDevice = function(device)
    {
        devices.set(device.getSerial(), device);
    };

    /**
     * Deermine how many devices this keeper has in it.
     *
     * @return {Number} The number of devices this keeper currently has in it.
    **/
    this.getNumDevices = function()
    {
        return devices.size;
    };

    /**
     * Remove a device from this keeper.
     *
     * @param {Device} device The device or equivalent device to remove from
     *      this keeper. The device removed from the keeper will be the one with
     *      the same serial number.
    **/
    this.removeDevice = function(device)
    {
        if(devices.has(device.getSerial()))
            devices.delete(device.getSerial());
    };

    /**
     * Get a device from this keeper.
     *
     * Get a device from this keeper based on a serial number. Will return null
     * if the device is not in this keeper.
     *
     * @return {Device} The device with the given serial number of null if no
     *      device by that serial number is in this keeper.
    **/
    this.getDevice = function(serial)
    {
        if(devices.has(serial))
            return devices.get(serial);
        else
            return null;
    };

    this.getDevices = function()
    {
        var retList = [];
        devices.forEach(function(value, key) {retList.push(value);});
        return retList;
    };

    this.getDeviceSerials = function()
    {
        var retList = [];
        devices.forEach(function(value, key) {retList.push(key);});
        return retList;
    };
}

var deviceKeeperInstance = null;

function openDeviceFromAttributes (deviceType, serialNumber, ipAddress,
    connectionType, onError, onSuccess)
{
    var device;
    if (connectionType == CONNECT_TYPE_USB) {
        device = new labjack_nodejs.device();
        device.open(
            deviceType,
            connectionType,
            String(serialNumber),
            onError,
            function () { onSuccess(device); }
        );
    } else {
        device = new labjack_nodejs.device();
        device.open(
            deviceType,
            connectionType,
            ipAddress,
            onError,
            function () { onSuccess(device); }
        );
    }
}

/**
 * Get the device keeper singlton instance maintaining the list of open devices.
 *
 * @return {DeviceKeeper} The device keeper used system wide that maintains a
 *      list of all open devices.
**/
exports.getDeviceKeeper = function()
{
    if(deviceKeeperInstance === null)
        deviceKeeperInstance = new DeviceKeeper();
    return deviceKeeperInstance;
};

function markConnectedDevices(devices)
{
    var devicesOfType;
    var device;

    var connectedSerials = exports.getDeviceKeeper().getDeviceSerials();

    var devicesLen = devices.length;
    for(var i=0; i<devicesLen; i++)
    {
        devicesOfType = devices[i].devices;
        var devicesOfTypeLen = devicesOfType.length;
        for(var j=0; j<devicesOfTypeLen; j++)
        {
            device = devicesOfType[j];
            device.connected = connectedSerials.indexOf(
                device.serial.toString()) != -1;
        }
    }
    return devices;
}

function getListingEntry (listingDict, device)
{
    var deviceType = String(device.deviceType);

    if (!listingDict.has(deviceType)) {
        listingDict.set(
            deviceType,
            {
                'name': DEVICE_TYPE_NAMES.get(deviceType, 'other'),
                'devices': []
            }
        );
    }

    return listingDict.get(deviceType);
}

function createDeviceListingRecord (device, name)
{
    var connectionType = CONNECTION_TYPE_NAMES.get(
        String(device.connectionType), 'other');
    var deviceType = DEVICE_TYPE_NAMES.get(String(device.deviceType), 'other');

    return {
        'serial': device.serialNumber,
        'connectionTypes': [connectionType],
        'origConnectionType': device.connectionType,
        'origDeviceType': device.deviceType,
        'type': deviceType,
        'name': name,
        'ipAddress': device.ipAddress,
        'ipSafe': device.ipAddress.replace(/\./g, '_')
    };
}

function openDeviceFromInfo (deviceInfo, connectionType, onError, onSuccess)
{
    openDeviceFromAttributes(
        deviceInfo.deviceType,
        deviceInfo.serialNumber,
        deviceInfo.ipAddress,
        connectionType,
        onError,
        onSuccess
    );
}

function finishDeviceRecord (listingDict, deviceInfo, callback)
{
    var listingEntry = getListingEntry(listingDict, deviceInfo);

    openDeviceFromInfo(
        deviceInfo,
        deviceInfo.connectionType,
        function () {callback();},
        function (device) {
            var name = device.readSync('DEVICE_NAME_DEFAULT');
            var record = createDeviceListingRecord(deviceInfo, name);
            listingEntry.devices.push(record);
            device.closeSync();
            callback();
        }
    );
}

var consolidateDevices = function (listing) {
    var existingDevice;
    var newDevice;
    var deviceListing = dict();
    var devices = listing.devices;
    var numDevices = devices.length;

    for (var i=0; i<numDevices; i++) {
        newDevice = devices[i];
        existingDevice = deviceListing.get(newDevice.serial.toString(), null);
        if (existingDevice !== null) {
            newDevice.connectionTypes.push.apply(
                newDevice.connectionTypes,
                existingDevice.connectionTypes
            );
        }
        deviceListing.set(newDevice.serial.toString(), newDevice);
    }

    var retList = [];
    deviceListing.forEach(function (value, key) {
        retList.push(value);
    });

    listing.devices = retList;
};

/**
 * Get a list of devices currently visible by to this computer.
 *
 * @param {function} onError The funciton to call if an error is encountered.
 * @param {function} onSuccess The function to call after the list of devices
 *      has been collected.
 * @return {Array} An Array of Objects with the keys "name" and "devices". The
 *      value of "name" is a String with the name of the device type (ex: "T7").
 *      The value of "devices" is a list of Objects with the keys "serial",
 *      "connectionTypes", "type", and "name".
**/
exports.getDevices = function (onError, onSuccess)
{
    labjack_driver.listAll(
        onError,
        function (driverListing) {
            var listingDict = dict();
            async.each(
                driverListing,
                function (deviceInfo, callback) {
                    finishDeviceRecord(listingDict, deviceInfo, callback);
                },
                function (err) {
                    if (err) {
                        alert(err);
                        return;
                    }

                    var listing = [];
                    listingDict.forEach(function (value, key) {
                        listing.push(value);
                    });

                    for (var i=0; i<listing.length; i++)
                        consolidateDevices(listing[i]);

                    listing = markConnectedDevices(listing);
                    onSuccess(listing);
                }
            );
        }
    );
};

/**
 * Open a connection to a device.
 *
 * @param {String} serial The serial number of the device to open.
 * @param {String} connType The type of connection to open to the device.
 *      Examples include "USB", "Ethernet", and "WiFi".
 * @param {String} deviceType The type of device that should be opened. Examples
 *      include "T7" and "Digit-TL".
 * @param {function} onError The function to call if an error is encountered.
 * @param {function} onSuccess The function to call afte the device has been
 *      successfully opened.
**/
exports.openDevice = function (serial, ipAddress, connType, deviceType, onError,
    onSuccess)
{
    openDeviceFromAttributes(
        deviceType,
        serial,
        ipAddress,
        connType,
        onError,
        function (innerDevice) {
            var device = new Device(innerDevice, serial, connType, deviceType);
            onSuccess(device);
        }
    );
};

/**
 * Close the connection to a device.
 *
 * @param {Device} device The device to close the connection to.
 * @param {function} onSuccess The function to call after the connection to the
 *      device is successfully closed.
 * @param {function} onError The function to call if an error is encountered
 *      while trying to close a device.
**/
exports.closeDevice = function(device, onSuccess, onError)
{
    device.close(onError, function() {
        onSuccess(device);
    });
};
<commit_msg>Correct marking of device but fail close.<commit_after>/**
 * Device controller that produces test devices.
 *
 * Device controller that produces test devices and simulates device
 * interactions.
 *
 * @author A. Samuel Pottinger (LabJack, 2013)
**/

var async = require('async');
var dict = require('dict');
var q = require('q');
var labjack_nodejs = require('labjack-nodejs');
var labjack_driver = new labjack_nodejs.driver();

var LJM_DT_T7 = labjack_nodejs.driver_const.LJM_DT_T7.toString();
var DEVICE_TYPE_NAMES = dict({
    '7': 'T7'
});

var CONNECT_TYPE_USB = 1;
var CONNECTION_TYPE_NAMES = dict({
    '1': 'USB',
    '3': 'Ethernet',
    '4': 'Wifi'
});

/**
 * Object with information about a device.
 *
 * @param {String} serial The serial number for the device.
 * @param {String} connectionType The type of connection used to communicate
 *      with the device. Examples include "USB", "Ethernet", and "WiFi".
 * @param {String} deviceType The type of model this device is. Examples include
 *      T7 and Digit-TL.
**/
var Device = function (device, serial, connectionType, deviceType)
{
    this.device = device;
    this.cachedName = null;

    /**
     * Get the serial number for this device.
     *
     * @return {String} The serial number for this device.
    **/
    this.getSerial = function () {
        return serial;
    };

    /**
     * Get the type of connection being used to communicate with this device.
     *
     * Get the type of connection being used to communicate with this device.
     * Examples include "USB", "Ethernet", and "WiFi".
     *
     * @return {String} The type of connection being used to communicate with
     *      this device.
    **/
    this.getConnectionType = function () {
        return connectionType;
    };

    /**
     * Get the type of model this device is.
     *
     * Get a String describing what kind of device this is. Examples include
     * "T7" and "Digit-TL".
     *
     * @return {String} A description of the type of device this is.
    **/
    this.getDeviceType = function () {
        return DEVICE_TYPE_NAMES.get(String(deviceType), 'Other');
    };

    /**
     * Get the user-assigned name for this device.
     *
     * Get the name of this device as assigned by a previous authorized user
     * (name not set by default).
     *
     * @return {String} The name of this device.
    **/
    this.getName = function () {
        if (this.cachedName === null)
            this.cachedName = this.device.readSync('DEVICE_NAME_DEFAULT');
        return this.cachedName;
    };

    // TODO
    this.getFirmwareVersion = function () {
        return this.device.readSync('FIRMWARE_VERSION');
    };

    // TODO
    this.getBootloaderVersion = function () {
        return this.device.readSync('BOOTLOADER_VERSION');
    };

    this.writeMany = function (addresses, values) {
        var deferred = q.defer();

        this.device.writeMany(
            addresses,
            values,
            function (err) {
                deferred.reject(err);
            },
            function (results) {
                deferred.resolve(results);
            }
        );

        return deferred.promise;
    };

    this.readMany = function (addresses) {
        var deferred = q.defer();

        this.device.readMany(
            addresses,
            function (err) {
                deferred.reject(err);
            },
            function (results) {
                deferred.resolve(results);
            }
        );

        return deferred.promise;
    };

    this.writeAsync = function(address, value) {
        var deferred = q.defer();

        this.device.write(
            address,
            value,
            function (err) {
                deferred.reject(err);
            },
            function (results) {
                deferred.resolve(results);
            }
        );

        return deferred.promise;
    };

    this.write = function (address, value) {
        this.device.writeSync(address, value);
    };

    this.read = function (address) {
        return this.device.readSync(address);
    };

    this.close = function (onError, onSuccess) {
        this.device.close(onError, onSuccess);
    };

    this.setName = function (newName) {
        this.write('DEVICE_NAME_DEFAULT', newName);
        this.cachedName = newName;
    };
};

// TODO: Cannot rely on serial number alone.
// TODO: This should be in a seperate module.
/**
 * A record keeper that holds a list of devices.
 *
 * A record keeper that holds a list of devices. Example usages include holding
 * a list of devices that are currently open.
**/
function DeviceKeeper()
{
    var devices = dict();

    /**
     * Add a new device to this keeper.
     *
     * @param {Device} device The device to add to this keeper.
    **/
    this.addDevice = function(device)
    {
        devices.set(device.getSerial(), device);
    };

    /**
     * Deermine how many devices this keeper has in it.
     *
     * @return {Number} The number of devices this keeper currently has in it.
    **/
    this.getNumDevices = function()
    {
        return devices.size;
    };

    /**
     * Remove a device from this keeper.
     *
     * @param {Device} device The device or equivalent device to remove from
     *      this keeper. The device removed from the keeper will be the one with
     *      the same serial number.
    **/
    this.removeDevice = function(device)
    {
        if(devices.has(device.getSerial()))
            devices.delete(device.getSerial());
    };

    /**
     * Get a device from this keeper.
     *
     * Get a device from this keeper based on a serial number. Will return null
     * if the device is not in this keeper.
     *
     * @return {Device} The device with the given serial number of null if no
     *      device by that serial number is in this keeper.
    **/
    this.getDevice = function(serial)
    {
        if(devices.has(serial))
            return devices.get(serial);
        else
            return null;
    };

    this.getDevices = function()
    {
        var retList = [];
        devices.forEach(function(value, key) {retList.push(value);});
        return retList;
    };

    this.getDeviceSerials = function()
    {
        var retList = [];
        devices.forEach(function(value, key) {retList.push(key);});
        return retList;
    };
}

var deviceKeeperInstance = null;

function openDeviceFromAttributes (deviceType, serialNumber, ipAddress,
    connectionType, onError, onSuccess)
{
    var device;
    if (connectionType == CONNECT_TYPE_USB) {
        device = new labjack_nodejs.device();
        device.open(
            deviceType,
            connectionType,
            String(serialNumber),
            onError,
            function () { onSuccess(device); }
        );
    } else {
        device = new labjack_nodejs.device();
        device.open(
            deviceType,
            connectionType,
            ipAddress,
            onError,
            function () { onSuccess(device); }
        );
    }
}

/**
 * Get the device keeper singlton instance maintaining the list of open devices.
 *
 * @return {DeviceKeeper} The device keeper used system wide that maintains a
 *      list of all open devices.
**/
exports.getDeviceKeeper = function()
{
    if(deviceKeeperInstance === null)
        deviceKeeperInstance = new DeviceKeeper();
    return deviceKeeperInstance;
};

function markConnectedDevices(devices, onSuccess, onError)
{
    var devicesOfType;
    var device;

    var connectedSerials = exports.getDeviceKeeper().getDeviceSerials();

    var devicesLen = devices.length;
    var connectedDevices = [];
    for(var i=0; i<devicesLen; i++)
    {
        devicesOfType = devices[i].devices;
        var devicesOfTypeLen = devicesOfType.length;
        for(var j=0; j<devicesOfTypeLen; j++)
        {
            device = devicesOfType[j];
            if (connectedSerials.indexOf(device.serial.toString()) != -1)
                connectedDevices.push(device);
            else
                device.connected = false;
        }
    }

    async.each(
        connectedDevices,
        function (item, callback) {
            openDeviceFromAttributes(
                device.origDeviceType,
                device.serial,
                device.ipAddress,
                device.origConnectionType,
                function (err) {
                    console.log('===err===');
                    console.log(err);
                    device.connected = false;
                    callback();
                },
                function (newInner) {
                    device.device = null;
                    device.connected = true;
                    callback();
                }
            );
        },
        function (err) {
            onSuccess(devices);
        }
    );
}

function getListingEntry (listingDict, device)
{
    var deviceType = String(device.deviceType);

    if (!listingDict.has(deviceType)) {
        listingDict.set(
            deviceType,
            {
                'name': DEVICE_TYPE_NAMES.get(deviceType, 'other'),
                'devices': []
            }
        );
    }

    return listingDict.get(deviceType);
}

function createDeviceListingRecord (device, name)
{
    var connectionType = CONNECTION_TYPE_NAMES.get(
        String(device.connectionType), 'other');
    var deviceType = DEVICE_TYPE_NAMES.get(String(device.deviceType), 'other');

    return {
        'serial': device.serialNumber,
        'connectionTypes': [connectionType],
        'origConnectionType': device.connectionType,
        'origDeviceType': device.deviceType,
        'type': deviceType,
        'name': name,
        'ipAddress': device.ipAddress,
        'ipSafe': device.ipAddress.replace(/\./g, '_')
    };
}

function openDeviceFromInfo (deviceInfo, connectionType, onError, onSuccess)
{
    openDeviceFromAttributes(
        deviceInfo.deviceType,
        deviceInfo.serialNumber,
        deviceInfo.ipAddress,
        connectionType,
        onError,
        onSuccess
    );
}

function finishDeviceRecord (listingDict, deviceInfo, callback)
{
    var listingEntry = getListingEntry(listingDict, deviceInfo);

    console.log('..open..');
    openDeviceFromInfo(
        deviceInfo,
        deviceInfo.connectionType,
        function () {callback();},
        function (device) {
            var name = device.readSync('DEVICE_NAME_DEFAULT');
            var record = createDeviceListingRecord(deviceInfo, name);
            listingEntry.devices.push(record);
            device.closeSync();
            callback();
        }
    );
}

var consolidateDevices = function (listing) {
    var existingDevice;
    var newDevice;
    var deviceListing = dict();
    var devices = listing.devices;
    var numDevices = devices.length;

    for (var i=0; i<numDevices; i++) {
        newDevice = devices[i];
        existingDevice = deviceListing.get(newDevice.serial.toString(), null);
        if (existingDevice !== null) {
            newDevice.connectionTypes.push.apply(
                newDevice.connectionTypes,
                existingDevice.connectionTypes
            );
        }
        deviceListing.set(newDevice.serial.toString(), newDevice);
    }

    var retList = [];
    deviceListing.forEach(function (value, key) {
        retList.push(value);
    });

    listing.devices = retList;
};

/**
 * Get a list of devices currently visible by to this computer.
 *
 * @param {function} onError The funciton to call if an error is encountered.
 * @param {function} onSuccess The function to call after the list of devices
 *      has been collected.
 * @return {Array} An Array of Objects with the keys "name" and "devices". The
 *      value of "name" is a String with the name of the device type (ex: "T7").
 *      The value of "devices" is a list of Objects with the keys "serial",
 *      "connectionTypes", "type", and "name".
**/
exports.getDevices = function (onError, onSuccess)
{
    labjack_driver.listAll(
        onError,
        function (driverListing) {
            var listingDict = dict();
            async.each(
                driverListing,
                function (deviceInfo, callback) {
                    finishDeviceRecord(listingDict, deviceInfo, callback);
                },
                function (err) {
                    if (err) {
                        alert(err);
                        return;
                    }

                    var listing = [];
                    listingDict.forEach(function (value, key) {
                        listing.push(value);
                    });

                    for (var i=0; i<listing.length; i++)
                        consolidateDevices(listing[i]);

                    markConnectedDevices(listing, onSuccess);
                }
            );
        }
    );
};

/**
 * Open a connection to a device.
 *
 * @param {String} serial The serial number of the device to open.
 * @param {String} connType The type of connection to open to the device.
 *      Examples include "USB", "Ethernet", and "WiFi".
 * @param {String} deviceType The type of device that should be opened. Examples
 *      include "T7" and "Digit-TL".
 * @param {function} onError The function to call if an error is encountered.
 * @param {function} onSuccess The function to call afte the device has been
 *      successfully opened.
**/
exports.openDevice = function (serial, ipAddress, connType, deviceType, onError,
    onSuccess)
{
    openDeviceFromAttributes(
        deviceType,
        serial,
        ipAddress,
        connType,
        onError,
        function (innerDevice) {
            var device = new Device(innerDevice, serial, connType, deviceType);
            onSuccess(device);
        }
    );
};

/**
 * Close the connection to a device.
 *
 * @param {Device} device The device to close the connection to.
 * @param {function} onSuccess The function to call after the connection to the
 *      device is successfully closed.
 * @param {function} onError The function to call if an error is encountered
 *      while trying to close a device.
**/
exports.closeDevice = function(device, onSuccess, onError)
{
    device.close(onError, function() {
        onSuccess(device);
    });
};
<|endoftext|>


<commit_before>// CKEditor TeXZilla Plugin
// Copyright (C) 2014  Raniere Silva
//
// This Source Code Form is subject to the terms of the
// Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

/* global CKEDITOR:false */
/* global TeXZilla:false */

function update_preview() {
    var dialog = CKEDITOR.dialog.getCurrent();

    var preview = document.getElementById('Preview');
    // Clean previous preview
    var previous = preview.childNodes;
    for (i = 0; i < previous.length; i++) {
        preview.removeChild(previous[i]);
    }
    var mathElement = TeXZilla.toMathML(
        dialog.getValueOf('basic', 'tex'),
        dialog.getValueOf('basic', 'display'),
        dialog.getValueOf('basic', 'direction'));

    // Check for error
    var has_error = false;
    for (var i = 0; i < mathElement.lastElementChild.childElementCount; i++) {
        if (mathElement.lastElementChild.children[i].localName === 'merror') {
            has_error = true;
        }
    }

    // Disable button if errer
    if (has_error === true) {
        dialog.disableButton("ok");
    }
    else {
        dialog.enableButton("ok");
    }
    preview.appendChild(mathElement);
}

CKEDITOR.dialog.add('texzillaDialog', function(editor) {
    return {
        title: 'TeXZilla Edit Box',
        minWidth: 300,
        minHeight: 300,
        contents: [{
            id: 'basic',
            label: 'Basic Settings',
            elements: [{
                id: 'tex',
                type: 'textarea',
                label: 'Please insert your (LaTex) code:',
                setup: function(insertMode, element) {
                    if (!insertMode) {
                        this.setValue(TeXZilla.getTeXSource(element.$));
                    }
                },
                onChange: function() {
                    update_preview();
                }
            }, {
                id: 'instruction',
                type: 'html',
                html: '
(Clicking outside of the textarea updates the preview)
'
            }, {
                id: 'options',
                type: 'html',
                html: '
Options:
'
            }, {
                id: 'display',
                type: 'checkbox',
                label: 'Display',
                setup: function(insertMode, element) {
                    var display = element.getAttribute("display");
                    if (display === 'block') {
                        this.setValue(true);
                    }
                    else {
                        this.setValue(false);
                    }
                },
                onChange: function() {
                    update_preview();
                }
            }, {
                id: 'direction',
                type: 'checkbox',
                label: 'RTL',
                setup: function(insertMode, element) {
                    var direction = element.getAttribute("dir");
                    if (direction === 'rtl') {
                        this.setValue(true);
                    }
                    else {
                        this.setValue(false);
                    }
                },
                onChange: function() {
                    update_preview();
                }
            }, {
                id: 'preview',
                type: 'html',
                label: 'Preview',
                // Width of the preview box is based on the minWidth of the dialog window.
                html: '

Preview:

',
                setup: function(insertMode, element) {
                    var preview = document.getElementById('Preview');
                    // Clean previous preview
                    var previous = preview.childNodes;
                    for (var i = 0; i < previous.length; i++) {
                        preview.removeChild(previous[i]);
                    }
                    if (!insertMode) {
                        preview.appendChild(element.$.cloneNode(true));
                    }
                }
            }]
        }],
        onShow: function() {
            var selection = editor.getSelection();
            var element = selection.getStartElement();
            // Try to locate a `math` or `body` tag.
            while (element.getName() !== 'math' &&
                element.getName() !== 'body') {
                element = element.getParent();
            }
            if (!element || element.getName() !== 'math') {
                this.insertMode = true;
            }
            else {
                this.insertMode = false;
                this.mathRoot = element;
            }
            // invoke the setup functions for the element
            this.setupContent(this.insertMode, element);
        },
        onOk: function() {
            var dialog = this;

            // This is the better way to insert the MathML, although we got
            //
            //     TypeError: element.getName is not a function
            //
            // when using it.
            //
            // var mathElement = TeXZilla.toMathML(dialog.getValueOf('basic', 'tex'));

            // This is a hack found at
            // http://stackoverflow.com/a/17339275/1802726.
            var math = TeXZilla.toMathMLString(
                dialog.getValueOf('basic', 'tex'),
                dialog.getValueOf('basic', 'display'),
                dialog.getValueOf('basic', 'direction'));
            var mathElement = CKEDITOR.dom.element.createFromHtml(math, editor.document);

            if (!this.insertMode) {
                // Remove old equation
                this.mathRoot.$.parentNode.removeChild(this.mathRoot.$);
            }
            editor.insertElement(mathElement);
        }
    };
});
<commit_msg>Use TeXZilla's ThrowExceptionOnError<commit_after>// CKEditor TeXZilla Plugin
// Copyright (C) 2014  Raniere Silva
//
// This Source Code Form is subject to the terms of the
// Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

/* global CKEDITOR:false */
/* global TeXZilla:false */

function update_preview() {
    var dialog = CKEDITOR.dialog.getCurrent();

    var preview = document.getElementById('Preview');
    // Clean previous preview
    var previous = preview.childNodes;
    for (i = 0; i < previous.length; i++) {
        preview.removeChild(previous[i]);
    }
    var has_error;
    var mathElement = TeXZilla.toMathML(
        dialog.getValueOf('basic', 'tex'),
        dialog.getValueOf('basic', 'display'),
        dialog.getValueOf('basic', 'direction'),
        has_error);

    // Disable button if errer
    if (has_error === true) {
        dialog.disableButton("ok");
    }
    else {
        dialog.enableButton("ok");
    }
    preview.appendChild(mathElement);
}

CKEDITOR.dialog.add('texzillaDialog', function(editor) {
    return {
        title: 'TeXZilla Edit Box',
        minWidth: 300,
        minHeight: 300,
        contents: [{
            id: 'basic',
            label: 'Basic Settings',
            elements: [{
                id: 'tex',
                type: 'textarea',
                label: 'Please insert your (LaTex) code:',
                setup: function(insertMode, element) {
                    if (!insertMode) {
                        this.setValue(TeXZilla.getTeXSource(element.$));
                    }
                },
                onChange: function() {
                    update_preview();
                }
            }, {
                id: 'instruction',
                type: 'html',
                html: '
(Clicking outside of the textarea updates the preview)
'
            }, {
                id: 'options',
                type: 'html',
                html: '
Options:
'
            }, {
                id: 'display',
                type: 'checkbox',
                label: 'Display',
                setup: function(insertMode, element) {
                    var display = element.getAttribute("display");
                    if (display === 'block') {
                        this.setValue(true);
                    }
                    else {
                        this.setValue(false);
                    }
                },
                onChange: function() {
                    update_preview();
                }
            }, {
                id: 'direction',
                type: 'checkbox',
                label: 'RTL',
                setup: function(insertMode, element) {
                    var direction = element.getAttribute("dir");
                    if (direction === 'rtl') {
                        this.setValue(true);
                    }
                    else {
                        this.setValue(false);
                    }
                },
                onChange: function() {
                    update_preview();
                }
            }, {
                id: 'preview',
                type: 'html',
                label: 'Preview',
                // Width of the preview box is based on the minWidth of the dialog window.
                html: '

Preview:

',
                setup: function(insertMode, element) {
                    var preview = document.getElementById('Preview');
                    // Clean previous preview
                    var previous = preview.childNodes;
                    for (var i = 0; i < previous.length; i++) {
                        preview.removeChild(previous[i]);
                    }
                    if (!insertMode) {
                        preview.appendChild(element.$.cloneNode(true));
                    }
                }
            }]
        }],
        onShow: function() {
            var selection = editor.getSelection();
            var element = selection.getStartElement();
            // Try to locate a `math` or `body` tag.
            while (element.getName() !== 'math' &&
                element.getName() !== 'body') {
                element = element.getParent();
            }
            if (!element || element.getName() !== 'math') {
                this.insertMode = true;
            }
            else {
                this.insertMode = false;
                this.mathRoot = element;
            }
            // invoke the setup functions for the element
            this.setupContent(this.insertMode, element);
        },
        onOk: function() {
            var dialog = this;

            // This is the better way to insert the MathML, although we got
            //
            //     TypeError: element.getName is not a function
            //
            // when using it.
            //
            // var mathElement = TeXZilla.toMathML(dialog.getValueOf('basic', 'tex'));

            // This is a hack found at
            // http://stackoverflow.com/a/17339275/1802726.
            var math = TeXZilla.toMathMLString(
                dialog.getValueOf('basic', 'tex'),
                dialog.getValueOf('basic', 'display'),
                dialog.getValueOf('basic', 'direction'));
            var mathElement = CKEDITOR.dom.element.createFromHtml(math, editor.document);

            if (!this.insertMode) {
                // Remove old equation
                this.mathRoot.$.parentNode.removeChild(this.mathRoot.$);
            }
            editor.insertElement(mathElement);
        }
    };
});
<|endoftext|>


<commit_before>/* jshint -W079 */
/* global define */

var W = {
    version: '0.0.4',
    debug: false
};

function expose() {
    var oldW = window.W;

    W.noConflict = function () {
        window.W = oldW;
        return this;
    };

    window.W = W;
}

// define Wialon for Node module pattern loaders, including Browserify
if (typeof module === 'object' && typeof module.exports === 'object') {
    module.exports = W;
// define Wialon as an AMD module
} else if (typeof define === 'function' && define.amd) {
    define(W);
}
// define Wialon as a global W variable, saving the original W to restore later if needed
if (typeof window !== 'undefined') {
    expose();
}<commit_msg>fix version<commit_after>/* jshint -W079 */
/* global define */

var W = {
    version: '0.0.7',
    debug: false
};

function expose() {
    var oldW = window.W;

    W.noConflict = function () {
        window.W = oldW;
        return this;
    };

    window.W = W;
}

// define Wialon for Node module pattern loaders, including Browserify
if (typeof module === 'object' && typeof module.exports === 'object') {
    module.exports = W;
// define Wialon as an AMD module
} else if (typeof define === 'function' && define.amd) {
    define(W);
}
// define Wialon as a global W variable, saving the original W to restore later if needed
if (typeof window !== 'undefined') {
    expose();
}<|endoftext|>


<commit_before>/**
 * Wrapper for {@link Ext.window.Window} providing common configuration for its
 * use within Slate UIs
 */
Ext.define('Slate.ui.Window', {
    extend: 'Ext.window.Window',
    xtype: 'slate-window',

    /**
     * @cfg {Boolean}
     * @inheritdoc Ext.tab.Panel#removePanelHeader
     * @localdoc True to remove header for any added mainView panel,
     * modeled after how tabpanel does the same
     */
    removeMainViewHeader: true,

    config: {

        /**
         * @cfg {Ext.Component|Object|String|null}
         *
         * A component, config, or xtype for a main view to add to the window
         */
        mainView: null,

        minWidth: 400,
        minHeight: 200
    },

    // window configuration
    shadow: 'frame',
    constrainHeader: true,

    // container configuration
    layout: 'fit',

    // component configuration
    componentClS: 'slate-window',

    // config handlers
    applyMainView: function(mainView) {
        if (mainView === null) {
            return mainView;
        }

        if (mainView === true) {
            mainView = {};
        } else if (typeof mainView == 'string') {
            mainView = {
                xtype: mainView
            };
        }

        return this.lookupComponent(mainView);
    },

    updateMainView: function(mainView, oldMainView) {
        var me = this,
            items = me.items;

        if (oldMainView) {
            oldMainView.un('titlechange', 'onMainViewTitleChange', me);
        }

        if (mainView && mainView.isPanel && me.removeMainViewHeader) {
            if (mainView.rendered) {
                if (mainView.header) {
                    mainView.header.hide();
                }
            } else {
                mainView.header = false;
            }

            if (mainView.title) {
                me.setTitle(mainView.title);
            }

            mainView.on('titlechange', 'onMainViewTitleChange', me);
        }

        if (items && items.isMixedCollection) {
            if (oldMainView) {
                me.remove(oldMainView);
            }

            if (mainView) {
                me.insert(0, mainView);
            }
        }
    },

    // container lifecycle
    initItems: function() {
        var me = this,
            mainView = me.getMainView();

        me.callParent(arguments);

        if (mainView) {
            me.insert(0, mainView);
        }
    },

    // event handlers
    onMainViewTitleChange: function(mainView, title) {
        this.setTitle(title);
    }
});<commit_msg>Remove default layout for slate-window<commit_after>/**
 * Wrapper for {@link Ext.window.Window} providing common configuration for its
 * use within Slate UIs
 */
Ext.define('Slate.ui.Window', {
    extend: 'Ext.window.Window',
    xtype: 'slate-window',

    /**
     * @cfg {Boolean}
     * @inheritdoc Ext.tab.Panel#removePanelHeader
     * @localdoc True to remove header for any added mainView panel,
     * modeled after how tabpanel does the same
     */
    removeMainViewHeader: true,

    config: {

        /**
         * @cfg {Ext.Component|Object|String|null}
         *
         * A component, config, or xtype for a main view to add to the window
         */
        mainView: null,

        minWidth: 400,
        minHeight: 200
    },

    // window configuration
    shadow: 'frame',
    constrainHeader: true,

    // component configuration
    componentClS: 'slate-window',

    // config handlers
    applyMainView: function(mainView) {
        if (mainView === null) {
            return mainView;
        }

        if (mainView === true) {
            mainView = {};
        } else if (typeof mainView == 'string') {
            mainView = {
                xtype: mainView
            };
        }

        return this.lookupComponent(mainView);
    },

    updateMainView: function(mainView, oldMainView) {
        var me = this,
            items = me.items;

        if (oldMainView) {
            oldMainView.un('titlechange', 'onMainViewTitleChange', me);
        }

        if (mainView && mainView.isPanel && me.removeMainViewHeader) {
            if (mainView.rendered) {
                if (mainView.header) {
                    mainView.header.hide();
                }
            } else {
                mainView.header = false;
            }

            if (mainView.title) {
                me.setTitle(mainView.title);
            }

            mainView.on('titlechange', 'onMainViewTitleChange', me);
        }

        if (items && items.isMixedCollection) {
            if (oldMainView) {
                me.remove(oldMainView);
            }

            if (mainView) {
                me.insert(0, mainView);
            }
        }
    },

    // container lifecycle
    initItems: function() {
        var me = this,
            mainView = me.getMainView();

        me.callParent(arguments);

        if (mainView) {
            me.insert(0, mainView);
        }
    },

    // event handlers
    onMainViewTitleChange: function(mainView, title) {
        this.setTitle(title);
    }
});<|endoftext|>


<commit_before>(function(global) {

  var SETTINGS_KEY = "remotestorage:access";

  /**
   * Class: RemoteStorage.Access
   *
   * Keeps track of claimed access and scopes.
   */
  RemoteStorage.Access = function() {
    this.reset();
  };

  RemoteStorage.Access.prototype = {

    /**
     * Method: claim
     *
     * Claim access on a given scope with given mode.
     *
     * Parameters:
     *   scope - An access scope, such as "contacts" or "calendar".
     *   mode  - Access mode to use. Either "r" or "rw".
     *
     * Example:
     *   (start code)
     *   remoteStorage.access.claim('contacts', 'r');
     *   remoteStorage.access.claim('pictures', 'rw');
     */
    claim: function(scope, mode) {
      if (typeof(scope) !== 'string' || scope.indexOf('/') !== -1 || scope.length === 0) {
        throw new Error('Scope should be a non-empty string without forward slashes');
      }
      if (!mode.match(/^rw?$/)) {
        throw new Error('Mode should be either \'r\' or \'rw\'');
      }
      this._adjustRootPaths(scope);
      this.scopeModeMap[scope] = mode;
    },

    get: function(scope) {
      return this.scopeModeMap[scope];
    },

    remove: function(scope) {
      var savedMap = {};
      var name;
      for (name in this.scopeModeMap) {
        savedMap[name] = this.scopeModeMap[name];
      }
      this.reset();
      delete savedMap[scope];
      for (name in savedMap) {
        this.set(name, savedMap[name]);
      }
    },

    /**
     * Verify permission for a given scope.
     */
    checkPermission: function(scope, mode) {
      var actualMode = this.get(scope);
      return actualMode && (mode === 'r' || actualMode === 'rw');
    },

    /**
     * Verify permission for a given path.
     */
    checkPathPermission: function(path, mode) {
      if (this.checkPermission('*', mode)) {
        return true;
      }
      return !!this.checkPermission(this._getModuleName(path), mode);
    },

    reset: function() {
      this.rootPaths = [];
      this.scopeModeMap = {};
    },

    /**
     * Return the module name for a given path.
     */
    _getModuleName: function(path) {
      if (path[0] !== '/') {
        throw new Error('Path should start with a slash');
      }
      var moduleMatch = path.replace(/^\/public/, '').match(/^\/([^\/]*)\//);
      return moduleMatch ? moduleMatch[1] : '*';
    },

    _adjustRootPaths: function(newScope) {
      if ('*' in this.scopeModeMap || newScope === '*') {
        this.rootPaths = ['/'];
      } else if (! (newScope in this.scopeModeMap)) {
        this.rootPaths.push('/' + newScope + '/');
        this.rootPaths.push('/public/' + newScope + '/');
      }
    },

    _scopeNameForParameter: function(scope) {
      if (scope.name === '*' && this.storageType) {
        if (this.storageType === '2012.04') {
          return '';
        } else if (this.storageType.match(/remotestorage-0[01]/)) {
          return 'root';
        }
      }
      return scope.name;
    },

    setStorageType: function(type) {
      this.storageType = type;
    }
  };

  /**
   * Property: scopes
   *
   * Holds an array of claimed scopes in the form
   * > { name: "<scope-name>", mode: "<mode>" }
   */
  Object.defineProperty(RemoteStorage.Access.prototype, 'scopes', {
    get: function() {
      return Object.keys(this.scopeModeMap).map(function(key) {
        return { name: key, mode: this.scopeModeMap[key] };
      }.bind(this));
    }
  });

  Object.defineProperty(RemoteStorage.Access.prototype, 'scopeParameter', {
    get: function() {
      return this.scopes.map(function(scope) {
        return this._scopeNameForParameter(scope) + ':' + scope.mode;
      }.bind(this)).join(' ');
    }
  });

  // Documented in src/remotestorage.js
  Object.defineProperty(RemoteStorage.prototype, 'access', {
    get: function() {
      var access = new RemoteStorage.Access();
      Object.defineProperty(this, 'access', {
        value: access
      });
      return access;
    },
    configurable: true
  });

  RemoteStorage.Access._rs_init = function() {};
})(typeof(window) !== 'undefined' ? window : global);
<commit_msg>Improve docs for `access.claim` (fixes #825)<commit_after>(function(global) {

  var SETTINGS_KEY = "remotestorage:access";

  /**
   * Class: RemoteStorage.Access
   *
   * Keeps track of claimed access and scopes.
   */
  RemoteStorage.Access = function() {
    this.reset();
  };

  RemoteStorage.Access.prototype = {

    /**
     * Method: claim
     *
     * Claim access on a given scope with given mode.
     *
     * Parameters:
     *   scope - An access scope, such as "contacts" or "calendar"
     *   mode  - Access mode. Either "r" for read-only or "rw" for read/write
     *
     * Example:
     *   (start code)
     *   remoteStorage.access.claim('contacts', 'r');
     *   remoteStorage.access.claim('pictures', 'rw');
     *   (end code)
     *
     * Root access:
     *   Claiming root access, meaning complete access to all files and folders
     *   of a storage, can be done using an asterisk:
     *
     *   (start code)
     *   remoteStorage.access.claim('*', 'rw');
     *   (end code)
     */
    claim: function(scope, mode) {
      if (typeof(scope) !== 'string' || scope.indexOf('/') !== -1 || scope.length === 0) {
        throw new Error('Scope should be a non-empty string without forward slashes');
      }
      if (!mode.match(/^rw?$/)) {
        throw new Error('Mode should be either \'r\' or \'rw\'');
      }
      this._adjustRootPaths(scope);
      this.scopeModeMap[scope] = mode;
    },

    get: function(scope) {
      return this.scopeModeMap[scope];
    },

    remove: function(scope) {
      var savedMap = {};
      var name;
      for (name in this.scopeModeMap) {
        savedMap[name] = this.scopeModeMap[name];
      }
      this.reset();
      delete savedMap[scope];
      for (name in savedMap) {
        this.set(name, savedMap[name]);
      }
    },

    /**
     * Verify permission for a given scope.
     */
    checkPermission: function(scope, mode) {
      var actualMode = this.get(scope);
      return actualMode && (mode === 'r' || actualMode === 'rw');
    },

    /**
     * Verify permission for a given path.
     */
    checkPathPermission: function(path, mode) {
      if (this.checkPermission('*', mode)) {
        return true;
      }
      return !!this.checkPermission(this._getModuleName(path), mode);
    },

    reset: function() {
      this.rootPaths = [];
      this.scopeModeMap = {};
    },

    /**
     * Return the module name for a given path.
     */
    _getModuleName: function(path) {
      if (path[0] !== '/') {
        throw new Error('Path should start with a slash');
      }
      var moduleMatch = path.replace(/^\/public/, '').match(/^\/([^\/]*)\//);
      return moduleMatch ? moduleMatch[1] : '*';
    },

    _adjustRootPaths: function(newScope) {
      if ('*' in this.scopeModeMap || newScope === '*') {
        this.rootPaths = ['/'];
      } else if (! (newScope in this.scopeModeMap)) {
        this.rootPaths.push('/' + newScope + '/');
        this.rootPaths.push('/public/' + newScope + '/');
      }
    },

    _scopeNameForParameter: function(scope) {
      if (scope.name === '*' && this.storageType) {
        if (this.storageType === '2012.04') {
          return '';
        } else if (this.storageType.match(/remotestorage-0[01]/)) {
          return 'root';
        }
      }
      return scope.name;
    },

    setStorageType: function(type) {
      this.storageType = type;
    }
  };

  /**
   * Property: scopes
   *
   * Holds an array of claimed scopes in the form
   * > { name: "<scope-name>", mode: "<mode>" }
   */
  Object.defineProperty(RemoteStorage.Access.prototype, 'scopes', {
    get: function() {
      return Object.keys(this.scopeModeMap).map(function(key) {
        return { name: key, mode: this.scopeModeMap[key] };
      }.bind(this));
    }
  });

  Object.defineProperty(RemoteStorage.Access.prototype, 'scopeParameter', {
    get: function() {
      return this.scopes.map(function(scope) {
        return this._scopeNameForParameter(scope) + ':' + scope.mode;
      }.bind(this)).join(' ');
    }
  });

  // Documented in src/remotestorage.js
  Object.defineProperty(RemoteStorage.prototype, 'access', {
    get: function() {
      var access = new RemoteStorage.Access();
      Object.defineProperty(this, 'access', {
        value: access
      });
      return access;
    },
    configurable: true
  });

  RemoteStorage.Access._rs_init = function() {};
})(typeof(window) !== 'undefined' ? window : global);
<|endoftext|>


<commit_before>import Emitter from './emitter'
import coroutine from './coroutine'
import tag from './tag'
import { inherit } from './utils'

/**
 * Actions encapsulate the process of resolving an action creator. Create an
 * action using `Microcosm::push`:
 * @constructor
 * @extends {Emitter}
 */
export default function Action (behavior) {
  Emitter.call(this)
  this.behavior = tag(behavior)
}

inherit(Action, Emitter, {
  type      : null,
  payload   : null,
  disabled  : false,
  diposable : false,
  parent    : null,
  sibling   : null,

  /**
   * Given a string or State constant, determine if the `state` bitmask for
   * the action includes the provided type.
   * @private
   */
  is (type) {
    return this.type === this.behavior[type]
  },

  /**
   * Evaluate the action by invoking the action's behavior with provided
   * parameters. Then pass that value into the `coroutine` function, which will
   * update the state of the action as it processes.
   */
  execute (params, repo) {
    coroutine(this, this.behavior.apply(this, params), repo)

    return this
  },

  /**
   * If defined, sets the payload for the action and triggers a "change" event.
   */
  set (type, payload, disposable) {
    // Ignore set if the action is already disposed.
    if (this.disposable) {
      return false
    }

    this.type = this.behavior[type]
    this.disposable = disposable

    if (payload != undefined) {
      this.payload = payload
    }

    this._emit('change', this)

    return true
  },

  /**
   * Set the action state to "open", then set a payload if provided. Triggers
   * the "open" event.
   */
  open (payload) {
    if (this.set('open', payload, false)) {
      this._emit('open', this.payload)
    }

    return this
  },

  /**
   * Set the action state to "loading", then set a payload if provided.
   * Triggers the "update" event.
   */
  send (payload) {
    if (this.set('loading', payload, false)) {
      this._emit('update', payload)
    }

    return this
  },

  /**
   * Set the action state to "error" and marks the action for clean up, then
   * set a payload if provided. Triggers the "error" event.
   */
  reject (payload) {
    if (this.set('error', payload, true)) {
      this._emit('error', payload)
    }

    return this
  },

  /**
   * Set the action state to "done" and marks the action for clean up, then set
   * a payload if provided. Triggers the "done" event.
   */
  resolve (payload) {
    if (this.set('done', payload, true)) {
      this._emit('done', this.payload)
    }

    return this
  },

  /**
   * Set the action state to "cancelled" and marks the action for clean up,
   * then set a payload if provided. Triggers the "cancel" event.
   */
  cancel (payload) {
    if (this.set('cancelled', payload, true)) {
      this._emit('cancel', this.payload)
    }

    return this
  },

  /**
   * Toggles the disabled state, where the action will not dispatch. This is
   * useful in the Microcosm debugger to quickly enable/disable actions.
   * Triggers the "change" event.
   */
  toggle () {
    this.disabled = !this.disabled

    return this._emit('change', this)
  },

  /**
   * Listen to failure. If the action has already failed, it will execute the
   * provided callback, otherwise it will wait and trigger upon the "error"
   * event.
   */
  onError (callback, scope) {
    if (!callback) {
      return this
    }

    if (this.is('error')) {
      callback.call(scope, this.payload)
    } else {
      this.once('error', callback, scope)
    }

    return this
  },

  /**
   * Listen to progress. Wait and trigger a provided callback on the "update" event.
   */
  onUpdate (callback, scope) {
    if (!callback) {
      return this
    }

    this.on('update', callback, scope)

    return this
  },

  /**
   * Listen for completion. If the action has already completed, it will
   * execute the provided callback, otherwise it will wait and trigger upon the
   * "done" event.
   */
  onDone (callback, scope) {
    if (!callback) {
      return this
    }

    if (this.is('done')) {
      callback.call(scope, this.payload)
    } else {
      this.once('done', callback, scope)
    }

    return this
  },

  /**
   * Listen for cancel. If the action has already cancelled, it will execute
   * the provided callback, otherwise it will wait and trigger upon the
   * "cancel" event.
   */
  onCancel (callback, scope) {
    if (!callback) {
      return this
    }

    if (this.is('cancelled')) {
      callback.call(scope, this.payload)
    } else {
      this.once('cancel', callback, scope)
    }

    return this
  },

  /**
   * For interop with promises. Returns a promise that resolves or rejects
   * based on the action's resolution.
   */
  then (pass, fail) {
    return new Promise((resolve, reject) => {
      this.onDone(resolve)
      this.onError(reject)
    }).then(pass, fail)
  },

  /**
   * Cleanup an action that has been disconnected from its history.
   */
  teardown () {
    this.parent = null
    this.sibling = null

    if (this.next) {
      this.next.parent = null
    }

    // Remove generic change events to free from history reconciliation
    this.off('change')
  }

})

/**
 * Get all child actions. Used by the Microcosm debugger to visualize history.
 */
Object.defineProperty(Action.prototype, 'children', {
  get () {
    let start = this.next
    let nodes = []

    while (start) {
      nodes.push(start)
      start = start.sibling
    }

    return nodes
  }
})
<commit_msg>Action payload is undefined by default<commit_after>import Emitter from './emitter'
import coroutine from './coroutine'
import tag from './tag'
import { inherit } from './utils'

/**
 * Actions encapsulate the process of resolving an action creator. Create an
 * action using `Microcosm::push`:
 * @constructor
 * @extends {Emitter}
 */
export default function Action (behavior) {
  Emitter.call(this)
  this.behavior = tag(behavior)
}

inherit(Action, Emitter, {
  type      : null,
  payload   : undefined,
  disabled  : false,
  diposable : false,
  parent    : null,
  sibling   : null,

  /**
   * Given a string or State constant, determine if the `state` bitmask for
   * the action includes the provided type.
   * @private
   */
  is (type) {
    return this.type === this.behavior[type]
  },

  /**
   * Evaluate the action by invoking the action's behavior with provided
   * parameters. Then pass that value into the `coroutine` function, which will
   * update the state of the action as it processes.
   */
  execute (params, repo) {
    coroutine(this, this.behavior.apply(this, params), repo)

    return this
  },

  /**
   * If defined, sets the payload for the action and triggers a "change" event.
   */
  set (type, payload, disposable) {
    // Ignore set if the action is already disposed.
    if (this.disposable) {
      return false
    }

    this.type = this.behavior[type]
    this.disposable = disposable

    if (payload != undefined) {
      this.payload = payload
    }

    this._emit('change', this)

    return true
  },

  /**
   * Set the action state to "open", then set a payload if provided. Triggers
   * the "open" event.
   */
  open (payload) {
    if (this.set('open', payload, false)) {
      this._emit('open', this.payload)
    }

    return this
  },

  /**
   * Set the action state to "loading", then set a payload if provided.
   * Triggers the "update" event.
   */
  send (payload) {
    if (this.set('loading', payload, false)) {
      this._emit('update', payload)
    }

    return this
  },

  /**
   * Set the action state to "error" and marks the action for clean up, then
   * set a payload if provided. Triggers the "error" event.
   */
  reject (payload) {
    if (this.set('error', payload, true)) {
      this._emit('error', payload)
    }

    return this
  },

  /**
   * Set the action state to "done" and marks the action for clean up, then set
   * a payload if provided. Triggers the "done" event.
   */
  resolve (payload) {
    if (this.set('done', payload, true)) {
      this._emit('done', this.payload)
    }

    return this
  },

  /**
   * Set the action state to "cancelled" and marks the action for clean up,
   * then set a payload if provided. Triggers the "cancel" event.
   */
  cancel (payload) {
    if (this.set('cancelled', payload, true)) {
      this._emit('cancel', this.payload)
    }

    return this
  },

  /**
   * Toggles the disabled state, where the action will not dispatch. This is
   * useful in the Microcosm debugger to quickly enable/disable actions.
   * Triggers the "change" event.
   */
  toggle () {
    this.disabled = !this.disabled

    return this._emit('change', this)
  },

  /**
   * Listen to failure. If the action has already failed, it will execute the
   * provided callback, otherwise it will wait and trigger upon the "error"
   * event.
   */
  onError (callback, scope) {
    if (!callback) {
      return this
    }

    if (this.is('error')) {
      callback.call(scope, this.payload)
    } else {
      this.once('error', callback, scope)
    }

    return this
  },

  /**
   * Listen to progress. Wait and trigger a provided callback on the "update" event.
   */
  onUpdate (callback, scope) {
    if (!callback) {
      return this
    }

    this.on('update', callback, scope)

    return this
  },

  /**
   * Listen for completion. If the action has already completed, it will
   * execute the provided callback, otherwise it will wait and trigger upon the
   * "done" event.
   */
  onDone (callback, scope) {
    if (!callback) {
      return this
    }

    if (this.is('done')) {
      callback.call(scope, this.payload)
    } else {
      this.once('done', callback, scope)
    }

    return this
  },

  /**
   * Listen for cancel. If the action has already cancelled, it will execute
   * the provided callback, otherwise it will wait and trigger upon the
   * "cancel" event.
   */
  onCancel (callback, scope) {
    if (!callback) {
      return this
    }

    if (this.is('cancelled')) {
      callback.call(scope, this.payload)
    } else {
      this.once('cancel', callback, scope)
    }

    return this
  },

  /**
   * For interop with promises. Returns a promise that resolves or rejects
   * based on the action's resolution.
   */
  then (pass, fail) {
    return new Promise((resolve, reject) => {
      this.onDone(resolve)
      this.onError(reject)
    }).then(pass, fail)
  },

  /**
   * Cleanup an action that has been disconnected from its history.
   */
  teardown () {
    this.parent = null
    this.sibling = null

    if (this.next) {
      this.next.parent = null
    }

    // Remove generic change events to free from history reconciliation
    this.off('change')
  }

})

/**
 * Get all child actions. Used by the Microcosm debugger to visualize history.
 */
Object.defineProperty(Action.prototype, 'children', {
  get () {
    let start = this.next
    let nodes = []

    while (start) {
      nodes.push(start)
      start = start.sibling
    }

    return nodes
  }
})
<|endoftext|>