<commit_before>/**
 * Base Grid
 *
 * @class Hatimeria.core.grid.BaseGrid
 * @extends Ext.grid.Panel
 *

Ext.define('Foo.Bar', {
    extend: 'Hatimeria.core.grid.BaseGrid',
    initComponent: function() {
    Ext.apply(this, {
            windowEditClass: 'Foo.BarWindow',
            rowActions: ['edit', 'clone', 'remove'],
            dockedElements: ['paging', 'add'],
            actionColumn: true
            });
        }
    });

 *
 */
(function() {

    Ext.define('Hatimeria.core.grid.BaseGrid', {
        extend: 'Ext.grid.Panel',
        alternateClassName: 'HatimeriaAdmin.core.grid.BaseGrid',
        mixins: {
            configurable: 'Hatimeria.core.mixins.ConfigurableExternal',
            translationable: 'Hatimeria.core.mixins.Translationable'
        },

        /**
         * Row operations
         *
         * predefined:
         *   - edit
         *   - clone
         *   - remove
         *
         * @cfg {Array/String/{Object} rowActions
         *

                // Simple array
                rowActions: ['edit', 'remove', 'clone']
                // Record based label name
                rowActions: ['publish': function(record) {return record.isPublished() ? 'publish':'unpublish'} ]
                // Codename: label object
                rowActions: {
                    edit: 'Edycja',
                    remove: 'Usuń',
                     clone: 'Klonuj',
                }
                 // String
                rowActions: "edit, remove, clone"

         * adding custom handlers:
         * 1. set: rowActions: {'enable': 'Enable it!'}
         * 2. add method: onEnableClick: function(record, index) { ... }
         */
        rowActions: {},

        /**
         * Docked elements (already defined: "paging", "add")
         *
         * @cfg {Array} dockedElements
         */
        dockedElements: ['paging'],

        /**
         * Translate title, headers etc ?
         *
         * @cfg {Boolean} translateAll
         */
        translateAll: false,

        /**
         * Action column added as last column in grid
         *
         * @cfg {String/Boolean} actionColumn
         */
        actionColumn: true,

        /**
         * Class name of edit window
         *
         * @property {String} windowEditClass
         */
        windowEditClass: false,

        /**
         * Class name of entity record
         *
         * @property {String} recordClass
         */
        recordClass: false,

        /**
         * @property {Array} rowActions
         * @private
         */
        defaultRowActions: {
            edit: 'Edycja',
            remove: 'Usuń',
            clone: 'Klonuj'
        },

        /**
         * Add or remove action based on current record
         *
         * @cfg {Function} addConditionalRowActions
         *
         * @return {Object}
         */
        addConditionalRecordActions: function(actions, record) {return actions},

        /**
         *
         * Translation namespace
         *
         * @property
         */
        transNS: 'grid',

        /**
         * Used for translation for current class not extended one
         *
         * @private
         */
        translate: function(key, placeholders)
        {
            return this.statics().prototype.__(key, placeholders);
        },

        /**
         * Initializes component
         */
        initComponent: function()
        {
            var grid = this;
            // Docked items:
            this.dockedItems = this.getDockedElements();

            if(this.translateAll) {
                Ext.Object.each(this.defaultRowActions, function(key, value, property) {
                    property[key] = grid.translate('actions.' + key);
                });
                Ext.each(this.columns, function(column) {
                    if(!column.header) {
                        column.header = grid.__('headers.'+column.dataIndex);
                    }
                });

                this.title = grid.__('title');
            }

            // Action column with row-operations:
            var actions = this.getRowActions();
            if (!Ext.isEmpty(actions) && this.actionColumn)
            {
                this.columns.push(this.getRowActionsColumn());
            }

            this.callParent();

            // Mouse row-operations:
            this.on({
                itemcontextmenu: {
                    scope: this,
                    fn: this.onContextMenu
                },
                itemclick: {
                    scope: this,
                    fn: this.onItemClick
                },
                itemdblclick: {
                    scope: this,
                    fn: function(grid, record, el, index, e) {
                        if (actions['edit'])
                        {
                            this.onEditClick(record, index);
                            e.stopEvent();
                        }
                    }
                }
            });
        },

        /**
         * On item click
         *
         * @private
         */
        onItemClick: function(grid, record, el, index, e) {
            if(e.getTarget().parentNode.className.match(/actions-show-column/)) {
                this.onContextMenu(grid, record, el, index, e);
            }
        },

        /**
         * Docked elements
         *
         * @return {Array}
         */
        getDockedElements: function()
        {
            var items = [];

            // With paging?
            if (Ext.Array.contains(this.dockedElements, 'paging'))
            {
                items.push({
                    xtype: 'pagingtoolbar',
                    dock: 'bottom',
                    store: this.store,
                    displayInfo: true,
                    displayMsg: this.translate('pager.records'),
                    emptyMsg: this.translate('pager.empty')
                })
            }

            // With add-button ?
            if (Ext.Array.contains(this.dockedElements, 'add'))
            {
                items.push({
                    xtype: 'toolbar',
                    docked: 'top',
                    items: [{
                        xtype: 'button',
                        iconCls: 'icon-add',
                        text: this.translate('actions.add'),
                        scope: this,
                        handler: function() {
                            var window = Ext.create(this.getWindowEditClass());
                            window.show();
                            if (this.recordClass)
                            {
                                window.populate(Ext.create(this.getRecordClass()))
                            }
                        }
                    }]
                });
            }

            return items;
        },

        /**
         * Row Actions
         *
         * @private
         * @return {Object}
         */
        getRowActions: function(record)
        {
            if (Ext.isArray(this.rowActions) || Ext.isString(this.rowActions))
            {
                if (Ext.isString(this.rowActions))
                {
                    this.rowActions = this.rowActions.split(/ ?, ?/g);
                }
                if (Ext.isArray(this.rowActions))
                {
                    var actions = {};
                    for (var i=0; i<this.rowActions.length; i++)
                    {
                        var item = this.rowActions[i];
                        actions[item] = this.defaultRowActions[item] ? this.defaultRowActions[item] : Ext.String.capitalize(item) ;
                    }
                    this.rowActions = actions;
                }
            }

            return this.rowActions;
        },

        /**
         * Action column with row-operations
         *
         * @private
         * @return {Object}
         */
        getRowActionsColumn: function()
        {
            var grid = this;

            return {
                tdCls: 'actions-show-column',
                width: 100,
                header: ' ',
                renderer: function() {
                    return grid.translate('options')
                }
            };
        },

        /**
         * Edit window class name
         *
         * @return {String}
         */
        getWindowEditClass: function()
        {
            return this.windowEditClass;
        },

        /**
         * Record class name
         *
         * @return {String}
         */
        getRecordClass: function()
        {
            return this.recordClass;
        },

        /**
         * Merges external config
         *
         * @param {Object} config
         * @return {Object}
         */
        applyExternal: function(cfg)
        {
            var config = this.getConnectedConfig();

            if (config && config.columns)
            {
                for (var i in config.columns)
                {
                    cfg.columns.push(config.columns[i]);
                }
            }

            return cfg;
        },

        /**
         * Yes/No renderer
         *
         * @return {String}
         */
        rendererYesNo: function(value)
        {
            return value ? this.translate('yes'): this.translate('no');
        },

        /**
         * Items for context menu
         *
         * @return {Array}
         */
        getContextMenuItems: function(record, index)
        {
            var items = [];
            var actions = Ext.clone(this.getRowActions(record));
            this.addConditionalRecordActions(actions, record);

            for (var name in actions)
            {
                var label = actions[name];
                (function(name, scope) {
                    items.push({
                        text: typeof label == 'function' ? label(record) : label,
                        cls: 'ux-' + name,
                        scope: scope,
                        handler: function() {
                            scope['on' + Ext.String.capitalize(name) + 'Click'](record, index);
                        }
                    });
                })(name, this);
            }

            return items;
        },

        /**
         * Event: right click on row
         * @private
         *
         * @param {Ext.tree.Panel} grid
         * @param {Ext.data.Node} record
         * @param {DOMElement} el
         * @param {Number} index
         * @param {Ext.Object.Event} event
         */
        onContextMenu: function(grid, record, el, index, event)
        {
            if (!Ext.isEmpty(this.getRowActions()))
            {
                Ext.create('Ext.menu.Menu', {
                    items: this.getContextMenuItems(record, index)
                }).showAt(event.getXY());

                event.stopEvent();
            }
        },

        /**
         * Event: edit click
         * @private
         *
         * @param {Ext.data.Model} record
         * @param {Number} index
         */
        onEditClick: function(record)
        {
            var editWindow = Ext.create(this.getWindowEditClass());
            editWindow.show();
            editWindow.populate(record);
        },

        /**
         * Cloning record
         * @private
         *
         * @param {Ext.data.Model} record
         * @param {Number} index
         */
        onCloneClick: function(record, index)
        {
            var editWindow = Ext.create(this.getWindowEditClass());
            editWindow.show();
            var newRecord = record.copy();
            newRecord.setId('');
            editWindow.populate(newRecord);
        },

        /**
         * Event: remove click
         * @private
         *
         * @param {Ext.data.Model} record
         */
        onRemoveClick: function(record)
        {
            var store = this.store;
            Ext.Msg.confirm(this.translate('remove.confirm.title'), this.translate('remove.confirm.text'), function(response) {
                if (response == 'yes')
                {
                    record.destroy({
                        success: function() {
                            store.load();
                        }
                    });
                }
            });
        }
    });

})();<commit_msg>class in add-button (BaseGrid)<commit_after>/**
 * Base Grid
 *
 * @class Hatimeria.core.grid.BaseGrid
 * @extends Ext.grid.Panel
 *

Ext.define('Foo.Bar', {
    extend: 'Hatimeria.core.grid.BaseGrid',
    initComponent: function() {
    Ext.apply(this, {
            windowEditClass: 'Foo.BarWindow',
            rowActions: ['edit', 'clone', 'remove'],
            dockedElements: ['paging', 'add'],
            actionColumn: true
            });
        }
    });

 *
 */
(function() {

    Ext.define('Hatimeria.core.grid.BaseGrid', {
        extend: 'Ext.grid.Panel',
        alternateClassName: 'HatimeriaAdmin.core.grid.BaseGrid',
        mixins: {
            configurable: 'Hatimeria.core.mixins.ConfigurableExternal',
            translationable: 'Hatimeria.core.mixins.Translationable'
        },

        /**
         * Row operations
         *
         * predefined:
         *   - edit
         *   - clone
         *   - remove
         *
         * @cfg {Array/String/{Object} rowActions
         *

                // Simple array
                rowActions: ['edit', 'remove', 'clone']
                // Record based label name
                rowActions: ['publish': function(record) {return record.isPublished() ? 'publish':'unpublish'} ]
                // Codename: label object
                rowActions: {
                    edit: 'Edycja',
                    remove: 'Usuń',
                     clone: 'Klonuj',
                }
                 // String
                rowActions: "edit, remove, clone"

         * adding custom handlers:
         * 1. set: rowActions: {'enable': 'Enable it!'}
         * 2. add method: onEnableClick: function(record, index) { ... }
         */
        rowActions: {},

        /**
         * Docked elements (already defined: "paging", "add")
         *
         * @cfg {Array} dockedElements
         */
        dockedElements: ['paging'],

        /**
         * Translate title, headers etc ?
         *
         * @cfg {Boolean} translateAll
         */
        translateAll: false,

        /**
         * Action column added as last column in grid
         *
         * @cfg {String/Boolean} actionColumn
         */
        actionColumn: true,

        /**
         * Class name of edit window
         *
         * @property {String} windowEditClass
         */
        windowEditClass: false,

        /**
         * Class name of entity record
         *
         * @property {String} recordClass
         */
        recordClass: false,

        /**
         * @property {Array} rowActions
         * @private
         */
        defaultRowActions: {
            edit: 'Edycja',
            remove: 'Usuń',
            clone: 'Klonuj'
        },

        /**
         * Add or remove action based on current record
         *
         * @cfg {Function} addConditionalRowActions
         *
         * @return {Object}
         */
        addConditionalRecordActions: function(actions, record) {return actions},

        /**
         *
         * Translation namespace
         *
         * @property
         */
        transNS: 'grid',

        /**
         * Used for translation for current class not extended one
         *
         * @private
         */
        translate: function(key, placeholders)
        {
            return this.statics().prototype.__(key, placeholders);
        },

        /**
         * Initializes component
         */
        initComponent: function()
        {
            var grid = this;
            // Docked items:
            this.dockedItems = this.getDockedElements();

            if(this.translateAll) {
                Ext.Object.each(this.defaultRowActions, function(key, value, property) {
                    property[key] = grid.translate('actions.' + key);
                });
                Ext.each(this.columns, function(column) {
                    if(!column.header) {
                        column.header = grid.__('headers.'+column.dataIndex);
                    }
                });

                this.title = grid.__('title');
            }

            // Action column with row-operations:
            var actions = this.getRowActions();
            if (!Ext.isEmpty(actions) && this.actionColumn)
            {
                this.columns.push(this.getRowActionsColumn());
            }

            this.callParent();

            // Mouse row-operations:
            this.on({
                itemcontextmenu: {
                    scope: this,
                    fn: this.onContextMenu
                },
                itemclick: {
                    scope: this,
                    fn: this.onItemClick
                },
                itemdblclick: {
                    scope: this,
                    fn: function(grid, record, el, index, e) {
                        if (actions['edit'])
                        {
                            this.onEditClick(record, index);
                            e.stopEvent();
                        }
                    }
                }
            });
        },

        /**
         * On item click
         *
         * @private
         */
        onItemClick: function(grid, record, el, index, e) {
            if(e.getTarget().parentNode.className.match(/actions-show-column/)) {
                this.onContextMenu(grid, record, el, index, e);
            }
        },

        /**
         * Docked elements
         *
         * @return {Array}
         */
        getDockedElements: function()
        {
            var items = [];

            // With paging?
            if (Ext.Array.contains(this.dockedElements, 'paging'))
            {
                items.push({
                    xtype: 'pagingtoolbar',
                    dock: 'bottom',
                    store: this.store,
                    displayInfo: true,
                    displayMsg: this.translate('pager.records'),
                    emptyMsg: this.translate('pager.empty')
                })
            }

            // With add-button ?
            if (Ext.Array.contains(this.dockedElements, 'add'))
            {
                items.push({
                    xtype: 'toolbar',
                    docked: 'top',
                    items: [{
                        xtype: 'button',
                        iconCls: 'icon-add',
                        cls: 'ux-icon-add-btn',
                        text: this.translate('actions.add'),
                        scope: this,
                        handler: function() {
                            var window = Ext.create(this.getWindowEditClass());
                            window.show();
                            if (this.recordClass)
                            {
                                window.populate(Ext.create(this.getRecordClass()))
                            }
                        }
                    }]
                });
            }

            return items;
        },

        /**
         * Row Actions
         *
         * @private
         * @return {Object}
         */
        getRowActions: function(record)
        {
            if (Ext.isArray(this.rowActions) || Ext.isString(this.rowActions))
            {
                if (Ext.isString(this.rowActions))
                {
                    this.rowActions = this.rowActions.split(/ ?, ?/g);
                }
                if (Ext.isArray(this.rowActions))
                {
                    var actions = {};
                    for (var i=0; i<this.rowActions.length; i++)
                    {
                        var item = this.rowActions[i];
                        actions[item] = this.defaultRowActions[item] ? this.defaultRowActions[item] : Ext.String.capitalize(item) ;
                    }
                    this.rowActions = actions;
                }
            }

            return this.rowActions;
        },

        /**
         * Action column with row-operations
         *
         * @private
         * @return {Object}
         */
        getRowActionsColumn: function()
        {
            var grid = this;

            return {
                tdCls: 'actions-show-column',
                width: 100,
                header: ' ',
                renderer: function() {
                    return grid.translate('options')
                }
            };
        },

        /**
         * Edit window class name
         *
         * @return {String}
         */
        getWindowEditClass: function()
        {
            return this.windowEditClass;
        },

        /**
         * Record class name
         *
         * @return {String}
         */
        getRecordClass: function()
        {
            return this.recordClass;
        },

        /**
         * Merges external config
         *
         * @param {Object} config
         * @return {Object}
         */
        applyExternal: function(cfg)
        {
            var config = this.getConnectedConfig();

            if (config && config.columns)
            {
                for (var i in config.columns)
                {
                    cfg.columns.push(config.columns[i]);
                }
            }

            return cfg;
        },

        /**
         * Yes/No renderer
         *
         * @return {String}
         */
        rendererYesNo: function(value)
        {
            return value ? this.translate('yes'): this.translate('no');
        },

        /**
         * Items for context menu
         *
         * @return {Array}
         */
        getContextMenuItems: function(record, index)
        {
            var items = [];
            var actions = Ext.clone(this.getRowActions(record));
            this.addConditionalRecordActions(actions, record);

            for (var name in actions)
            {
                var label = actions[name];
                (function(name, scope) {
                    items.push({
                        text: typeof label == 'function' ? label(record) : label,
                        cls: 'ux-' + name,
                        scope: scope,
                        handler: function() {
                            scope['on' + Ext.String.capitalize(name) + 'Click'](record, index);
                        }
                    });
                })(name, this);
            }

            return items;
        },

        /**
         * Event: right click on row
         * @private
         *
         * @param {Ext.tree.Panel} grid
         * @param {Ext.data.Node} record
         * @param {DOMElement} el
         * @param {Number} index
         * @param {Ext.Object.Event} event
         */
        onContextMenu: function(grid, record, el, index, event)
        {
            if (!Ext.isEmpty(this.getRowActions()))
            {
                Ext.create('Ext.menu.Menu', {
                    items: this.getContextMenuItems(record, index)
                }).showAt(event.getXY());

                event.stopEvent();
            }
        },

        /**
         * Event: edit click
         * @private
         *
         * @param {Ext.data.Model} record
         * @param {Number} index
         */
        onEditClick: function(record)
        {
            var editWindow = Ext.create(this.getWindowEditClass());
            editWindow.show();
            editWindow.populate(record);
        },

        /**
         * Cloning record
         * @private
         *
         * @param {Ext.data.Model} record
         * @param {Number} index
         */
        onCloneClick: function(record, index)
        {
            var editWindow = Ext.create(this.getWindowEditClass());
            editWindow.show();
            var newRecord = record.copy();
            newRecord.setId('');
            editWindow.populate(newRecord);
        },

        /**
         * Event: remove click
         * @private
         *
         * @param {Ext.data.Model} record
         */
        onRemoveClick: function(record)
        {
            var store = this.store;
            Ext.Msg.confirm(this.translate('remove.confirm.title'), this.translate('remove.confirm.text'), function(response) {
                if (response == 'yes')
                {
                    record.destroy({
                        success: function() {
                            store.load();
                        }
                    });
                }
            });
        }
    });

})();<|endoftext|>


<commit_before>'use strict';

var isObject = require('isobject');
var File = require('vinyl');

/**
 * Returns true if a file is a vinyl file object.
 *
 * ```js
 * var file = utils.isVinyl(file);
 * ```
 *
 * @name .isVinyl
 * @param  {Object} `file` Object to test.
 * @return {Boolean} Returns true if it's a vinyl file.
 * @api public
 */

module.exports = function isVinyl_(file) {
  return isObject(file) && file instanceof File;
};
<commit_msg>allow instance of vinyl to be passed<commit_after>'use strict';

var isObject = require('isobject');

/**
 * Returns true if a file is a vinyl file object.
 *
 * ```js
 * var file = utils.isVinyl(file);
 * ```
 *
 * @name .isVinyl
 * @param  {Object} `file` Object to test.
 * @return {Boolean} Returns true if it's a vinyl file.
 * @api public
 */

module.exports = function isVinyl(file, Vinyl) {
  if (!isObject(file)) return false;

  if (Vinyl && file instanceof Vinyl) {
    return true;
  }
  if (file.path && Buffer.isBuffer(file.contents)) {
    return true;
  }
  return false;
};
<|endoftext|>


<commit_before>'use strict';

var pretty = require('prettyjson');
var arrify = require('arrify');
var debug = require('debug');
var clor = require('clor');
var _ = debug('fly:log');

/**
 * Apply args to the `console[method]` & Add a date stamp.
 * Bind `this` to an object with the following options:
 *
 * @param  {String} args.date 		The color string to use for the date
 * @param  {String} args.method 	The `console` method to use
 * @param  {String} args.custom 	The custom styling to append to args
 */
function stamp() {
	var args = [].slice.call(arguments);

	if (process.env.DEBUG) {
		return _.apply(_, args);
	}
	// print the curr time.
	var time = clor[this.color](datefmt(new Date(), 'HH:MM:ss'));
	process.stdout.write('[' + time + '] ');

	// apply arguments to `console` method
	console[this.method].apply(console, (this.custom ? [this.custom].concat(args) : args));
}

/**
 * Logging Utilities
 */

function log() {
	stamp.apply({method: 'log', color: 'magenta'}, arguments);
	return this;
}

function error() {
	stamp.apply({method: 'error', color: 'red'}, arguments);
	return this;
}

function alert() {
	if (process.env.VERBOSE) {
		stamp.apply({
			custom: clor.yellow.bold('%s'),
			method: 'log',
			color: 'yellow'
		}, arguments);
	}
	return this;
}

/**
 * PrettyJSON wrapper & stack tracer
 * @param  {Object} e The Error object
 */
function trace(e) {
	var msg = pretty.render(e)
		.replace(/(\sFunction|\sObject)\./g, clor.blue('$1') + '.')
		.replace(/\((~?\/.*)\)/g, '(' + clor.gray('$1') + ')')
		.replace(/:([0-9]*):([0-9]*)/g, ' ' + clor.yellow('$1') + ':' + clor.yellow('$2'))
		.replace(new RegExp(process.env.HOME, 'g'), '~');
	console.error(msg);
}

module.exports = {
	alert: alert,
	error: error,
	log: log,
	stamp: stamp,
	trace: trace
};
<commit_msg>add timestamp() in place of bigger 'dateformat' pkg<commit_after>'use strict';

var pretty = require('prettyjson');
var debug = require('debug');
var clor = require('clor');
var _ = debug('fly:log');

/**
 * Apply args to the `console[method]` & Add a date stamp.
 * Bind `this` to an object with the following options:
 *
 * @param  {String} args.date 		The color string to use for the date
 * @param  {String} args.method 	The `console` method to use
 * @param  {String} args.custom 	The custom styling to append to args
 */
function stamp() {
	var args = [].slice.call(arguments);

	if (process.env.DEBUG) {
		return _.apply(_, args);
	}

	// print the curr time.
	process.stdout.write('[' + clor[this.color](timestamp()) + '] ');

	// apply arguments to `console` method
	console[this.method].apply(console, (this.custom ? [this.custom].concat(args) : args));
}

/**
 * Quickly format the current timestamp
 * @return {String}		 as `HH:MM:ss`
 */
function timestamp() {
	var now = new Date();
	return [
		now.getHours(),
		now.getMinutes(),
		now.getSeconds()
	].join(':');
}

/**
 * Logging Utilities
 */

function log() {
	stamp.apply({method: 'log', color: 'magenta'}, arguments);
	return this;
}

function error() {
	stamp.apply({method: 'error', color: 'red'}, arguments);
	return this;
}

function alert() {
	if (process.env.VERBOSE) {
		stamp.apply({
			custom: clor.yellow.bold('%s'),
			method: 'log',
			color: 'yellow'
		}, arguments);
	}
	return this;
}

/**
 * PrettyJSON wrapper & stack tracer
 * @param  {Object} e The Error object
 */
function trace(e) {
	var msg = pretty.render(e)
		.replace(/(\sFunction|\sObject)\./g, clor.blue('$1') + '.')
		.replace(/\((~?\/.*)\)/g, '(' + clor.gray('$1') + ')')
		.replace(/:([0-9]*):([0-9]*)/g, ' ' + clor.yellow('$1') + ':' + clor.yellow('$2'))
		.replace(new RegExp(process.env.HOME, 'g'), '~');
	console.error(msg);
}

module.exports = {
	alert: alert,
	error: error,
	log: log,
	stamp: stamp,
	trace: trace
};
<|endoftext|>


<commit_before>/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Mickael Jeanroy
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

import Backbone from 'backbone';
import {defaults, result, isEmpty, isArray} from 'core/utils';
import {compile} from 'core/compile';
import {templateManager} from 'core/template-manager';

const EVT_PREFIX = 'render';

export class TemplateView extends Backbone.View {
  /**
   * Template / Array of templates to load to render the view.
   * May return:
   *  - A single string.
   *  - An array of strings.
   *
   * Each entry should be:
   *  - A selector in the DOM if view use an instance of DomTemplateManager.
   *  - A template URL if view use an instance of RemoteTemplateManager.
   *
   * @return {string|array<string>} Templates to load in the browser.
   */
  templates() {
    return null;
  }

  /**
   * Default template manager for the view: by default, delegate
   * to`defaults.templateManager` but sub classes may override this function to
   * provide a template manager for this view particularly.
   *
   * @return {TemplateView} The template manager for this view.
   */
  templateManager() {
    return templateManager();
  }

  /**
   * Compile HTML and produce a render function: by default, delegate
   * to`defaults.compile` but sub classes may override this function to
   * provide a custom compile implementation for this view particularly.
   *
   * @param {string} html HTML input.
   * @return {function} Render function.
   */
  compile(html) {
    return compile(html);
  }

  /**
   * Default implementation.
   *
   * This will create an object such as:
   *  ```
   *    {
   *      model: model.toJSON(options),
   *      collection: collection.toJSON(options)
   *    }
   *  ```
   *
   * Note that if `model` or `collection` are not defined, it will not be
   * added to the result object.
   *
   * @param {object?} options Optional options given to the `toJSON` methods of models.
   * @return {object} Data object rendered into the view.
   */
  toJSON(options) {
    // Result object.
    const results = {};

    // Add `view` options to let model knows that `toJSON` is called from a view.
    const opts = defaults(options || {}, {
      view: true
    });

    // Add model if it exists.
    if (this.model) {
      results.model = this.model.toJSON(opts);
    }

    // Add collection if it exists.
    if (this.collection) {
      results.collection = this.collection.toJSON(opts);
    }

    return results;
  }

  /**
   * Default render function:
   *
   *  1. Get view template manager.
   *  2. Fetch templates.
   *  3. Compile templates.
   *  4. Produce data model (calling `toJSON`).
   *  4. Render view with templates.
   *
   * @return {TemplateView} Current view (for chaining).
   */
  render() {
    const templates = result(this, 'templates');
    if (!isEmpty(templates)) {
      // Trigger event
      this.trigger(`${EVT_PREFIX}:loading`);

      // Get view's template manager.
      // Use _.result if template manager is a static variable defined
      // on the class.
      const tmplMngr = result(this, 'templateManager');

      // Fetch templates and render view on success.
      tmplMngr.fetch(templates, {
        success: results => {
          this._renderTemplates(templates, results);
          this.trigger(`${EVT_PREFIX}:success`);
        },

        error: () => {
          this.trigger(`${EVT_PREFIX}:error`);
          // Should we throw an exception?
        }
      });

      return this;
    }
  }

  /**
   * Render templates into the view.
   * This function should not be called directly but it can be overridden
   * with custom logic.
   *
   * @param {string|array<string>} templates Set of templates sources.
   * @param {string|object<string, string>} results Fetched templates.
   * @return {TemplateView} Current view (for chaining).
   */
  _renderTemplates(templates, results) {
    const main = isArray(templates) ? results[0] : results;
    const html = this.compile(main);
    const partials = isArray(results) ? results : null;
    const output = html(this.toJSON(), partials);
    this._setHtml(output);
    return this;
  }

  /**
   * Update view content with new html output.
   * This function should not be called directly but it can be overridden
   * with custom logic (use sanitization for example).
   *
   * @param {string} output New html content.
   * @return {TemplateView} Current view (for chaining).
   */
  _setHtml(output) {
    this.$el.html(output);
    return this;
  }
}

// Add `extend` method from Backbone.
TemplateView.extend = Backbone.View.extend;
<commit_msg>refacto: extract toHTML method from TemplateView<commit_after>/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Mickael Jeanroy
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

import Backbone from 'backbone';
import {defaults, result, isEmpty, isArray} from 'core/utils';
import {compile} from 'core/compile';
import {templateManager} from 'core/template-manager';

const EVT_PREFIX = 'render';

export class TemplateView extends Backbone.View {
  /**
   * Template / Array of templates to load to render the view.
   * May return:
   *  - A single string.
   *  - An array of strings.
   *
   * Each entry should be:
   *  - A selector in the DOM if view use an instance of DomTemplateManager.
   *  - A template URL if view use an instance of RemoteTemplateManager.
   *
   * @return {string|array<string>} Templates to load in the browser.
   */
  templates() {
    return null;
  }

  /**
   * Default template manager for the view: by default, delegate
   * to`defaults.templateManager` but sub classes may override this function to
   * provide a template manager for this view particularly.
   *
   * @return {TemplateView} The template manager for this view.
   */
  templateManager() {
    return templateManager();
  }

  /**
   * Compile HTML and produce a render function: by default, delegate
   * to`defaults.compile` but sub classes may override this function to
   * provide a custom compile implementation for this view particularly.
   *
   * @param {string} html HTML input.
   * @return {function} Render function.
   */
  compile(html) {
    return compile(html);
  }

  /**
   * Default implementation.
   *
   * This will create an object such as:
   *  ```
   *    {
   *      model: model.toJSON(options),
   *      collection: collection.toJSON(options)
   *    }
   *  ```
   *
   * Note that if `model` or `collection` are not defined, it will not be
   * added to the result object.
   *
   * @param {object?} options Optional options given to the `toJSON` methods of models.
   * @return {object} Data object rendered into the view.
   */
  toJSON(options) {
    // Result object.
    const results = {};

    // Add `view` options to let model knows that `toJSON` is called from a view.
    const opts = defaults(options || {}, {
      view: true
    });

    // Add model if it exists.
    if (this.model) {
      results.model = this.model.toJSON(opts);
    }

    // Add collection if it exists.
    if (this.collection) {
      results.collection = this.collection.toJSON(opts);
    }

    return results;
  }

  /**
   * Default render function:
   *
   *  1. Get view template manager.
   *  2. Fetch templates.
   *  3. Compile templates.
   *  4. Produce data model (calling `toJSON`).
   *  4. Render view with templates.
   *
   * @return {TemplateView} Current view (for chaining).
   */
  render() {
    const templates = result(this, 'templates');
    if (!isEmpty(templates)) {
      // Trigger event
      this.trigger(`${EVT_PREFIX}:loading`);

      // Get view's template manager.
      // Use _.result if template manager is a static variable defined
      // on the class.
      const tmplMngr = result(this, 'templateManager');

      // Fetch templates and render view on success.
      tmplMngr.fetch(templates, {
        success: results => {
          this._renderTemplates(templates, results);
          this.trigger(`${EVT_PREFIX}:success`);
        },

        error: () => {
          this.trigger(`${EVT_PREFIX}:error`);
          // Should we throw an exception?
        }
      });

      return this;
    }
  }

  /**
   * Render templates into the view.
   * This function should not be called directly but it can be overridden
   * with custom logic.
   *
   * @param {string|array<string>} templates Set of templates sources.
   * @param {string|object<string, string>} results Fetched templates.
   * @return {TemplateView} Current view (for chaining).
   */
  _renderTemplates(templates, results) {
    const main = isArray(templates) ? results[0] : results;
    const partials = isArray(results) ? results : null;
    const output = this._toHTML(main, partials);
    this._setHtml(output);
    return this;
  }

  /**
   * Produce HTML output from main template and optional partials.
   * This function should not be called directly but it can be overridden
   * with custom logic.
   *
   * @param {string} main Main template.
   * @param {object<string, string>?} partials Optional partials.
   * @return {string} HTML output.
   */
  _toHTML(main, partials) {
    const html = this.compile(main);
    return html(this.toJSON(), partials);
  }

  /**
   * Update view content with new html output.
   * This function should not be called directly but it can be overridden
   * with custom logic (use sanitization for example).
   *
   * @param {string} output New html content.
   * @return {TemplateView} Current view (for chaining).
   */
  _setHtml(output) {
    this.$el.html(output);
    return this;
  }
}

// Add `extend` method from Backbone.
TemplateView.extend = Backbone.View.extend;
<|endoftext|>


<commit_before>import CONST from '../const';
import EventEmitter from 'eventemitter3';

// Internal event used by composed emitter
const TICK = 'tick';

/**
 * A Ticker class that runs an update loop that other objects listen to.
 * This class is composed around an EventEmitter object to add listeners
 * meant for execution on the next requested animation frame.
 * Animation frames are requested only when necessary,
 * e.g. When the ticker is started and the emitter has listeners.
 *
 * @class
 * @memberof PIXI.ticker
 */
class Ticker
{
    constructor()
    {
        /**
         * Internal emitter used to fire 'tick' event
         * @private
         */
        this._emitter = new EventEmitter();

        /**
         * Internal current frame request ID
         * @private
         */
        this._requestId = null;

        /**
         * Internal value managed by minFPS property setter and getter.
         * This is the maximum allowed milliseconds between updates.
         * @private
         */
        this._maxElapsedMS = 100;

        /**
         * Whether or not this ticker should invoke the method
         * {@link PIXI.ticker.Ticker#start} automatically
         * when a listener is added.
         *
         * @member {boolean}
         * @default false
         */
        this.autoStart = false;

        /**
         * Scalar time value from last frame to this frame.
         * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS}
         * and is scaled with {@link PIXI.ticker.Ticker#speed}.
         * **Note:** The cap may be exceeded by scaling.
         *
         * @member {number}
         * @default 1
         */
        this.deltaTime = 1;

        /**
         * Time elapsed in milliseconds from last frame to this frame.
         * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime}
         * is based, this value is neither capped nor scaled.
         * If the platform supports DOMHighResTimeStamp,
         * this value will have a precision of 1 µs.
         *
         * @member {number}
         * @default 1 / TARGET_FPMS
         */
        this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time

        /**
         * The last time {@link PIXI.ticker.Ticker#update} was invoked.
         * This value is also reset internally outside of invoking
         * update, but only when a new animation frame is requested.
         * If the platform supports DOMHighResTimeStamp,
         * this value will have a precision of 1 µs.
         *
         * @member {number}
         * @default 0
         */
        this.lastTime = 0;

        /**
         * Factor of current {@link PIXI.ticker.Ticker#deltaTime}.
         * @example
         * // Scales ticker.deltaTime to what would be
         * // the equivalent of approximately 120 FPS
         * ticker.speed = 2;
         *
         * @member {number}
         * @default 1
         */
        this.speed = 1;

        /**
         * Whether or not this ticker has been started.
         * `true` if {@link PIXI.ticker.Ticker#start} has been called.
         * `false` if {@link PIXI.ticker.Ticker#stop} has been called.
         * While `false`, this value may change to `true` in the
         * event of {@link PIXI.ticker.Ticker#autoStart} being `true`
         * and a listener is added.
         *
         * @member {boolean}
         * @default false
         */
        this.started = false;

        // Bind tick
        this._tick = this._tick.bind(this);
    }

    /**
     * Internal tick method bound to ticker instance.
     * This is because in early 2015, Function.bind
     * is still 60% slower in high performance scenarios.
     * Also separating frame requests from update method
     * so listeners may be called at any time and with
     * any animation API, just invoke ticker.update(time).
     *
     * @private
     */
    _tick(time)
    {
        this._requestId = null;

        if (this.started)
        {
            // Invoke listeners now
            this.update(time);
            // Listener side effects may have modified ticker state.
            if (this.started && this._requestId === null && this._emitter.listeners(TICK, true))
            {
                this._requestId = requestAnimationFrame(this._tick);
            }
        }
    }

    /**
     * Conditionally requests a new animation frame.
     * If a frame has not already been requested, and if the internal
     * emitter has listeners, a new frame is requested.
     *
     * @private
     */
    _requestIfNeeded()
    {
        if (this._requestId === null && this._emitter.listeners(TICK, true))
        {
            // ensure callbacks get correct delta
            this.lastTime = performance.now();
            this._requestId = requestAnimationFrame(this._tick);
        }
    }

    /**
     * Conditionally cancels a pending animation frame.
     *
     * @private
     */
    _cancelIfNeeded()
    {
        if (this._requestId !== null)
        {
            cancelAnimationFrame(this._requestId);
            this._requestId = null;
        }
    }

    /**
     * Conditionally requests a new animation frame.
     * If the ticker has been started it checks if a frame has not already
     * been requested, and if the internal emitter has listeners. If these
     * conditions are met, a new frame is requested. If the ticker has not
     * been started, but autoStart is `true`, then the ticker starts now,
     * and continues with the previous conditions to request a new frame.
     *
     * @private
     */
    _startIfPossible()
    {
        if (this.started)
        {
            this._requestIfNeeded();
        }
        else if (this.autoStart)
        {
            this.start();
        }
    }

    /**
     * Calls {@link module:eventemitter3.EventEmitter#on} internally for the
     * internal 'tick' event. It checks if the emitter has listeners,
     * and if so it requests a new animation frame at this point.
     *
     * @param fn {Function} The listener function to be added for updates
     * @param [context] {Function} The listener context
     * @returns {PIXI.ticker.Ticker} This instance of a ticker
     */
    add(fn, context)
    {
        this._emitter.on(TICK, fn, context);

        this._startIfPossible();

        return this;
    }

    /**
     * Calls {@link module:eventemitter3.EventEmitter#once} internally for the
     * internal 'tick' event. It checks if the emitter has listeners,
     * and if so it requests a new animation frame at this point.
     *
     * @param fn {Function} The listener function to be added for one update
     * @param [context] {Function} The listener context
     * @returns {PIXI.ticker.Ticker} This instance of a ticker
     */
    addOnce(fn, context)
    {
        this._emitter.once(TICK, fn, context);

        this._startIfPossible();

        return this;
    }

    /**
     * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event.
     * It checks if the emitter has listeners for 'tick' event.
     * If it does, then it cancels the animation frame.
     *
     * @param [fn] {Function} The listener function to be removed
     * @param [context] {Function} The listener context to be removed
     * @returns {PIXI.ticker.Ticker} This instance of a ticker
     */
    remove(fn, context)
    {
        this._emitter.off(TICK, fn, context);

        if (!this._emitter.listeners(TICK, true))
        {
            this._cancelIfNeeded();
        }

        return this;
    }

    /**
     * Starts the ticker. If the ticker has listeners
     * a new animation frame is requested at this point.
     */
    start()
    {
        if (!this.started)
        {
            this.started = true;
            this._requestIfNeeded();
        }
    }

    /**
     * Stops the ticker. If the ticker has requested
     * an animation frame it is canceled at this point.
     */
    stop()
    {
        if (this.started)
        {
            this.started = false;
            this._cancelIfNeeded();
        }
    }

    /**
     * Triggers an update. An update entails setting the
     * current {@link PIXI.ticker.Ticker#elapsedMS},
     * the current {@link PIXI.ticker.Ticker#deltaTime},
     * invoking all listeners with current deltaTime,
     * and then finally setting {@link PIXI.ticker.Ticker#lastTime}
     * with the value of currentTime that was provided.
     * This method will be called automatically by animation
     * frame callbacks if the ticker instance has been started
     * and listeners are added.
     *
     * @param [currentTime=performance.now()] {number} the current time of execution
     */
    update(currentTime)
    {
        let elapsedMS;

        // Allow calling update directly with default currentTime.
        currentTime = currentTime || performance.now();

        // If the difference in time is zero or negative, we ignore most of the work done here.
        // If there is no valid difference, then should be no reason to let anyone know about it.
        // A zero delta, is exactly that, nothing should update.
        //
        // The difference in time can be negative, and no this does not mean time traveling.
        // This can be the result of a race condition between when an animation frame is requested
        // on the current JavaScript engine event loop, and when the ticker's start method is invoked
        // (which invokes the internal _requestIfNeeded method). If a frame is requested before
        // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests,
        // can receive a time argument that can be less than the lastTime value that was set within
        // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems.
        //
        // This check covers this browser engine timing issue, as well as if consumers pass an invalid
        // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves.

        if (currentTime > this.lastTime)
        {
            // Save uncapped elapsedMS for measurement
            elapsedMS = this.elapsedMS = currentTime - this.lastTime;

            // cap the milliseconds elapsed used for deltaTime
            if (elapsedMS > this._maxElapsedMS)
            {
                elapsedMS = this._maxElapsedMS;
            }

            this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed;

            // Invoke listeners added to internal emitter
            this._emitter.emit(TICK, this.deltaTime);
        }
        else
        {
            this.deltaTime = this.elapsedMS = 0;
        }

        this.lastTime = currentTime;
    }

    /**
     * The frames per second at which this ticker is running.
     * The default is approximately 60 in most modern browsers.
     * **Note:** This does not factor in the value of
     * {@link PIXI.ticker.Ticker#speed}, which is specific
     * to scaling {@link PIXI.ticker.Ticker#deltaTime}.
     *
     * @memberof PIXI.ticker.Ticker#
     * @readonly
     */
    get FPS()
    {
        return 1000 / this.elapsedMS;
    }

    /**
     * Manages the maximum amount of milliseconds allowed to
     * elapse between invoking {@link PIXI.ticker.Ticker#update}.
     * This value is used to cap {@link PIXI.ticker.Ticker#deltaTime},
     * but does not effect the measured value of {@link PIXI.ticker.Ticker#FPS}.
     * When setting this property it is clamped to a value between
     * `0` and `PIXI.TARGET_FPMS * 1000`.
     *
     * @memberof PIXI.ticker.Ticker#
     * @default 10
     */
    get minFPS()
    {
        return 1000 / this._maxElapsedMS;
    }
    set minFPS(fps)
    {
        // Clamp: 0 to TARGET_FPMS
        let minFPMS = Math.min(Math.max(0, fps) / 1000, CONST.TARGET_FPMS);
        this._maxElapsedMS = 1 / minFPMS;
    }
}

export default Ticker;<commit_msg>Fixed Ticker's internal _tick method<commit_after>import CONST from '../const';
import EventEmitter from 'eventemitter3';

// Internal event used by composed emitter
const TICK = 'tick';

/**
 * A Ticker class that runs an update loop that other objects listen to.
 * This class is composed around an EventEmitter object to add listeners
 * meant for execution on the next requested animation frame.
 * Animation frames are requested only when necessary,
 * e.g. When the ticker is started and the emitter has listeners.
 *
 * @class
 * @memberof PIXI.ticker
 */
class Ticker
{
    constructor()
    {
        /**
         * Internal emitter used to fire 'tick' event
         * @private
         */
        this._emitter = new EventEmitter();

        /**
         * Internal current frame request ID
         * @private
         */
        this._requestId = null;

        /**
         * Internal value managed by minFPS property setter and getter.
         * This is the maximum allowed milliseconds between updates.
         * @private
         */
        this._maxElapsedMS = 100;

        /**
         * Whether or not this ticker should invoke the method
         * {@link PIXI.ticker.Ticker#start} automatically
         * when a listener is added.
         *
         * @member {boolean}
         * @default false
         */
        this.autoStart = false;

        /**
         * Scalar time value from last frame to this frame.
         * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS}
         * and is scaled with {@link PIXI.ticker.Ticker#speed}.
         * **Note:** The cap may be exceeded by scaling.
         *
         * @member {number}
         * @default 1
         */
        this.deltaTime = 1;

        /**
         * Time elapsed in milliseconds from last frame to this frame.
         * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime}
         * is based, this value is neither capped nor scaled.
         * If the platform supports DOMHighResTimeStamp,
         * this value will have a precision of 1 µs.
         *
         * @member {number}
         * @default 1 / TARGET_FPMS
         */
        this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time

        /**
         * The last time {@link PIXI.ticker.Ticker#update} was invoked.
         * This value is also reset internally outside of invoking
         * update, but only when a new animation frame is requested.
         * If the platform supports DOMHighResTimeStamp,
         * this value will have a precision of 1 µs.
         *
         * @member {number}
         * @default 0
         */
        this.lastTime = 0;

        /**
         * Factor of current {@link PIXI.ticker.Ticker#deltaTime}.
         * @example
         * // Scales ticker.deltaTime to what would be
         * // the equivalent of approximately 120 FPS
         * ticker.speed = 2;
         *
         * @member {number}
         * @default 1
         */
        this.speed = 1;

        /**
         * Whether or not this ticker has been started.
         * `true` if {@link PIXI.ticker.Ticker#start} has been called.
         * `false` if {@link PIXI.ticker.Ticker#stop} has been called.
         * While `false`, this value may change to `true` in the
         * event of {@link PIXI.ticker.Ticker#autoStart} being `true`
         * and a listener is added.
         *
         * @member {boolean}
         * @default false
         */
        this.started = false;

        /**
         * Internal tick method bound to ticker instance.
         * This is because in early 2015, Function.bind
         * is still 60% slower in high performance scenarios.
         * Also separating frame requests from update method
         * so listeners may be called at any time and with
         * any animation API, just invoke ticker.update(time).
         *
         * @private
         */
        this._tick = (time) => {

            this._requestId = null;

            if (this.started)
            {
                // Invoke listeners now
                this.update(time);
                // Listener side effects may have modified ticker state.
                if (this.started && this._requestId === null && this._emitter.listeners(TICK, true))
                {
                    this._requestId = requestAnimationFrame(this._tick);
                }
            }
        };
    }

    /**
     * Conditionally requests a new animation frame.
     * If a frame has not already been requested, and if the internal
     * emitter has listeners, a new frame is requested.
     *
     * @private
     */
    _requestIfNeeded()
    {
        if (this._requestId === null && this._emitter.listeners(TICK, true))
        {
            // ensure callbacks get correct delta
            this.lastTime = performance.now();
            this._requestId = requestAnimationFrame(this._tick);
        }
    }

    /**
     * Conditionally cancels a pending animation frame.
     *
     * @private
     */
    _cancelIfNeeded()
    {
        if (this._requestId !== null)
        {
            cancelAnimationFrame(this._requestId);
            this._requestId = null;
        }
    }

    /**
     * Conditionally requests a new animation frame.
     * If the ticker has been started it checks if a frame has not already
     * been requested, and if the internal emitter has listeners. If these
     * conditions are met, a new frame is requested. If the ticker has not
     * been started, but autoStart is `true`, then the ticker starts now,
     * and continues with the previous conditions to request a new frame.
     *
     * @private
     */
    _startIfPossible()
    {
        if (this.started)
        {
            this._requestIfNeeded();
        }
        else if (this.autoStart)
        {
            this.start();
        }
    }

    /**
     * Calls {@link module:eventemitter3.EventEmitter#on} internally for the
     * internal 'tick' event. It checks if the emitter has listeners,
     * and if so it requests a new animation frame at this point.
     *
     * @param fn {Function} The listener function to be added for updates
     * @param [context] {Function} The listener context
     * @returns {PIXI.ticker.Ticker} This instance of a ticker
     */
    add(fn, context)
    {
        this._emitter.on(TICK, fn, context);

        this._startIfPossible();

        return this;
    }

    /**
     * Calls {@link module:eventemitter3.EventEmitter#once} internally for the
     * internal 'tick' event. It checks if the emitter has listeners,
     * and if so it requests a new animation frame at this point.
     *
     * @param fn {Function} The listener function to be added for one update
     * @param [context] {Function} The listener context
     * @returns {PIXI.ticker.Ticker} This instance of a ticker
     */
    addOnce(fn, context)
    {
        this._emitter.once(TICK, fn, context);

        this._startIfPossible();

        return this;
    }

    /**
     * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event.
     * It checks if the emitter has listeners for 'tick' event.
     * If it does, then it cancels the animation frame.
     *
     * @param [fn] {Function} The listener function to be removed
     * @param [context] {Function} The listener context to be removed
     * @returns {PIXI.ticker.Ticker} This instance of a ticker
     */
    remove(fn, context)
    {
        this._emitter.off(TICK, fn, context);

        if (!this._emitter.listeners(TICK, true))
        {
            this._cancelIfNeeded();
        }

        return this;
    }

    /**
     * Starts the ticker. If the ticker has listeners
     * a new animation frame is requested at this point.
     */
    start()
    {
        if (!this.started)
        {
            this.started = true;
            this._requestIfNeeded();
        }
    }

    /**
     * Stops the ticker. If the ticker has requested
     * an animation frame it is canceled at this point.
     */
    stop()
    {
        if (this.started)
        {
            this.started = false;
            this._cancelIfNeeded();
        }
    }

    /**
     * Triggers an update. An update entails setting the
     * current {@link PIXI.ticker.Ticker#elapsedMS},
     * the current {@link PIXI.ticker.Ticker#deltaTime},
     * invoking all listeners with current deltaTime,
     * and then finally setting {@link PIXI.ticker.Ticker#lastTime}
     * with the value of currentTime that was provided.
     * This method will be called automatically by animation
     * frame callbacks if the ticker instance has been started
     * and listeners are added.
     *
     * @param [currentTime=performance.now()] {number} the current time of execution
     */
    update(currentTime)
    {
        let elapsedMS;

        // Allow calling update directly with default currentTime.
        currentTime = currentTime || performance.now();

        // If the difference in time is zero or negative, we ignore most of the work done here.
        // If there is no valid difference, then should be no reason to let anyone know about it.
        // A zero delta, is exactly that, nothing should update.
        //
        // The difference in time can be negative, and no this does not mean time traveling.
        // This can be the result of a race condition between when an animation frame is requested
        // on the current JavaScript engine event loop, and when the ticker's start method is invoked
        // (which invokes the internal _requestIfNeeded method). If a frame is requested before
        // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests,
        // can receive a time argument that can be less than the lastTime value that was set within
        // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems.
        //
        // This check covers this browser engine timing issue, as well as if consumers pass an invalid
        // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves.

        if (currentTime > this.lastTime)
        {
            // Save uncapped elapsedMS for measurement
            elapsedMS = this.elapsedMS = currentTime - this.lastTime;

            // cap the milliseconds elapsed used for deltaTime
            if (elapsedMS > this._maxElapsedMS)
            {
                elapsedMS = this._maxElapsedMS;
            }

            this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed;

            // Invoke listeners added to internal emitter
            this._emitter.emit(TICK, this.deltaTime);
        }
        else
        {
            this.deltaTime = this.elapsedMS = 0;
        }

        this.lastTime = currentTime;
    }

    /**
     * The frames per second at which this ticker is running.
     * The default is approximately 60 in most modern browsers.
     * **Note:** This does not factor in the value of
     * {@link PIXI.ticker.Ticker#speed}, which is specific
     * to scaling {@link PIXI.ticker.Ticker#deltaTime}.
     *
     * @memberof PIXI.ticker.Ticker#
     * @readonly
     */
    get FPS()
    {
        return 1000 / this.elapsedMS;
    }

    /**
     * Manages the maximum amount of milliseconds allowed to
     * elapse between invoking {@link PIXI.ticker.Ticker#update}.
     * This value is used to cap {@link PIXI.ticker.Ticker#deltaTime},
     * but does not effect the measured value of {@link PIXI.ticker.Ticker#FPS}.
     * When setting this property it is clamped to a value between
     * `0` and `PIXI.TARGET_FPMS * 1000`.
     *
     * @memberof PIXI.ticker.Ticker#
     * @default 10
     */
    get minFPS()
    {
        return 1000 / this._maxElapsedMS;
    }
    set minFPS(fps)
    {
        // Clamp: 0 to TARGET_FPMS
        let minFPMS = Math.min(Math.max(0, fps) / 1000, CONST.TARGET_FPMS);
        this._maxElapsedMS = 1 / minFPMS;
    }
}

export default Ticker;<|endoftext|>


<commit_before>import { window, document } from 'ssr-window';

const Support = (function Support() {
  return {
    touch: (function checkTouch() {
      return !!((window.navigator.maxTouchPoints > 0) || ('ontouchstart' in window) || (window.DocumentTouch && document instanceof window.DocumentTouch));
    }()),

    pointerEvents: !!window.PointerEvent,

    observer: (function checkObserver() {
      return ('MutationObserver' in window || 'WebkitMutationObserver' in window);
    }()),

    passiveListener: (function checkPassiveListener() {
      let supportsPassive = false;
      try {
        const opts = Object.defineProperty({}, 'passive', {
          // eslint-disable-next-line
          get() {
            supportsPassive = true;
          },
        });
        window.addEventListener('testPassiveListener', null, opts);
      } catch (e) {
        // No support
      }
      return supportsPassive;
    }()),

    gestures: (function checkGestures() {
      return 'ongesturestart' in window;
    }()),

    intersectionObserver: (function checkObserver() {
      return ('IntersectionObserver' in window);
    }()),
  };
}());

export default Support;
<commit_msg>Core: Tweak touch events detection<commit_after>import { window, document } from 'ssr-window';

const Support = (function Support() {
  return {
    touch: !!(('ontouchstart' in window) || (window.DocumentTouch && document instanceof window.DocumentTouch)),

    pointerEvents: !!window.PointerEvent && ('maxTouchPoints' in window.navigator) && window.navigator.maxTouchPoints >= 0,

    observer: (function checkObserver() {
      return ('MutationObserver' in window || 'WebkitMutationObserver' in window);
    }()),

    passiveListener: (function checkPassiveListener() {
      let supportsPassive = false;
      try {
        const opts = Object.defineProperty({}, 'passive', {
          // eslint-disable-next-line
          get() {
            supportsPassive = true;
          },
        });
        window.addEventListener('testPassiveListener', null, opts);
      } catch (e) {
        // No support
      }
      return supportsPassive;
    }()),

    gestures: (function checkGestures() {
      return 'ongesturestart' in window;
    }()),

    intersectionObserver: (function checkObserver() {
      return ('IntersectionObserver' in window);
    }()),
  };
}());

export default Support;
<|endoftext|>


<commit_before>(function(args, client){
    boss.lib.print.log("[[;green;] ___  ___  ___  ___ ]", client);
    boss.lib.print.log("[[;green;]| . >| . |\/ __>\/ __>]", client);
    boss.lib.print.log("[[;green;]| . \\| | |\\__ \\\\__ \\]", client);
    boss.lib.print.log("[[;green;]|___\/`___'<___\/<___\/]", client);
    boss.lib.print.log("Browser Operating System Simulator - 0.0.1", client);
    boss.lib.print.log("Soong Research Labs -- Copyright 2017", client);
});<commit_msg>bump version<commit_after>(function(args, client){
    boss.lib.print.log("[[;green;] ___  ___  ___  ___ ]", client);
    boss.lib.print.log("[[;green;]| . >| . |\/ __>\/ __>]", client);
    boss.lib.print.log("[[;green;]| . \\| | |\\__ \\\\__ \\]", client);
    boss.lib.print.log("[[;green;]|___\/`___'<___\/<___\/]", client);
    boss.lib.print.log("Browser Operating System Simulator - 0.0.2", client);
    boss.lib.print.log("Soong Research Labs -- Copyright 2017", client);
});<|endoftext|>


<commit_before>/**
 * @title Set roles to a user
 * @overview Add a Roles field to the user based on some pattern.
 * @gallery true
 * @category access control
 *
 * This rule adds a Roles field to the user based on some pattern.
 *
 */

function (user, context, callback) {

  // Roles should only be set to verified users.
  if (!user.email || !user.email_verified) {
    return callback(null, user, context);
  }

  user.app_metadata = user.app_metadata || {};
  // You can add a Role based on what you want
  // In this case I check domain
  const addRolesToUser = function(user) {
    const endsWith = '@example.com';

    if (user.email && (user.email.substring(user.email.length - endsWith.length, user.email.length) === endsWith)) {
      return ['admin']
    }
    return ['user'];
  };

  const roles = addRolesToUser(user);

  user.app_metadata.roles = roles;
  auth0.users.updateAppMetadata(user.user_id, user.app_metadata)
    .then(function() {
      context.idToken['https://example.com/roles'] = user.app_metadata.roles;
      callback(null, user, context);
    })
    .catch(function (err) {
      callback(err);
    });
}
<commit_msg>adding in semicolon to roles creation rule (#198)<commit_after>/**
 * @title Set roles to a user
 * @overview Add a Roles field to the user based on some pattern.
 * @gallery true
 * @category access control
 *
 * This rule adds a Roles field to the user based on some pattern.
 *
 */

function (user, context, callback) {

  // Roles should only be set to verified users.
  if (!user.email || !user.email_verified) {
    return callback(null, user, context);
  }

  user.app_metadata = user.app_metadata || {};
  // You can add a Role based on what you want
  // In this case I check domain
  const addRolesToUser = function (user) {
    const endsWith = '@example.com';

    if (user.email && (user.email.substring(user.email.length - endsWith.length, user.email.length) === endsWith)) {
      return ['admin'];
    }
    return ['user'];
  };

  const roles = addRolesToUser(user);

  user.app_metadata.roles = roles;
  auth0.users.updateAppMetadata(user.user_id, user.app_metadata)
    .then(function () {
      context.idToken['https://example.com/roles'] = user.app_metadata.roles;
      callback(null, user, context);
    })
    .catch(function (err) {
      callback(err);
    });
}
<|endoftext|>


<commit_before>/**
 * Creates accessors for different views to use to get at job data
 */

import * as d3 from "d3";

/**
 * Collection of props that can be accessed from jobs
 * .access(job) -> return value of prop
 * .label -> text display of prop
 * .calcDomain(scheduler) -> return domain of value [min, max]
 */
export const props = {
    [".init.ioFreq"]: {
        access(d) {
            return d.init.ioFreq;
        },
        label: "IO Frequency",
        tooltip: "A job's IO Frequency is how many cycles a job will run on the CPU before being interupted and moved to IO. Interactive jobs have high IO Frequency, while CPU bound jobs will have low IO frequency",
        legend: ["Low IO Freq.", "High IO Freq."],
        calcDomain(scheduler) {
            return [d3.max(scheduler.allJobs, d => d.init.ioFreq), 0]
        }
    },
    [".perf.responseTime"]: {
        access(d) {
            return d.perf.responseTime;
        },
        label: "Response Time",
        legend: ["Quick Response", "Slow Response"],
        tooltip: "A job's Response Time is the measured number of CPU cycles from when a job first entered the system to when it was first run on the CPU",
        calcDomain(scheduler) {
            return [0, d3.max(scheduler.allJobs, d => d.perf.responseTime)]
        }
    },
    [".perf.turnaroundTime"]: {
        access(d) {
            return d.perf.turnaroundTime;
        },
        label: "Turnaround Time",
        legend: ["Short Turnaround", "Long Turnaround"],
        tooltip: "A job's Turnaround Time time is the measured number of CPU cycles from when the job entered the system to when it finished and left the system",
        calcDomain(scheduler) {
            return [0, d3.max(scheduler.allJobs, d => d.perf.turnaroundTime)]
        }
    },
    [".running.serviceTime"]: {
        access(d) {
            return d.running.serviceTime;
        },
        label: "Service Time",
        tooltip: "Service Time is amount of cycles a job has been run on the CPU.",
        legend: ["Little Service Time", "Lots of Service Time"],
        calcDomain(scheduler) {
            return [0, d3.max(scheduler.allJobs, d => d.running.serviceTime)]
        }
    },
    [".running.totalWaitingTime"]: {
        access(d) {
            return d.running.totalWaitingTime;
        },
        label: "Total Waiting Time",
        tooltip: "Total Waiting Time is the amount of CPU cycles a job has spent waiting in a queue",
        legend: ["Little Waiting", "Lots of Waiting"],
        calcDomain(scheduler) {
            return [0, d3.max(scheduler.allJobs, d => d.running.totalWaitingTime)]
        }
    },
    [".running.avgPriority"]: {
        access(d) {
            return d.running.avgPriority;
        },
        label: "Average Priority",
        tooltip: "Average Priority represents the queue a job has spent the most time in",
        legend: ["Usually Low Priority", "Usually High Priority"],
        calcDomain(scheduler) {
            return [0, d3.max(scheduler.allJobs, d => d.running.avgPriority)]
        }
    },
    [".init.runTime"]: {
        access(d) {
            return d.init.runTime;
        },
        label: "Run Time",
        tooltip: "Run Time is the number of cycles a job needs to be run on the CPU to be completed",
        legend: ["Short Job", "Long Job"],
        calcDomain(scheduler) {
            return [0, d3.max(scheduler.allJobs, d => d.init.runTime)]
        }
    },
    [".init.createTime"]: {
        access(d) {
            return d.init.createTime;
        },
        label: "Create Time",
        legend: ["Early Job", "Late Job"],
        tooltip: "Create time stores the CPU cycle number that a job entered (or will enter) the simulation at",
        calcDomain(scheduler) {
            return [0, d3.max(scheduler.allJobs, d => d.init.createTime)]
        }
    },
    [".init.ioLength"]: {
        access(d) {
            return d.init.ioLength;
        },
        label: "Job IO Length",
        legend: ["Short IO", "Long IO"],
        tooltip: "IO length is a fixed number of CPU cycles that will pass after a job has entered IO before the job leaves IO",
        calcDomain(scheduler) {
            return [0, d3.max(scheduler.allJobs, d => d.init.ioLength)]
        }
    },
    ["tq"]: {
        label: "Time Quantum",
        legend: ["Barely Depleted", "Almost Depleted"],
    },
    ["timeQuantum"]: {
        access(d) {
            return `${d.running.quantumLeft} / ${d.running.quantumFull}`;
        },
        label: "Time Quantum",
        tooltip: "Time Quantum the number of cycles a job may run on the CPU before being kicked off and deprioritized"
    },
    ["none"]: {
        access(d) {
            return 0;
        },
        label: "None",
        tooltip: "This is a blank attribute... Why are you reading this?",
        legend: ["No Encoding", ""],
        calcDomain(scheduler) {
            return [0, 0];
        }
    },
    [".running.priority"]: {
        access(d) {
            return d.running.priority;
        },
        label: "Priority",
        tooltip: "The MLFQ algorithm decides which job to run based on priority. The highest priority waiting job is always selected to be run on the CPU",
        legend: ["Low Priority!!!&", "High Priority"],
        calcDomain(scheduler) {
            return [0, scheduler.numQueues]
        }
    },
    ["none&priority=greyscale"]: {
        label: "Priority (Greyscale)",
        legend: ["High Priority!!!!", "Low Priority"],
    },
    ["tq&priority=greyscale"]: {
        label: "Time Quantum & Priority (Greyscale)",
        legend: ["Barely Depleted", "Almost Depleted"],
    },
    ["none&priority=rainbow"]: {
        label: "Priority (Coloured)",
        legend: ["Low Priority*(&^*", "High Priority"],
    },
    ["tq&priority=rainbow"]: {
        label: "Time Quantum & Priority (Coloured)",
        legend: ["Barely Depleted", "Almost Depleted"],
    }
};

let i = 0;
const colours = d3.scaleOrdinal(d3.schemeCategory10)
    .domain(d3.range(Object.keys(props).length));
for (const prop in props) {
    props[prop].colour = d3.color(colours(i++)).brighter(.5).rgb();
}
/**
 * Create an accessor factory for an axis
 * @param axis X, Y, Z, etc
 * gets appended to method names ie getX
 */
function propSetter(axis) {
    return function (propName) {
        if (!(propName in props)) propName = "none";
        const { access, label, calcDomain, colour, legend, tooltip } = props[propName];
        this.accessors["get" + axis] = access;
        this.accessors["getDomain" + axis] = calcDomain;
        this.accessors["label" + axis] = label;
        this.accessors["colour" + axis] = colour;
        this.accessors["legend" + axis] = legend;
        this.accessors["tooltip" + axis] = tooltip;
        return this;
    };
}

export function getLabel(key) {
    return props[key].label;
}

/**
 * Create a factory for creating all needed acessor factories
 */
export function accessorFactoryFactory() {
    return {
        accessors: {},
        x: propSetter("X"),
        y: propSetter("Y"),
        z: propSetter("Z"),
        w: propSetter("W")
    }
}

/**
 * Yields every combination for values in an array
 * @example ["a", "b", "c"] -> [["a", "b"], ["a", "c"], ["b", "c"]]
 * @param props to combine
 */
function* getCombinations2(props) {
    for (let i = 0; i < props.length; i++) {
        for (let j = 0; j <= i; j++) {
            yield [props[j], props[i]];
        }
    }
}

/**
 * Yield all accessors needed for a matrix
 * Used for the SPLOM
 */
function* accessorMatrix2d(props) {
    for (const [propX, propY] of getCombinations2(props)) {
        yield accessorFactoryFactory()
            .x(propX)
            .y(propY)
            .accessors;
    }
}

/**
 * Get accessors
 */
function *getAccessors(props) {
    for (const prop of props) {
        yield accessorFactoryFactory()
            .y(prop)
            .accessors;
    }
}

export function accessorMatrix(props) {
    return [...accessorMatrix2d(props)];
}

export function accessorParallelAxis(props){
    return [...getAccessors(props)];

}<commit_msg>Fix data colours and change time quantum in details panel<commit_after>/**
 * Creates accessors for different views to use to get at job data
 */

import * as d3 from "d3";

const colours = d3.scaleOrdinal(d3.schemeCategory10)
    .domain(d3.range(3));

/**
 * Collection of props that can be accessed from jobs
 * .access(job) -> return value of prop
 * .label -> text display of prop
 * .calcDomain(scheduler) -> return domain of value [min, max]
 */
export const props = {
    [".init.ioFreq"]: {
        access(d) {
            return d.init.ioFreq;
        },
        label: "IO Frequency",
        tooltip: "A job's IO Frequency is how many cycles a job will run on the CPU before being interupted and moved to IO. Interactive jobs have high IO Frequency, while CPU bound jobs will have low IO frequency",
        legend: ["Low IO Freq.", "High IO Freq."],
        calcDomain(scheduler) {
            return [d3.max(scheduler.allJobs, d => d.init.ioFreq), 0]
        },
        colour: colours(0)
    },
    [".perf.responseTime"]: {
        access(d) {
            return d.perf.responseTime;
        },
        label: "Response Time",
        legend: ["Quick Response", "Slow Response"],
        tooltip: "A job's Response Time is the measured number of CPU cycles from when a job first entered the system to when it was first run on the CPU",
        calcDomain(scheduler) {
            return [0, d3.max(scheduler.allJobs, d => d.perf.responseTime)]
        }
    },
    [".perf.turnaroundTime"]: {
        access(d) {
            return d.perf.turnaroundTime;
        },
        label: "Turnaround Time",
        legend: ["Short Turnaround", "Long Turnaround"],
        tooltip: "A job's Turnaround Time time is the measured number of CPU cycles from when the job entered the system to when it finished and left the system",
        calcDomain(scheduler) {
            return [0, d3.max(scheduler.allJobs, d => d.perf.turnaroundTime)]
        }
    },
    [".running.serviceTime"]: {
        access(d) {
            return d.running.serviceTime;
        },
        label: "Service Time",
        tooltip: "Service Time is amount of cycles a job has been run on the CPU.",
        legend: ["Little Service Time", "Lots of Service Time"],
        calcDomain(scheduler) {
            return [0, d3.max(scheduler.allJobs, d => d.running.serviceTime)]
        }
    },
    [".running.totalWaitingTime"]: {
        access(d) {
            return d.running.totalWaitingTime;
        },
        label: "Total Waiting Time",
        tooltip: "Total Waiting Time is the amount of CPU cycles a job has spent waiting in a queue",
        legend: ["Little Waiting", "Lots of Waiting"],
        calcDomain(scheduler) {
            return [0, d3.max(scheduler.allJobs, d => d.running.totalWaitingTime)]
        }
    },
    [".running.avgPriority"]: {
        access(d) {
            return d.running.avgPriority;
        },
        label: "Average Priority",
        tooltip: "Average Priority represents the queue a job has spent the most time in",
        legend: ["Usually Low Priority", "Usually High Priority"],
        calcDomain(scheduler) {
            return [0, d3.max(scheduler.allJobs, d => d.running.avgPriority)]
        }
    },
    [".init.runTime"]: {
        access(d) {
            return d.init.runTime;
        },
        label: "Run Time",
        tooltip: "Run Time is the number of cycles a job needs to be run on the CPU to be completed",
        legend: ["Short Job", "Long Job"],
        calcDomain(scheduler) {
            return [0, d3.max(scheduler.allJobs, d => d.init.runTime)]
        }
    },
    [".init.createTime"]: {
        access(d) {
            return d.init.createTime;
        },
        label: "Create Time",
        legend: ["Early Job", "Late Job"],
        tooltip: "Create time stores the CPU cycle number that a job entered (or will enter) the simulation at",
        calcDomain(scheduler) {
            return [0, d3.max(scheduler.allJobs, d => d.init.createTime)]
        }
    },
    [".init.ioLength"]: {
        access(d) {
            return d.init.ioLength;
        },
        label: "Job IO Length",
        legend: ["Short IO", "Long IO"],
        tooltip: "IO length is a fixed number of CPU cycles that will pass after a job has entered IO before the job leaves IO",
        calcDomain(scheduler) {
            return [0, d3.max(scheduler.allJobs, d => d.init.ioLength)]
        },
        colour: colours(1)
    },
    ["tq"]: {
        label: "Time Quantum",
        legend: ["Barely Depleted", "Almost Depleted"],
        colour: colours(2)
    },
    ["timeQuantum"]: {
        access(d) {
            return `${d.running.quantumFull-d.running.quantumLeft} / ${d.running.quantumFull}`;
        },
        label: "Time Quantum",
        tooltip: "Time Quantum the number of cycles a job may run on the CPU before being kicked off and deprioritized"
    },
    ["none"]: {
        access(d) {
            return 0;
        },
        label: "None",
        tooltip: "This is a blank attribute... Why are you reading this?",
        legend: ["No Encoding", ""],
        calcDomain(scheduler) {
            return [0, 0];
        }
    },
    [".running.priority"]: {
        access(d) {
            return d.running.priority;
        },
        label: "Priority",
        tooltip: "The MLFQ algorithm decides which job to run based on priority. The highest priority waiting job is always selected to be run on the CPU",
        legend: ["Low Priority!!!&", "High Priority"],
        calcDomain(scheduler) {
            return [0, scheduler.numQueues]
        }
    },
    ["none&priority=greyscale"]: {
        label: "Priority (Greyscale)",
        legend: ["High Priority!!!!", "Low Priority"],
    },
    ["tq&priority=greyscale"]: {
        label: "Time Quantum & Priority (Greyscale)",
        legend: ["Barely Depleted", "Almost Depleted"],
    },
    ["none&priority=rainbow"]: {
        label: "Priority (Coloured)",
        legend: ["Low Priority*(&^*", "High Priority"],
    },
    ["tq&priority=rainbow"]: {
        label: "Time Quantum & Priority (Coloured)",
        legend: ["Barely Depleted", "Almost Depleted"],
    }
};

/**
 * Create an accessor factory for an axis
 * @param axis X, Y, Z, etc
 * gets appended to method names ie getX
 */
function propSetter(axis) {
    return function (propName) {
        if (!(propName in props)) propName = "none";
        const { access, label, calcDomain, colour, legend, tooltip } = props[propName];
        this.accessors["get" + axis] = access;
        this.accessors["getDomain" + axis] = calcDomain;
        this.accessors["label" + axis] = label;
        this.accessors["colour" + axis] = colour;
        this.accessors["legend" + axis] = legend;
        this.accessors["tooltip" + axis] = tooltip;
        return this;
    };
}

export function getLabel(key) {
    return props[key].label;
}

/**
 * Create a factory for creating all needed acessor factories
 */
export function accessorFactoryFactory() {
    return {
        accessors: {},
        x: propSetter("X"),
        y: propSetter("Y"),
        z: propSetter("Z"),
        w: propSetter("W")
    }
}

/**
 * Yields every combination for values in an array
 * @example ["a", "b", "c"] -> [["a", "b"], ["a", "c"], ["b", "c"]]
 * @param props to combine
 */
function* getCombinations2(props) {
    for (let i = 0; i < props.length; i++) {
        for (let j = 0; j <= i; j++) {
            yield [props[j], props[i]];
        }
    }
}

/**
 * Yield all accessors needed for a matrix
 * Used for the SPLOM
 */
function* accessorMatrix2d(props) {
    for (const [propX, propY] of getCombinations2(props)) {
        yield accessorFactoryFactory()
            .x(propX)
            .y(propY)
            .accessors;
    }
}

/**
 * Get accessors
 */
function *getAccessors(props) {
    for (const prop of props) {
        yield accessorFactoryFactory()
            .y(prop)
            .accessors;
    }
}

export function accessorMatrix(props) {
    return [...accessorMatrix2d(props)];
}

export function accessorParallelAxis(props){
    return [...getAccessors(props)];

}<|endoftext|>