<commit_before>(function () {

/**
 * UI controller for interactive plots
 * @Class
 * @Param {object} config
 * @Param {object} config.buttons    nested arrays of grouped buttons to initialize
 * @Param {object} config.container  container div to append modebar
 * @Param {object} config.Plotly     main plotly namespace module
 * @Param {object} config.graphInfo  primary plot object containing data and layout
 */
function ModeBar (config) {

    if (!(this instanceof ModeBar)) return new ModeBar();

    var _this = this;

    this.Plotly = config.Plotly;
    this.graphInfo = config.graphInfo;
    this.element = document.createElement('div');
    this.element.className = 'modebar';
    this.buttons = config.buttons;
    this.buttonElements = [];

    this.buttons.forEach( function (buttonGroup) {
        var group = _this.createGroup();

        buttonGroup.forEach( function (buttonName) {
            var buttonConfig = _this.config[buttonName];

            if (!buttonConfig) {
                throw new Error(buttonName + ' not specfied in modebar configuration');
            }

            var button = _this.createButton(buttonConfig);

            _this.buttonElements.push(button);
            group.appendChild(button);
        });

        _this.element.appendChild(group);
    });

    config.container.appendChild(this.element);

    this.updateActiveButton();

}

window.ModeBar = ModeBar;

/**
 * Empty div for containing a group of buttons
 * @Return {HTMLelement}
 */
ModeBar.prototype.createGroup = function () {
    var group = document.createElement('div');
    group.className = 'btn-group float--left';

    return group;
};

/**
 * Create a new button div and set constant and configurable attributes
 * @Param {object} config
 * @Param {string} config.attr
 * @Param {string} config.val
 * @Param {string} config.title
 * @Param {function} config.click
 * @Return {HTMLelement}
 */
ModeBar.prototype.createButton = function (config) {
    var button = document.createElement('button');
    var icon = document.createElement('i');

    button.setAttribute('rel', 'tooltip');
    button.className = 'btn btn--icon btn--plot ploticon';

    button.setAttribute('data-attr', config.attr);
    button.setAttribute('data-val', config.val);
    button.setAttribute('title', config.title);
    button.addEventListener('click', config.click.bind(this));

    icon.className = config.icon;
    button.appendChild(icon);

    return button;
};

/**
 * Updates active button with attribute specified in layout
 * @Param {object} graphInfo plot object containing data and layout
 * @Return {HTMLelement}
 */
ModeBar.prototype.updateActiveButton = function () {
    var graphInfo = this.graphInfo;
    this.buttonElements.forEach( function (button) {
        var thisval = button.getAttribute('data-val') || true,
            dataAttr = button.getAttribute('data-attr'),
            curval = graphInfo._fullLayout[dataAttr];

        button.classList.toggle('active', curval===thisval);
    });
};

/**
 * Check if modebar is configured as button configuration argument
 * @Param {object} buttons 2d array of grouped button names
 * @Return {boolean}
 */
ModeBar.prototype.hasButtons = function (buttons) {
    var currentButtons = this.buttons;

    if (buttons.length !== currentButtons.length) return false;

    for (var i = 0; i < buttons.length; ++i) {
        if (buttons[i].length !== currentButtons[i].length) return false;
        for (var j = 0; j < buttons[i].length; j++) {
            if (buttons[i][j] !== currentButtons[i][j]) return false;
        }
    }

    return true;
};

/**
 * Apply D3 cartesian mode attributes to layout to update hover functionality
 * @Param {object} ev event object
 */
function handleCartesian (ev) {
    var button = ev.currentTarget,
        astr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        aobj = {},
        graphInfo = this.graphInfo,
        layout = this.graphInfo._fullLayout,
        Plotly = this.Plotly,
        _this = this;

    aobj[astr] = val;

    if(astr === 'zoom') {
        var xr = layout.xaxis.range,
            yr = layout.yaxis.range,
            mag = (val==='in') ? 0.5 : 2,
            r0 = (1+mag)/2, r1 = (1-mag)/2;
        aobj = {
            'xaxis.range[0]': r0*xr[0] + r1*xr[1],
            'xaxis.range[1]': r0*xr[1] + r1*xr[0],
            'yaxis.range[0]': r0*yr[0] + r1*yr[1],
            'yaxis.range[1]': r0*yr[1] + r1*yr[0]
        };
    }

    Plotly.relayout(graphInfo, aobj).then( function() {
        _this.updateActiveButton();
        if(astr === 'dragmode') {
            Plotly.Fx.setCursor(
                layout._paper.select('.nsewdrag'),
                {pan:'move', zoom:'crosshair'}[val]
            );
        }
    });
}

/**
 * Toggle the data hover mode
 * @Param {object} ev event object
 * @Return {HTMLelement}
 */
function handleHover3d (ev) {
    var _this = this,
        button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        layoutUpdate = {},
        graphInfo = this.graphInfo,
        layout = graphInfo._fullLayout,
        scenes = Object.keys(layout).filter(function(k){
            return k.match(/^scene[0-9]*$/);
        }).map( function (sceneKey) {
            return layout[sceneKey];
        });

    layoutUpdate[attr] = val;

    scenes.forEach( function (scene) {
        scene._webgl.spikeEnable = !scene._webgl.spikeEnable;
    });

    this.Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
        scenes[0]._container.focus();
    });
}

/**
 * Reconfigure keyboard bindings for webgl3D camera control
 * @Param {object} ev event object
 * @Return {HTMLelement}
 */
function handle3dCamera (ev) {
    var _this = this,
        button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        layoutUpdate = {},
        graphInfo = this.graphInfo,
        layout = graphInfo._fullLayout,
        scenes = Object.keys(layout).filter(function(k){
            return k.match(/^scene[0-9]*$/);
        }).map( function (sceneKey) {
            return layout[sceneKey];
        });

    if (attr === 'reset') {
        // Reset camera position to initial value, go in rotate mode
        val = 'rotate';
        var cameraPositionInitial = layout.scene._cameraPositionInitial;
        layoutUpdate = {
            'dragmode': 'rotate',
            'scene.cameraposition': $.extend(true, [], cameraPositionInitial)
        };
    } else {
        layoutUpdate[attr] = val;
    }

    scenes.forEach( function (scene) {
        if ('_webgl' in scene && 'camera' in scene._webgl) {
            scene._webgl.camera.keyBindingMode = val;
        }
    });

    this.Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
        scenes[0]._container.focus();
    });
}

/**
 *
 * @Property config specification hash of button parameters
 */
ModeBar.prototype.config = {
    zoom2d: {
        title: 'Zoom',
        attr: 'dragmode',
        val: 'zoom',
        icon: 'ploticon-zoombox',
        click: handleCartesian
    },
    pan2d: {
        title: 'Pan',
        attr: 'dragmode',
        val: 'pan',
        icon: 'ploticon-pan',
        click: handleCartesian
    },
    zoomIn2d: {
        title: 'Zoom in',
        attr: 'zoom',
        val: 'in',
        icon: 'ploticon-zoom_plus',
        click: handleCartesian
    },
    zoomOut2d: {
        title: 'Zoom out',
        attr: 'zoom',
        val: 'out',
        icon: 'ploticon-zoom_minus',
        click: handleCartesian
    },
    autoScale2d: {
        title: 'Autoscale',
        attr: 'allaxes.autorange',
        val: '',
        icon: 'ploticon-autoscale',
        click: handleCartesian
    },
    hoverClosest2d: {
        title: 'Show closest data on hover',
        attr: 'hovermode',
        val: 'closest',
        icon: 'ploticon-tooltip_basic',
        click: handleCartesian
    },
    hoverCompare2d: {
        title: 'Compare data on hover',
        attr: 'hovermode',
        val: 'x',
        icon: 'ploticon-tooltip_compare',
        click: handleCartesian
    },
    resetCamera3d: {
        title: 'Reset camera',
        attr: 'reset',
        val: '',
        icon: 'icon-home',
        click: handle3dCamera
    },
    zoom3d: {
        title: 'Zoom',
        attr: 'dragmode',
        val: 'zoom',
        icon: 'ploticon-zoombox',
        click: handle3dCamera
    },
    pan3d: {
        title: 'Pan',
        attr: 'dragmode',
        val: 'pan',
        icon: 'ploticon-pan',
        click: handle3dCamera
    },
    rotate3d: {
        title: 'Rotate',
        attr: 'dragmode',
        val: 'rotate',
        icon: 'icon-undo',
        click: handle3dCamera
    },
    closest3d: {
        title: 'Toggle show closest data on hover',
        attr: 'hovermode',
        val: 'closest',
        icon: 'ploticon-tooltip_basic',
        click: handleHover3d
    }

};

})();
<commit_msg>update modebar with new surface and scatter API<commit_after>(function () {

/**
 * UI controller for interactive plots
 * @Class
 * @Param {object} config
 * @Param {object} config.buttons    nested arrays of grouped buttons to initialize
 * @Param {object} config.container  container div to append modebar
 * @Param {object} config.Plotly     main plotly namespace module
 * @Param {object} config.graphInfo  primary plot object containing data and layout
 */
function ModeBar (config) {

    if (!(this instanceof ModeBar)) return new ModeBar();

    var _this = this;

    this.Plotly = config.Plotly;
    this.graphInfo = config.graphInfo;
    this.element = document.createElement('div');
    this.element.className = 'modebar';
    this.buttons = config.buttons;
    this.buttonElements = [];

    this.buttons.forEach( function (buttonGroup) {
        var group = _this.createGroup();

        buttonGroup.forEach( function (buttonName) {
            var buttonConfig = _this.config[buttonName];

            if (!buttonConfig) {
                throw new Error(buttonName + ' not specfied in modebar configuration');
            }

            var button = _this.createButton(buttonConfig);

            _this.buttonElements.push(button);
            group.appendChild(button);
        });

        _this.element.appendChild(group);
    });

    config.container.appendChild(this.element);

    this.updateActiveButton();

}

window.ModeBar = ModeBar;

/**
 * Empty div for containing a group of buttons
 * @Return {HTMLelement}
 */
ModeBar.prototype.createGroup = function () {
    var group = document.createElement('div');
    group.className = 'btn-group float--left';

    return group;
};

/**
 * Create a new button div and set constant and configurable attributes
 * @Param {object} config
 * @Param {string} config.attr
 * @Param {string} config.val
 * @Param {string} config.title
 * @Param {function} config.click
 * @Return {HTMLelement}
 */
ModeBar.prototype.createButton = function (config) {
    var button = document.createElement('button');
    var icon = document.createElement('i');

    button.setAttribute('rel', 'tooltip');
    button.className = 'btn btn--icon btn--plot ploticon';

    button.setAttribute('data-attr', config.attr);
    button.setAttribute('data-val', config.val);
    button.setAttribute('title', config.title);
    button.addEventListener('click', config.click.bind(this));

    icon.className = config.icon;
    button.appendChild(icon);

    return button;
};

/**
 * Updates active button with attribute specified in layout
 * @Param {object} graphInfo plot object containing data and layout
 * @Return {HTMLelement}
 */
ModeBar.prototype.updateActiveButton = function () {
    var graphInfo = this.graphInfo;
    this.buttonElements.forEach( function (button) {
        var thisval = button.getAttribute('data-val') || true,
            dataAttr = button.getAttribute('data-attr'),
            curval = graphInfo._fullLayout[dataAttr];

        button.classList.toggle('active', curval===thisval);
    });
};

/**
 * Check if modebar is configured as button configuration argument
 * @Param {object} buttons 2d array of grouped button names
 * @Return {boolean}
 */
ModeBar.prototype.hasButtons = function (buttons) {
    var currentButtons = this.buttons;

    if (buttons.length !== currentButtons.length) return false;

    for (var i = 0; i < buttons.length; ++i) {
        if (buttons[i].length !== currentButtons[i].length) return false;
        for (var j = 0; j < buttons[i].length; j++) {
            if (buttons[i][j] !== currentButtons[i][j]) return false;
        }
    }

    return true;
};

/**
 * Apply D3 cartesian mode attributes to layout to update hover functionality
 * @Param {object} ev event object
 */
function handleCartesian (ev) {
    var button = ev.currentTarget,
        astr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        aobj = {},
        graphInfo = this.graphInfo,
        layout = this.graphInfo._fullLayout,
        Plotly = this.Plotly,
        _this = this;

    aobj[astr] = val;

    if(astr === 'zoom') {
        var xr = layout.xaxis.range,
            yr = layout.yaxis.range,
            mag = (val==='in') ? 0.5 : 2,
            r0 = (1+mag)/2, r1 = (1-mag)/2;
        aobj = {
            'xaxis.range[0]': r0*xr[0] + r1*xr[1],
            'xaxis.range[1]': r0*xr[1] + r1*xr[0],
            'yaxis.range[0]': r0*yr[0] + r1*yr[1],
            'yaxis.range[1]': r0*yr[1] + r1*yr[0]
        };
    }

    Plotly.relayout(graphInfo, aobj).then( function() {
        _this.updateActiveButton();
        if(astr === 'dragmode') {
            Plotly.Fx.setCursor(
                layout._paper.select('.nsewdrag'),
                {pan:'move', zoom:'crosshair'}[val]
            );
        }
    });
}

/**
 * Toggle the data hover mode
 * @Param {object} ev event object
 * @Return {HTMLelement}
 */
function handleHover3d (ev) {
    var _this = this,
        button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        layoutUpdate = {},
        graphInfo = this.graphInfo,
        layout = graphInfo._fullLayout,
        scenes = Object.keys(layout).filter(function(k){
            return k.match(/^scene[0-9]*$/);
        }).map( function (sceneKey) {
            return layout[sceneKey];
        });

    layoutUpdate[attr] = val;

    scenes.forEach( function (sceneLayout) {
        sceneLayout._scene.spikeEnable = !sceneLayout._scene.spikeEnable;
    });

    this.Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
        scenes[0]._container.focus();
    });
}

/**
 * Reconfigure keyboard bindings for webgl3D camera control
 * @Param {object} ev event object
 * @Return {HTMLelement}
 */
function handle3dCamera (ev) {
    var _this = this,
        button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        layoutUpdate = {},
        graphInfo = this.graphInfo,
        layout = graphInfo._fullLayout,
        scenes = Object.keys(layout).filter(function(k){
            return k.match(/^scene[0-9]*$/);
        }).map( function (sceneKey) {
            return layout[sceneKey];
        });

    if (attr === 'reset') {
        // Reset camera position to initial value, go in rotate mode
        val = 'rotate';
        var cameraPositionInitial = layout.scene._cameraPositionInitial;
        layoutUpdate = {
            'dragmode': 'rotate',
            'scene.cameraposition': $.extend(true, [], cameraPositionInitial)
        };
    } else {
        layoutUpdate[attr] = val;
    }

    scenes.forEach( function (sceneLayout) {
        if ('_scene' in sceneLayout && 'camera' in sceneLayout._scene) {
            sceneLayout._scene.camera.keyBindingMode = val;
        }
    });

    this.Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
        scenes[0]._container.focus();
    });
}

/**
 *
 * @Property config specification hash of button parameters
 */
ModeBar.prototype.config = {
    zoom2d: {
        title: 'Zoom',
        attr: 'dragmode',
        val: 'zoom',
        icon: 'ploticon-zoombox',
        click: handleCartesian
    },
    pan2d: {
        title: 'Pan',
        attr: 'dragmode',
        val: 'pan',
        icon: 'ploticon-pan',
        click: handleCartesian
    },
    zoomIn2d: {
        title: 'Zoom in',
        attr: 'zoom',
        val: 'in',
        icon: 'ploticon-zoom_plus',
        click: handleCartesian
    },
    zoomOut2d: {
        title: 'Zoom out',
        attr: 'zoom',
        val: 'out',
        icon: 'ploticon-zoom_minus',
        click: handleCartesian
    },
    autoScale2d: {
        title: 'Autoscale',
        attr: 'allaxes.autorange',
        val: '',
        icon: 'ploticon-autoscale',
        click: handleCartesian
    },
    hoverClosest2d: {
        title: 'Show closest data on hover',
        attr: 'hovermode',
        val: 'closest',
        icon: 'ploticon-tooltip_basic',
        click: handleCartesian
    },
    hoverCompare2d: {
        title: 'Compare data on hover',
        attr: 'hovermode',
        val: 'x',
        icon: 'ploticon-tooltip_compare',
        click: handleCartesian
    },
    resetCamera3d: {
        title: 'Reset camera',
        attr: 'reset',
        val: '',
        icon: 'icon-home',
        click: handle3dCamera
    },
    zoom3d: {
        title: 'Zoom',
        attr: 'dragmode',
        val: 'zoom',
        icon: 'ploticon-zoombox',
        click: handle3dCamera
    },
    pan3d: {
        title: 'Pan',
        attr: 'dragmode',
        val: 'pan',
        icon: 'ploticon-pan',
        click: handle3dCamera
    },
    rotate3d: {
        title: 'Rotate',
        attr: 'dragmode',
        val: 'rotate',
        icon: 'icon-undo',
        click: handle3dCamera
    },
    closest3d: {
        title: 'Toggle show closest data on hover',
        attr: 'hovermode',
        val: 'closest',
        icon: 'ploticon-tooltip_basic',
        click: handleHover3d
    }

};

})();
<|endoftext|>


<commit_before>(function () {

/**
 * UI controller for interactive plots
 * @Class
 * @Param {object} config
 * @Param {object} config.buttons    nested arrays of grouped buttons to initialize
 * @Param {object} config.container  container div to append modebar
 * @Param {object} config.Plotly     main plotly namespace module
 * @Param {object} config.graphInfo  primary plot object containing data and layout
 */
function ModeBar (config) {

    if (!(this instanceof ModeBar)) return new ModeBar();

    var _this = this;

    this.Plotly = config.Plotly;
    this.graphInfo = config.graphInfo;
    this.element = document.createElement('div');
    this.element.className = 'modebar';
    this.buttons = config.buttons;
    this.buttonElements = [];

    this.buttons.forEach( function (buttonGroup) {
        var group = _this.createGroup();

        buttonGroup.forEach( function (buttonName) {
            var buttonConfig = _this.config[buttonName];

            if (!buttonConfig) {
                throw new Error(buttonName + ' not specfied in modebar configuration');
            }

            var button = _this.createButton(buttonConfig);

            _this.buttonElements.push(button);
            group.appendChild(button);
        });

        _this.element.appendChild(group);
    });

    config.container.appendChild(this.element);

    this.updateActiveButton();

}

window.ModeBar = ModeBar;

/**
 * Empty div for containing a group of buttons
 * @Return {HTMLelement}
 */
ModeBar.prototype.createGroup = function () {
    var group = document.createElement('div');
    group.className = 'btn-group float--left';

    return group;
};

/**
 * Create a new button div and set constant and configurable attributes
 * @Param {object} config
 * @Param {string} config.attr
 * @Param {string} config.val
 * @Param {string} config.title
 * @Param {function} config.click
 * @Return {HTMLelement}
 */
ModeBar.prototype.createButton = function (config) {
    var button = document.createElement('button');
    var icon = document.createElement('i');

    button.setAttribute('rel', 'tooltip');
    button.className = 'btn btn--icon btn--plot ploticon';

    button.setAttribute('data-attr', config.attr);
    button.setAttribute('data-val', config.val);
    button.setAttribute('title', config.title);
    button.addEventListener('click', config.click.bind(this));

    icon.className = config.icon;
    button.appendChild(icon);

    return button;
};

/**
 * Updates active button with attribute specified in layout
 * @Param {object} graphInfo plot object containing data and layout
 * @Return {HTMLelement}
 */
ModeBar.prototype.updateActiveButton = function () {
    var graphInfo = this.graphInfo;
    this.buttonElements.forEach( function (button) {
        var thisval = button.getAttribute('data-val') || true,
            dataAttr = button.getAttribute('data-attr'),
            curval = graphInfo._fullLayout[dataAttr];

        button.classList.toggle('active', curval===thisval);
    });
};

/**
 * Check if modebar is configured as button configuration argument
 * @Param {object} buttons 2d array of grouped button names
 * @Return {boolean}
 */
ModeBar.prototype.hasButtons = function (buttons) {
    var currentButtons = this.buttons;

    if (buttons.length !== currentButtons.length) return false;

    for (var i = 0; i < buttons.length; ++i) {
        if (buttons[i].length !== currentButtons[i].length) return false;
        for (var j = 0; j < buttons[i].length; j++) {
            if (buttons[i][j] !== currentButtons[i][j]) return false;
        }
    }

    return true;
};

/**
 * Apply D3 cartesian mode attributes to layout to update hover functionality
 * @Param {object} ev event object
 */
function handleCartesian (ev) {
    var button = ev.currentTarget,
        astr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        aobj = {},
        graphInfo = this.graphInfo,
        layout = this.graphInfo._fullLayout,
        Plotly = this.Plotly,
        _this = this;

    aobj[astr] = val;

    if(astr === 'zoom') {
        var xr = layout.xaxis.range,
            yr = layout.yaxis.range,
            mag = (val==='in') ? 0.5 : 2,
            r0 = (1+mag)/2, r1 = (1-mag)/2;
        aobj = {
            'xaxis.range[0]': r0*xr[0] + r1*xr[1],
            'xaxis.range[1]': r0*xr[1] + r1*xr[0],
            'yaxis.range[0]': r0*yr[0] + r1*yr[1],
            'yaxis.range[1]': r0*yr[1] + r1*yr[0]
        };
    }

    Plotly.relayout(graphInfo, aobj).then( function() {
        _this.updateActiveButton();
        if(astr === 'dragmode') {
            Plotly.Fx.setCursor(
                layout._paper.select('.nsewdrag'),
                {pan:'move', zoom:'crosshair'}[val]
            );
        }
    });
}

/**
 * Toggle the data hover mode
 * @Param {object} ev event object
 * @Return {HTMLelement}
 */
function handleHover3d (ev) {
    var _this = this,
        button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        layoutUpdate = {},
        graphInfo = this.graphInfo,
        layout = graphInfo._fullLayout,
        scenes = Object.keys(layout).filter(function(k){
            return k.match(/^scene[0-9]*$/);
        }).map( function (sceneKey) {
            return layout[sceneKey];
        });

    layoutUpdate[attr] = val;

    scenes.forEach( function (sceneLayout) {
        sceneLayout._scene.spikeEnable = !sceneLayout._scene.spikeEnable;
    });

    this.Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
        scenes[0]._container.focus();
    });
}

/**
 * Reconfigure keyboard bindings for webgl3D camera control on drag
 * @Param {object} ev event object
 * @Return {HTMLelement}
 */
function handleDrag3d (ev) {
    var _this = this,
        button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        layoutUpdate = {},
        graphInfo = this.graphInfo,
        layout = graphInfo._fullLayout,
        scenes = Object.keys(layout).filter(function(k){
            return k.match(/^scene[0-9]*$/);
        }).map( function (sceneKey) {
            return layout[sceneKey];
        });

    // set dragmode to given value
    layoutUpdate[attr] = val;

    // update the webgl3D key binding
    scenes.forEach( function (sceneLayout) {
        if ('_scene' in sceneLayout && 'camera' in sceneLayout._scene) {
            sceneLayout._scene.camera.keyBindingMode = val;
        }
    });

    this.Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
        scenes[0]._container.focus();
    });
}

/**
 * Reset the position of the webgl3D camera
 * @Param {object} ev event object
 * @Return {HTMLelement}
 */
function handleCamera3d (ev) {
    var _this = this,
        button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        layoutUpdate = {},
        graphInfo = this.graphInfo,
        layout = graphInfo._fullLayout,
        scenes = Object.keys(layout).filter(function(k){
            return k.match(/^scene[0-9]*$/);
        }).map( function (sceneKey) {
            return layout[sceneKey];
        }),
        _scene = layout.scene._scene;

    if (attr === 'resetDefault') {
        // Reset camera position to default
        _scene.setCameraToDefault();
    } else if (attr === 'resetLastSave') {
        // Reset camera back to the position at the last save
        var cameraPositionLastSave = _scene._cameraPositionLastSave;
        _scene.setCameraPosition(cameraPositionLastSave);
    }

    /**
     * TODO multiple scenes!
     * Ideally, in a multiple scene plot, the modebar buttons should
     * reset the camera position of the scene last moved.
     */
}

/**
 *
 * @Property config specification hash of button parameters
 */
ModeBar.prototype.config = {
    zoom2d: {
        title: 'Zoom',
        attr: 'dragmode',
        val: 'zoom',
        icon: 'ploticon-zoombox',
        click: handleCartesian
    },
    pan2d: {
        title: 'Pan',
        attr: 'dragmode',
        val: 'pan',
        icon: 'ploticon-pan',
        click: handleCartesian
    },
    zoomIn2d: {
        title: 'Zoom in',
        attr: 'zoom',
        val: 'in',
        icon: 'ploticon-zoom_plus',
        click: handleCartesian
    },
    zoomOut2d: {
        title: 'Zoom out',
        attr: 'zoom',
        val: 'out',
        icon: 'ploticon-zoom_minus',
        click: handleCartesian
    },
    autoScale2d: {
        title: 'Autoscale',
        attr: 'allaxes.autorange',
        val: '',
        icon: 'ploticon-autoscale',
        click: handleCartesian
    },
    hoverClosest2d: {
        title: 'Show closest data on hover',
        attr: 'hovermode',
        val: 'closest',
        icon: 'ploticon-tooltip_basic',
        click: handleCartesian
    },
    hoverCompare2d: {
        title: 'Compare data on hover',
        attr: 'hovermode',
        val: 'x',
        icon: 'ploticon-tooltip_compare',
        click: handleCartesian
    },
    zoom3d: {
        title: 'Zoom',
        attr: 'dragmode',
        val: 'zoom',
        icon: 'ploticon-zoombox',
        click: handleDrag3d
    },
    pan3d: {
        title: 'Pan',
        attr: 'dragmode',
        val: 'pan',
        icon: 'ploticon-pan',
        click: handleDrag3d
    },
    rotate3d: {
        title: 'Rotate',
        attr: 'dragmode',
        val: 'rotate',
        icon: 'icon-undo',
        click: handleDrag3d
    },
    resetCameraDefault3d: {
        title: 'Reset camera to default',
        attr: 'resetDefault',
        val: false,
        icon: 'icon-home',
        click: handleCamera3d
    },
    resetCameraLastSave3d: {
        title: 'Reset camera to last save',
        attr: 'resetLastSave',
        val: false,
        icon: 'icon-camera-retro',
        click: handleCamera3d
    },
    closest3d: {
        title: 'Toggle show closest data on hover',
        attr: 'hovermode',
        val: 'closest',
        icon: 'ploticon-tooltip_basic',
        click: handleHover3d
    }

};

})();
<commit_msg>add modebar cleanup method<commit_after>'use strict';

(function () {

/**
 * UI controller for interactive plots
 * @Class
 * @Param {object} config
 * @Param {object} config.buttons    nested arrays of grouped buttons to initialize
 * @Param {object} config.container  container div to append modebar
 * @Param {object} config.Plotly     main plotly namespace module
 * @Param {object} config.graphInfo  primary plot object containing data and layout
 */
function ModeBar (config) {

    if (!(this instanceof ModeBar)) return new ModeBar();

    var _this = this;

    this.Plotly = config.Plotly;
    this.graphInfo = config.graphInfo;
    this.element = document.createElement('div');
    this.element.className = 'modebar';
    this.buttons = config.buttons;
    this.buttonElements = [];

    this.buttons.forEach( function (buttonGroup) {
        var group = _this.createGroup();

        buttonGroup.forEach( function (buttonName) {
            var buttonConfig = _this.config[buttonName];

            if (!buttonConfig) {
                throw new Error(buttonName + ' not specfied in modebar configuration');
            }

            var button = _this.createButton(buttonConfig);

            _this.buttonElements.push(button);
            group.appendChild(button);
        });

        _this.element.appendChild(group);
    });

    config.container.appendChild(this.element);

    this.updateActiveButton();

}

window.ModeBar = ModeBar;

/**
 * Empty div for containing a group of buttons
 * @Return {HTMLelement}
 */
ModeBar.prototype.createGroup = function () {
    var group = document.createElement('div');
    group.className = 'btn-group float--left';

    return group;
};

/**
 * Create a new button div and set constant and configurable attributes
 * @Param {object} config
 * @Param {string} config.attr
 * @Param {string} config.val
 * @Param {string} config.title
 * @Param {function} config.click
 * @Return {HTMLelement}
 */
ModeBar.prototype.createButton = function (config) {
    var button = document.createElement('button');
    var icon = document.createElement('i');

    button.setAttribute('rel', 'tooltip');
    button.className = 'btn btn--icon btn--plot ploticon';

    button.setAttribute('data-attr', config.attr);
    button.setAttribute('data-val', config.val);
    button.setAttribute('title', config.title);
    button.addEventListener('click', config.click.bind(this));

    icon.className = config.icon;
    button.appendChild(icon);

    return button;
};

/**
 * Updates active button with attribute specified in layout
 * @Param {object} graphInfo plot object containing data and layout
 * @Return {HTMLelement}
 */
ModeBar.prototype.updateActiveButton = function () {
    var graphInfo = this.graphInfo;
    this.buttonElements.forEach( function (button) {
        var thisval = button.getAttribute('data-val') || true,
            dataAttr = button.getAttribute('data-attr'),
            curval = graphInfo._fullLayout[dataAttr];

        button.classList.toggle('active', curval===thisval);
    });
};

/**
 * Check if modebar is configured as button configuration argument
 * @Param {object} buttons 2d array of grouped button names
 * @Return {boolean}
 */
ModeBar.prototype.hasButtons = function (buttons) {
    var currentButtons = this.buttons;

    if (buttons.length !== currentButtons.length) return false;

    for (var i = 0; i < buttons.length; ++i) {
        if (buttons[i].length !== currentButtons[i].length) return false;
        for (var j = 0; j < buttons[i].length; j++) {
            if (buttons[i][j] !== currentButtons[i][j]) return false;
        }
    }

    return true;
};

/**
 * Apply D3 cartesian mode attributes to layout to update hover functionality
 * @Param {object} ev event object
 */
function handleCartesian (ev) {
    var button = ev.currentTarget,
        astr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        aobj = {},
        graphInfo = this.graphInfo,
        layout = this.graphInfo._fullLayout,
        Plotly = this.Plotly,
        _this = this;

    aobj[astr] = val;

    if(astr === 'zoom') {
        var xr = layout.xaxis.range,
            yr = layout.yaxis.range,
            mag = (val==='in') ? 0.5 : 2,
            r0 = (1+mag)/2, r1 = (1-mag)/2;
        aobj = {
            'xaxis.range[0]': r0*xr[0] + r1*xr[1],
            'xaxis.range[1]': r0*xr[1] + r1*xr[0],
            'yaxis.range[0]': r0*yr[0] + r1*yr[1],
            'yaxis.range[1]': r0*yr[1] + r1*yr[0]
        };
    }

    Plotly.relayout(graphInfo, aobj).then( function() {
        _this.updateActiveButton();
        if(astr === 'dragmode') {
            Plotly.Fx.setCursor(
                layout._paper.select('.nsewdrag'),
                {pan:'move', zoom:'crosshair'}[val]
            );
        }
    });
}

/**
 * Toggle the data hover mode
 * @Param {object} ev event object
 * @Return {HTMLelement}
 */
function handleHover3d (ev) {
    var _this = this,
        button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        layoutUpdate = {},
        graphInfo = this.graphInfo,
        layout = graphInfo._fullLayout,
        scenes = Object.keys(layout).filter(function(k){
            return k.match(/^scene[0-9]*$/);
        }).map( function (sceneKey) {
            return layout[sceneKey];
        });

    layoutUpdate[attr] = val;

    scenes.forEach( function (sceneLayout) {
        sceneLayout._scene.spikeEnable = !sceneLayout._scene.spikeEnable;
    });

    this.Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
        scenes[0]._container.focus();
    });
}

/**
 * Reconfigure keyboard bindings for webgl3D camera control on drag
 * @Param {object} ev event object
 * @Return {HTMLelement}
 */
function handleDrag3d (ev) {
    var _this = this,
        button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        layoutUpdate = {},
        graphInfo = this.graphInfo,
        layout = graphInfo._fullLayout,
        scenes = Object.keys(layout).filter(function(k){
            return k.match(/^scene[0-9]*$/);
        }).map( function (sceneKey) {
            return layout[sceneKey];
        });

    // set dragmode to given value
    layoutUpdate[attr] = val;

    // update the webgl3D key binding
    scenes.forEach( function (sceneLayout) {
        if ('_scene' in sceneLayout && 'camera' in sceneLayout._scene) {
            sceneLayout._scene.camera.keyBindingMode = val;
        }
    });

    this.Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
        scenes[0]._container.focus();
    });
}

/**
 * Reset the position of the webgl3D camera
 * @Param {object} ev event object
 * @Return {HTMLelement}
 */
function handleCamera3d (ev) {
    var _this = this,
        button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        layoutUpdate = {},
        graphInfo = this.graphInfo,
        layout = graphInfo._fullLayout,
        scenes = Object.keys(layout).filter(function(k){
            return k.match(/^scene[0-9]*$/);
        }).map( function (sceneKey) {
            return layout[sceneKey];
        }),
        _scene = layout.scene._scene;

    if (attr === 'resetDefault') {
        // Reset camera position to default
        _scene.setCameraToDefault();
    } else if (attr === 'resetLastSave') {
        // Reset camera back to the position at the last save
        var cameraPositionLastSave = _scene._cameraPositionLastSave;
        _scene.setCameraPosition(cameraPositionLastSave);
    }

    /**
     * TODO multiple scenes!
     * Ideally, in a multiple scene plot, the modebar buttons should
     * reset the camera position of the scene last moved.
     */
}

ModeBar.prototype.cleanup = function(){
    this.element.innerHTML = '';
    var modebarParent = this.element.parentNode;
    modebarParent.removeChild(this.element);
};

/**
 *
 * @Property config specification hash of button parameters
 */
ModeBar.prototype.config = {
    zoom2d: {
        title: 'Zoom',
        attr: 'dragmode',
        val: 'zoom',
        icon: 'ploticon-zoombox',
        click: handleCartesian
    },
    pan2d: {
        title: 'Pan',
        attr: 'dragmode',
        val: 'pan',
        icon: 'ploticon-pan',
        click: handleCartesian
    },
    zoomIn2d: {
        title: 'Zoom in',
        attr: 'zoom',
        val: 'in',
        icon: 'ploticon-zoom_plus',
        click: handleCartesian
    },
    zoomOut2d: {
        title: 'Zoom out',
        attr: 'zoom',
        val: 'out',
        icon: 'ploticon-zoom_minus',
        click: handleCartesian
    },
    autoScale2d: {
        title: 'Autoscale',
        attr: 'allaxes.autorange',
        val: '',
        icon: 'ploticon-autoscale',
        click: handleCartesian
    },
    hoverClosest2d: {
        title: 'Show closest data on hover',
        attr: 'hovermode',
        val: 'closest',
        icon: 'ploticon-tooltip_basic',
        click: handleCartesian
    },
    hoverCompare2d: {
        title: 'Compare data on hover',
        attr: 'hovermode',
        val: 'x',
        icon: 'ploticon-tooltip_compare',
        click: handleCartesian
    },
    zoom3d: {
        title: 'Zoom',
        attr: 'dragmode',
        val: 'zoom',
        icon: 'ploticon-zoombox',
        click: handleDrag3d
    },
    pan3d: {
        title: 'Pan',
        attr: 'dragmode',
        val: 'pan',
        icon: 'ploticon-pan',
        click: handleDrag3d
    },
    rotate3d: {
        title: 'Rotate',
        attr: 'dragmode',
        val: 'rotate',
        icon: 'icon-undo',
        click: handleDrag3d
    },
    resetCameraDefault3d: {
        title: 'Reset camera to default',
        attr: 'resetDefault',
        val: false,
        icon: 'icon-home',
        click: handleCamera3d
    },
    resetCameraLastSave3d: {
        title: 'Reset camera to last save',
        attr: 'resetLastSave',
        val: false,
        icon: 'icon-camera-retro',
        click: handleCamera3d
    },
    closest3d: {
        title: 'Toggle show closest data on hover',
        attr: 'hovermode',
        val: 'closest',
        icon: 'ploticon-tooltip_basic',
        click: handleHover3d
    }

};

})();
<|endoftext|>


<commit_before>/*
 * Collapsible widget
 * https://github.com/filamentgroup/collapsible
 * Copyright (c) 2014 Filament Group, Inc.
 * Licensed under the MIT, GPL licenses.
 */

;(function ($, window, document, undefined) {

	// Defaults
	var pluginName = "collapsible";
	// overrideable defaults
	var defaults = {
		pluginClass: pluginName,
		collapsedClass: pluginName + "-collapsed",
		headerClass: pluginName + "-header",
		contentClass: pluginName + "-content",
		enhancedClass: pluginName + "-enhanced",
		instructions: false,
		collapsed: false
	};

	// plugin constructor
	function Plugin(element, options) {
		this.element = $( element );
		var self = this,
			dataOptions = {};

		// Allow data-attr option setting
		if( this.element.is( "[data-config]" ) ){
			for( var option in defaults ){
					if( defaults.hasOwnProperty( option) ){
					var value = self.element.attr( "data-" + option.replace( /[A-Z]/g, function( c ) {
									return "-" + c.toLowerCase();
								}));

					if ( value !== undefined ) {
						if( value === "true" || value === "false" ){
							dataOptions[ option ] = value === "true";
						}
						else {
							dataOptions[ option ] = value;
						}
					}
				}
			}
		}

		this.options = $.extend( {}, defaults, dataOptions, options );

		// allow the collapsedClass to set the option if specified
		if( this.element.is( "." + this.options.collapsedClass ) ){
			this.options.collapsed= true;
		}

		this._defaults = defaults;
		this._name = pluginName;
		this.init();
	}

	Plugin.prototype = {
		init: function () {
			this.header = this.element.children().eq( 0 );
			this.content = this.header.next();
			this._addAttributes();
			this._bindEvents();
			this.element.trigger( "init" );
		},

		_addAttributes: function(){
			this.element
				.addClass( this.options.pluginClass )
				.addClass( this.options.enhancedClass );

			this.header.addClass( this.options.headerClass );

			if( this.options.instructions ){
				this.header.attr( "title", this.options.instructions );
			}

			this.header.attr( "role", "button" );

			this.header.attr( "aria-expanded", "true" );

			this.header.attr( "tabindex", "0" );

			this.content.addClass( this.options.contentClass );
		},

		_bindEvents: function(){
			var self = this;

			this.header
				// use the tappy plugin if it's available
				.bind( ( window.tappy ? "tap" : "click" ) + "." + pluginName, function( e ){
					self.toggle();
					e.preventDefault();
				})
				.bind( "keyup." + pluginName, function( e ){
					if( e.which === 13 || e.which === 32 ){
						self.toggle();
						e.preventDefault();
					}
				});

			if( this.options.collapsed ){
				this.collapse();
			}
		},

		collapsed: false,

		expand: function () {
			var self = $( this ).data( pluginName ) || this;
			self.element.removeClass( self.options.collapsedClass );
			self.collapsed = false;
			self.header.attr( "aria-expanded", "true" );
			self.element.trigger( "expand" );
		},

		collapse: function() {
			var self = $( this ).data( pluginName ) || this;
			self.element.addClass( self.options.collapsedClass );
			self.collapsed = true;
			self.header.attr( "aria-expanded", "false" );
			self.element.trigger( "collapse" );
		},

		toggle: function(){
			if(  this.collapsed ){
				this.expand();
			} else {
				this.collapse();
			}
		}
	};

	// lightweight plugin wrapper around the constructor,
	// preventing against multiple instantiations
	$.fn[ pluginName ] = function (options) {
		return this.each(function () {
			if ( !$( this ).data( pluginName ) ) {
				$( this ).data( pluginName, new Plugin( this, options ));
			}
		});
	};

	// Simple auto-init by selector that runs when the dom is ready. Trigger "enhance" if desirable.
	$( document ).bind( "enhance", function( e ){
		var selector = "." + pluginName;
		$( $( e.target ).is( selector ) && e.target ).add( selector, e.target ).filter( selector )[ pluginName ]();
	});

})(jQuery, window, document);
<commit_msg>bring back on, if that's preferrable.works now<commit_after>/*
 * Collapsible widget
 * https://github.com/filamentgroup/collapsible
 * Copyright (c) 2014 Filament Group, Inc.
 * Licensed under the MIT, GPL licenses.
 */

;(function ($, window, document, undefined) {

	// Defaults
	var pluginName = "collapsible";
	// overrideable defaults
	var defaults = {
		pluginClass: pluginName,
		collapsedClass: pluginName + "-collapsed",
		headerClass: pluginName + "-header",
		contentClass: pluginName + "-content",
		enhancedClass: pluginName + "-enhanced",
		instructions: false,
		collapsed: false
	};

	// plugin constructor
	function Plugin(element, options) {
		this.element = $( element );
		var self = this,
			dataOptions = {};

		// Allow data-attr option setting
		if( this.element.is( "[data-config]" ) ){
			for( var option in defaults ){
					if( defaults.hasOwnProperty( option) ){
					var value = self.element.attr( "data-" + option.replace( /[A-Z]/g, function( c ) {
									return "-" + c.toLowerCase();
								}));

					if ( value !== undefined ) {
						if( value === "true" || value === "false" ){
							dataOptions[ option ] = value === "true";
						}
						else {
							dataOptions[ option ] = value;
						}
					}
				}
			}
		}

		this.options = $.extend( {}, defaults, dataOptions, options );

		// allow the collapsedClass to set the option if specified
		if( this.element.is( "." + this.options.collapsedClass ) ){
			this.options.collapsed= true;
		}

		this._defaults = defaults;
		this._name = pluginName;
		this.init();
	}

	Plugin.prototype = {
		init: function () {
			this.header = this.element.children().eq( 0 );
			this.content = this.header.next();
			this._addAttributes();
			this._bindEvents();
			this.element.trigger( "init" );
		},

		_addAttributes: function(){
			this.element
				.addClass( this.options.pluginClass )
				.addClass( this.options.enhancedClass );

			this.header.addClass( this.options.headerClass );

			if( this.options.instructions ){
				this.header.attr( "title", this.options.instructions );
			}

			this.header.attr( "role", "button" );

			this.header.attr( "aria-expanded", "true" );

			this.header.attr( "tabindex", "0" );

			this.content.addClass( this.options.contentClass );
		},

		_bindEvents: function(){
			var self = this;

			this.header
				// use the tappy plugin if it's available
				.on( ( window.tappy ? "tap" : "click" ) + "." + pluginName, function( e ){
					self.toggle();
					e.preventDefault();
				})
				.on( "keyup." + pluginName, function( e ){
					if( e.which === 13 || e.which === 32 ){
						self.toggle();
						e.preventDefault();
					}
				});

			if( this.options.collapsed ){
				this.collapse();
			}
		},

		collapsed: false,

		expand: function () {
			var self = $( this ).data( pluginName ) || this;
			self.element.removeClass( self.options.collapsedClass );
			self.collapsed = false;
			self.header.attr( "aria-expanded", "true" );
			self.element.trigger( "expand" );
		},

		collapse: function() {
			var self = $( this ).data( pluginName ) || this;
			self.element.addClass( self.options.collapsedClass );
			self.collapsed = true;
			self.header.attr( "aria-expanded", "false" );
			self.element.trigger( "collapse" );
		},

		toggle: function(){
			if(  this.collapsed ){
				this.expand();
			} else {
				this.collapse();
			}
		}
	};

	// lightweight plugin wrapper around the constructor,
	// preventing against multiple instantiations
	$.fn[ pluginName ] = function (options) {
		return this.each(function () {
			if ( !$( this ).data( pluginName ) ) {
				$( this ).data( pluginName, new Plugin( this, options ));
			}
		});
	};

	// Simple auto-init by selector that runs when the dom is ready. Trigger "enhance" if desirable.
	$( document ).bind( "enhance", function( e ){
		var selector = "." + pluginName;
		$( $( e.target ).is( selector ) && e.target ).add( selector, e.target ).filter( selector )[ pluginName ]();
	});

})(jQuery, window, document);
<|endoftext|>


<commit_before>// ==UserScript==
// @name Main
// @include http://*.youneedabudget.com/*
// @include https://*.youneedabudget.com/*
// @require res/features/allSettings.js
// ==/UserScript==

function injectCSS(path) {
  var link = document.createElement('link');
  link.setAttribute('rel', 'stylesheet');
  link.setAttribute('type', 'text/css');
  link.setAttribute('href', kango.io.getResourceUrl(path));

  document.getElementsByTagName('head')[0].appendChild(link);
}

var previouslyInjectedScripts = [];

function injectScript(path) {
  if (previouslyInjectedScripts.indexOf(path) < 0) {

    previouslyInjectedScripts.push(path);

    var script = document.createElement('script');
    script.setAttribute('type', 'text/javascript');
    script.setAttribute('src', kango.io.getResourceUrl(path));

    document.getElementsByTagName('head')[0].appendChild(script);
  }
}

function injectJSString(js) {
  var script = document.createElement('script');
  script.text = js;

  document.getElementsByTagName('head')[0].appendChild(script);
}

function applySettingsToDom() {
  ynabToolKit.settings.forEach(function(setting) {

    getKangoSetting(setting.name).then(function (data) {
      if (data in setting.actions) {
        var selectedActions = setting.actions[data.toString()];
        for (var i = 0; i < selectedActions.length; i += 2) {
          var action = selectedActions[i];
          var target = selectedActions[i + 1];

          if (action == "injectCSS") {
            injectCSS(target);
          } else if (action == "injectScript") {
            injectScript(target);
          } else if (action == "injectJSString") {
            injectJSString(target);
          } else {
            throw "Invalid action '" + action + "'. Only injectCSS, injectScript and injectJSString are currently supported.";
          }
        }
      }
    })
  });
}

/* Init ynabToolKit object and import options from Kango  */
var optionsArray = [];

function pushOption(setting) {
    return getKangoSetting(setting.name).then(function (data) {
      optionsArray.push(setting.name + " : " + data + ", ");
    });
}

var optionsPromises = [];

ynabToolKit.settings.forEach(function(setting) {
  optionsPromises.push(pushOption(setting));
});

Promise.all(optionsPromises).then(function() {
  injectJSString("window.ynabToolKit = {}; ynabToolKit.options= {" + optionsArray.reduce(function(a, b) { return a + b }, "") + "}");

  /* Load this to setup shared utility functions */
  injectScript('res/features/shared/main.js');

  /* Global toolkit css. */
  injectCSS('res/features/shared/main.css');

  /* This script to be built automatically by the python script */
  injectScript('res/features/act-on-change/feedChanges.js');

  /* Load this to setup behaviors when the DOM updates and shared functions */
  injectScript('res/features/act-on-change/main.js');

  ensureDefaultsAreSet().then(applySettingsToDom);
});
<commit_msg>Switched to JSON.stringify.<commit_after>// ==UserScript==
// @name Main
// @include http://*.youneedabudget.com/*
// @include https://*.youneedabudget.com/*
// @require res/features/allSettings.js
// ==/UserScript==

function injectCSS(path) {
  var link = document.createElement('link');
  link.setAttribute('rel', 'stylesheet');
  link.setAttribute('type', 'text/css');
  link.setAttribute('href', kango.io.getResourceUrl(path));

  document.getElementsByTagName('head')[0].appendChild(link);
}

var previouslyInjectedScripts = [];

function injectScript(path) {
  if (previouslyInjectedScripts.indexOf(path) < 0) {

    previouslyInjectedScripts.push(path);

    var script = document.createElement('script');
    script.setAttribute('type', 'text/javascript');
    script.setAttribute('src', kango.io.getResourceUrl(path));

    document.getElementsByTagName('head')[0].appendChild(script);
  }
}

function injectJSString(js) {
  var script = document.createElement('script');
  script.text = js;

  document.getElementsByTagName('head')[0].appendChild(script);
}

function applySettingsToDom() {
  ynabToolKit.settings.forEach(function(setting) {

    getKangoSetting(setting.name).then(function (data) {
      if (data in setting.actions) {
        var selectedActions = setting.actions[data.toString()];
        for (var i = 0; i < selectedActions.length; i += 2) {
          var action = selectedActions[i];
          var target = selectedActions[i + 1];

          if (action == "injectCSS") {
            injectCSS(target);
          } else if (action == "injectScript") {
            injectScript(target);
          } else if (action == "injectJSString") {
            injectJSString(target);
          } else {
            throw "Invalid action '" + action + "'. Only injectCSS, injectScript and injectJSString are currently supported.";
          }
        }
      }
    })
  });
}

/* Init ynabToolKit object and import options from Kango  */
var options = {};

function pushOption(setting) {
    return getKangoSetting(setting.name).then(function (data) {
      options[setting.name] = data;
    });
}

var optionsPromises = [];

ynabToolKit.settings.forEach(function(setting) {
  optionsPromises.push(pushOption(setting));
});

Promise.all(optionsPromises).then(function() {
  injectJSString("window.ynabToolKit = {}; ynabToolKit.options = " + JSON.stringify(options) + "; Object.freeze(ynabToolKit.options); Object.seal(ynabToolKit.options);");

  /* Load this to setup shared utility functions */
  injectScript('res/features/shared/main.js');

  /* Global toolkit css. */
  injectCSS('res/features/shared/main.css');

  /* This script to be built automatically by the python script */
  injectScript('res/features/act-on-change/feedChanges.js');

  /* Load this to setup behaviors when the DOM updates and shared functions */
  injectScript('res/features/act-on-change/main.js');

  ensureDefaultsAreSet().then(applySettingsToDom);
});
<|endoftext|>


<commit_before>import * as I from 'immutable';

const now = (() => {
  if (typeof performance != 'undefined' && performance.now)
    return performance.now.bind(performance);
  else if (typeof process != 'undefined' && process.hrtime)
    return () => {
      const [sec, nsec] = process.hrtime();
      return sec * 1000 + nsec / 1000000;
    };
  else
    return Date.now.bind(Date);
})();

export function timer() {
  let _last = now();

  return () => {
    const previous = _last;
    _last = now();
    return _last - previous;
  };
};

export function timers() {
  const _accumulated = {};
  const _start = {};

  return {
    start(key) {
      if (key != null && _start[key] == null) {
        _start[key] = now();
      }
    },
    stop(key) {
      if (_start[key] != null) {
        _accumulated[key] = (_accumulated[key] || 0.0) + now() - _start[key];
        _start[key] = null;
      }
    },
    stopAll() {
      for (const k in _start)
        this.stop(k);
    },
    switchTo(key) {
      this.stopAll();
      this.start(key);
    },
    current() {
      for (const k in _start) {
        if (_start[k] != null) {
          this.stop(k);
          this.start(k);
        }
      }

      const result = {};
      for (const k in _accumulated)
        result[k] = _accumulated[k];
      return result;
    }
  };
};
<commit_msg>include execution counts for timers()<commit_after>import * as I from 'immutable';

const now = (() => {
  if (typeof performance != 'undefined' && performance.now)
    return performance.now.bind(performance);
  else if (typeof process != 'undefined' && process.hrtime)
    return () => {
      const [sec, nsec] = process.hrtime();
      return sec * 1000 + nsec / 1000000;
    };
  else
    return Date.now.bind(Date);
})();

export function timer() {
  let _last = now();

  return () => {
    const previous = _last;
    _last = now();
    return _last - previous;
  };
};

export function timers() {
  const _accumulated = {};
  const _start = {};
  const _count = {};

  return {
    start(key) {
      if (key != null && _start[key] == null) {
        _start[key] = now();
        _count[key] = (_count[key] || 0) + 1
      }
    },
    stop(key) {
      if (_start[key] != null) {
        _accumulated[key] = (_accumulated[key] || 0.0) + now() - _start[key];
        _start[key] = null;
      }
    },
    stopAll() {
      for (const k in _start)
        this.stop(k);
    },
    switchTo(key) {
      this.stopAll();
      this.start(key);
    },
    current() {
      for (const k in _start) {
        if (_start[k] != null) {
          this.stop(k);
          this.start(k);
        }
      }

      const result = {};
      for (const k in _accumulated)
        result[k] = [_accumulated[k], _count[k]];
      return result;
    }
  };
};
<|endoftext|>


<commit_before>"use strict";

(function() {

walContext.Composition = function( wCtx ) {
	this.wCtx = wCtx;
	this.wSamples = [];
};

walContext.Composition.prototype = {
	addSamples: function( wSamplesArr ) {
		var that = this;
		wSamplesArr.map( function( ws ) {
			if ( that.wSamples.length === 0 || that.wSamples.indexOf( ws ) === -1 ) {
				that.wSamples.push( ws );
				ws.setComposition( ws );
			}
		});
	},
	removeSamples: function( wSamplesArr ) {
		var
			that = this,
			index
		;

		wSamplesArr.map( function( ws ) {
			if ( that.wSamples.length !== 0 && ( index = that.wSamples.indexOf( ws ) ) !== -1 ) {
				that.wSamples.splice( index, 1 );
				ws.setComposition( null );
			}
		});
	},
	loadSamples: function( compoOffset ) {
		this.wSamples.map( function( ws ) {
			if ( !compoOffset || ws.getEndTime() > compoOffset ) {
				ws.load();
			}
		});
		return this;
	},
	playSamples: function( compoOffset ) {
		var offset, start;
		this.wSamples.map( function( ws ) {
			if ( !compoOffset || ws.getEndTime() > compoOffset ) {
				start = compoOffset ? ws.when - compoOffset : ws.when;
				offset = compoOffset ? compoOffset - ws.when : ws.offset;
				ws.start( start, offset < 0 ? 0 : offset );
			}
		});
		return this;
	},
	stopSamples: function( compoOffset ) {
		this.wSamples.map( function( ws ) {
			ws.stop();
		});
		return this;
	},
	getLastSample: function() {
		var s, sEnd, end;
		if ( this.wSamples.length ) {
			s = this.wSamples[ 0 ];
			sEnd = s.getEndTime()
			this.wSamples.map( function( ws ) {
				end = ws.getEndTime();
				if ( end > sEnd ) {
					s = ws;
					sEnd = end;
				}
			});
		}
		return s;
	}
};

})();<commit_msg>composition : manage last sample and on ended composition<commit_after>"use strict";

(function() {

walContext.Composition = function( wCtx ) {
	this.wCtx = wCtx;
	this.wSamples = [];
	this.lastSample = null;
	this.onended = function() {};
};

walContext.Composition.prototype = {
	addSamples: function( wSamplesArr ) {
		var that = this;
		wSamplesArr.map( function( ws ) {
			if ( that.wSamples.length === 0 || that.wSamples.indexOf( ws ) === -1 ) {
				if ( that.wSamples.length === 0 || ws.getEndTime() > that.lastSample.getEndTime() ) {
					if ( that.lastSample ) {
						that.lastSample.onended( function() {} );
					}
					that.lastSample = ws;
					that.lastSample.onended( that.onended );
				}
				that.wSamples.push( ws );
				ws.setComposition( ws );
			}
		});
	},
	removeSamples: function( wSamplesArr ) {
		var
			that = this,
			index
		;

		wSamplesArr.map( function( ws ) {
			if ( that.wSamples.length !== 0 && ( index = that.wSamples.indexOf( ws ) ) !== -1 ) {
				that.wSamples.splice( index, 1 );
				ws.setComposition( null );
				if ( that.wSamples.length !== 0 || ws === that.lastSample ) {
					ws.onended( function() {} );
					that.lastSample = that.getLastSample();
					that.lastSample.onended( that.onended );
				} else if ( that.wSamples.length === 0 ) {
					ws.onended( function() {} );
					that.lastSample = null;
					that.onended( null );
				}
			}
		});
	},
	updateSamples: function( ws ) {
		var newLast;

		if ( this.wSamples.length !== 0 && ws !== this.lastSample && ws.getEndTime() > this.lastSample.getEndTime() ) {
			if ( this.lastSample ) {
				this.lastSample.onended( function() {} );
			}
			this.lastSample = ws;
			this.lastSample.onended( this.onended );
		} else if ( this.wSamples.length !== 0 && ws === this.lastSample && ( ( newLast = this.getLastSample() ) !==  ws ) ) {
			if ( this.lastSample ) {
				this.lastSample.onended( function() {} );
			}
			this.lastSample = newLast;
			this.lastSample.onended( this.onended );
		}
	},
	loadSamples: function( compoOffset ) {
		this.wSamples.map( function( ws ) {
			if ( !compoOffset || ws.getEndTime() > compoOffset ) {
				ws.load();
			}
		});
		return this;
	},
	playSamples: function( compoOffset ) {
		var offset, start;
		this.wSamples.map( function( ws ) {
			if ( !compoOffset || ws.getEndTime() > compoOffset ) {
				start = compoOffset ? ws.when - compoOffset : ws.when;
				offset = compoOffset ? compoOffset - ws.when : ws.offset;
				ws.start( start, offset < 0 ? 0 : offset );
			}
		});
		return this;
	},
	stopSamples: function( compoOffset ) {
		this.wSamples.map( function( ws ) {
			ws.stop();
		});
		return this;
	},
	getLastSample: function() {
		var s, sEnd, end;
		if ( this.wSamples.length ) {
			s = this.wSamples[ 0 ];
			sEnd = s.getEndTime()
			this.wSamples.map( function( ws ) {
				end = ws.getEndTime();
				if ( end > sEnd ) {
					s = ws;
					sEnd = end;
				}
			});
		}
		return s;
	},
	setOnEnded: function( fn ) {
		this.onended = fn;
		this.lastSample.onended( fn );
	}
};

})();<|endoftext|>


<commit_before>import _ from './util/lodash-wrap'
import getAllCustomPrototypalFunctionNames from './util/get-all-custom-prototypal-function-names'
import tdFunction from './function'
import config from './config'
import copyProperties from './util/copy-properties'

export default (typeOrNames) =>
  _.isFunction(typeOrNames)
    ? fakeConstructorFromType(typeOrNames)
    : fakeConstructorFromNames(typeOrNames)

var fakeConstructorFromType = (type) =>
  _.tap(createFakeType(type), (fakeType) => {
    const name = type.name || ''
    copyProperties(type, fakeType)
    copyProperties(type.prototype, fakeType.prototype)

    // Override "static" functions with instance test doubles
    _.each(_.functions(type), funcName => {
      fakeType[funcName] = tdFunction(`${name}.${funcName}`)
    })

    // Override prototypal functions with instance test doubles
    _.each(getAllCustomPrototypalFunctionNames(type), funcName => {
      fakeType.prototype[funcName] = tdFunction(`${name}#${funcName}`)
    })

    addToStringMethodsToFakeType(fakeType, name)
  })

var createFakeType = (type) => {
  const fauxConstructor = tdFunction(`${type.name || 'anonymous'} constructor`)

  if (config().extendWhenReplacingConstructors) {
    return class TestDoubleConstructorExtendingRealType extends type {
      constructor () {
        super(...arguments)
        fauxConstructor(...arguments)
      }
    }
  } else {
    return class TestDoubleConstructor {
      constructor () {
        fauxConstructor(...arguments)
      }
    }
  }
}

var fakeConstructorFromNames = (funcNames) => {
  return _.tap(class TestDoubleConstructor {}, (cls) => {
    _.each(funcNames, (funcName) => {
      cls.prototype[funcName] = tdFunction(`#${funcName}`)
    })

    addToStringMethodsToFakeType(cls)
  })
}

var addToStringMethodsToFakeType = (fakeType, name) => {
  fakeType.toString = () =>
    `[test double constructor${name ? ` for "${name}"` : ''}]`

  fakeType.prototype.toString = () =>
    `[test double instance of constructor${name ? ` "${name}"` : ''}]`
}
<commit_msg>phew standard<commit_after>import _ from './util/lodash-wrap'
import getAllCustomPrototypalFunctionNames from './util/get-all-custom-prototypal-function-names'
import tdFunction from './function'
import config from './config'
import copyProperties from './util/copy-properties'

export default (typeOrNames) =>
  _.isFunction(typeOrNames)
    ? fakeConstructorFromType(typeOrNames)
    : fakeConstructorFromNames(typeOrNames)

var fakeConstructorFromType = (type) =>
  _.tap(createFakeType(type), (fakeType) => {
    const name = type.name || ''
    copyProperties(type, fakeType)
    copyProperties(type.prototype, fakeType.prototype)

    // Override "static" functions with instance test doubles
    _.each(_.functions(type), funcName => {
      fakeType[funcName] = tdFunction(`${name}.${funcName}`)
    })

    // Override prototypal functions with instance test doubles
    _.each(getAllCustomPrototypalFunctionNames(type), funcName => {
      fakeType.prototype[funcName] = tdFunction(`${name}#${funcName}`)
    })

    addToStringMethodsToFakeType(fakeType, name)
  })

var createFakeType = (type) => {
  const fauxConstructor = tdFunction(`${type.name || 'anonymous'} constructor`)

  if (config().extendWhenReplacingConstructors) {
    return class TestDoubleConstructorExtendingRealType extends type {
      constructor () {
        super(...arguments)
        fauxConstructor(...arguments)
      }
    }
  } else {
    return class TestDoubleConstructor {
      constructor () {
        fauxConstructor(...arguments)
      }
    }
  }
}

var fakeConstructorFromNames = (funcNames) => {
  return _.tap(class TestDoubleConstructor {}, (cls) => {
    _.each(funcNames, (funcName) => {
      cls.prototype[funcName] = tdFunction(`#${funcName}`)
    })

    addToStringMethodsToFakeType(cls)
  })
}

var addToStringMethodsToFakeType = (fakeType, name) => {
  fakeType.toString = () =>
    `[test double constructor${name ? ` for "${name}"` : ''}]`

  fakeType.prototype.toString = () =>
    `[test double instance of constructor${name ? ` "${name}"` : ''}]`
}
<|endoftext|>


<commit_before>/**
* _Internal Class_, not generally used outside of the engine's internals.
*
* @class Common
*/

var Common = {};

(function() {

    /**
     * Description
     * @method extend
     * @param {} obj
     * @param {boolean} deep
     * @return {} obj extended
     */
    Common.extend = function(obj, deep) {
        var argsStart,
            args,
            deepClone;

        if (typeof deep === 'boolean') {
            argsStart = 2;
            deepClone = deep;
        } else {
            argsStart = 1;
            deepClone = true;
        }

        args = Array.prototype.slice.call(arguments, argsStart);

        for (var i = 0; i < args.length; i++) {
            var source = args[i];

            if (source) {
                for (var prop in source) {
                    if (deepClone && source[prop] && source[prop].constructor === Object) {
                        if (!obj[prop] || obj[prop].constructor === Object) {
                            obj[prop] = obj[prop] || {};
                            Common.extend(obj[prop], deepClone, source[prop]);
                        } else {
                            obj[prop] = source[prop];
                        }
                    } else {
                        obj[prop] = source[prop];
                    }
                }
            }
        }

        return obj;
    };

    /**
     * Creates a new clone of the object, if deep is true references will also be cloned
     * @method clone
     * @param {} obj
     * @param {bool} deep
     * @return {} obj cloned
     */
    Common.clone = function(obj, deep) {
        return Common.extend({}, deep, obj);
    };

    /**
     * Description
     * @method keys
     * @param {} obj
     * @return {string[]} keys
     */
    Common.keys = function(obj) {
        if (Object.keys)
            return Object.keys(obj);

        // avoid hasOwnProperty for performance
        var keys = [];
        for (var key in obj)
            keys.push(key);
        return keys;
    };

    /**
     * Description
     * @method values
     * @param {} obj
     * @return {array} Array of the objects property values
     */
    Common.values = function(obj) {
        var values = [];

        if (Object.keys) {
            var keys = Object.keys(obj);
            for (var i = 0; i < keys.length; i++) {
                values.push(obj[keys[i]]);
            }
            return values;
        }

        // avoid hasOwnProperty for performance
        for (var key in obj)
            values.push(obj[key]);
        return values;
    };

    /**
     * Description
     * @method shadeColor
     * @param {string} color
     * @param {number} percent
     * @return {string} A hex colour string made by lightening or darkening color by percent
     */
    Common.shadeColor = function(color, percent) {
        // http://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color
        var colorInteger = parseInt(color.slice(1),16),
            amount = Math.round(2.55 * percent),
            R = (colorInteger >> 16) + amount,
            B = (colorInteger >> 8 & 0x00FF) + amount,
            G = (colorInteger & 0x0000FF) + amount;
        return "#" + (0x1000000 + (R < 255 ? R < 1 ? 0 : R :255) * 0x10000
                + (B < 255 ? B < 1 ? 0 : B : 255) * 0x100
                + (G < 255 ? G < 1 ? 0 : G : 255)).toString(16).slice(1);
    };

    /**
     * Description
     * @method shuffle
     * @param {array} array
     * @return {array} array shuffled randomly
     */
    Common.shuffle = function(array) {
        for (var i = array.length - 1; i > 0; i--) {
            var j = Math.floor(Math.random() * (i + 1));
            var temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        }
        return array;
    };

    /**
     * Description
     * @method choose
     * @param {array} choices
     * @return {object} A random choice object from the array
     */
    Common.choose = function(choices) {
        return choices[Math.floor(Math.random() * choices.length)];
    };

    /**
     * Description
     * @method isElement
     * @param {object} obj
     * @return {boolean} True if the object is a HTMLElement, otherwise false
     */
    Common.isElement = function(obj) {
        // http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object
        try {
            return obj instanceof HTMLElement;
        }
        catch(e){
            return (typeof obj==="object") &&
              (obj.nodeType===1) && (typeof obj.style === "object") &&
              (typeof obj.ownerDocument ==="object");
        }
    };

    /**
     * Description
     * @method clamp
     * @param {number} value
     * @param {number} min
     * @param {number} max
     * @return {number} The value clamped between min and max inclusive
     */
    Common.clamp = function(value, min, max) {
        if (value < min)
            return min;
        if (value > max)
            return max;
        return value;
    };

    /**
     * Description
     * @method sign
     * @param {number} value
     * @return {number} -1 if negative, +1 if 0 or positive
     */
    Common.sign = function(value) {
        return value < 0 ? -1 : 1;
    };

    /**
     * Description
     * @method now
     * @return {number} the current timestamp (high-res if avaliable)
     */
    Common.now = function() {
        // http://stackoverflow.com/questions/221294/how-do-you-get-a-timestamp-in-javascript
        // https://gist.github.com/davidwaterston/2982531

        var perf = window.performance;

        if (perf) {
            perf.now = perf.now || perf.webkitNow || perf.msNow || perf.oNow || perf.mozNow;
            return +(perf.now());
        }

        return +(new Date());
    };

    /**
     * Description
     * @method random
     * @param {number} min
     * @param {number} max
     * @return {number} A random number between min and max inclusive
     */
    Common.random = function(min, max) {
        return min + Math.random() * (max - min);
    };

    /**
     * Converts a CSS hex colour string into an integer
     * @method colorToNumber
     * @param {string} colorString
     * @return {number} An integer representing the CSS hex string
     */
    Common.colorToNumber = function(colorString) {
        colorString = colorString.replace('#','');

        if (colorString.length == 3) {
            colorString = colorString.charAt(0) + colorString.charAt(0)
                        + colorString.charAt(1) + colorString.charAt(1)
                        + colorString.charAt(2) + colorString.charAt(2);
        }

        return parseInt(colorString, 16);
    };

    /**
     * A wrapper for console.log, for providing errors and warnings
     * @method log
     * @param {string} message
     * @param {string} type
     */
    Common.log = function(message, type) {
        if (!console || !console.log)
            return;

        var style;

        switch (type) {
            case 'warn':
                style = 'color: coral';
            break;
            case 'error':
                style = 'color: red';
        }

        console.log('%c [Matter] ' + type + ': ' + message, style);
    };

})();<commit_msg>appease jshint<commit_after>/**
* _Internal Class_, not generally used outside of the engine's internals.
*
* @class Common
*/

var Common = {};

(function() {

    /**
     * Description
     * @method extend
     * @param {} obj
     * @param {boolean} deep
     * @return {} obj extended
     */
    Common.extend = function(obj, deep) {
        var argsStart,
            args,
            deepClone;

        if (typeof deep === 'boolean') {
            argsStart = 2;
            deepClone = deep;
        } else {
            argsStart = 1;
            deepClone = true;
        }

        args = Array.prototype.slice.call(arguments, argsStart);

        for (var i = 0; i < args.length; i++) {
            var source = args[i];

            if (source) {
                for (var prop in source) {
                    if (deepClone && source[prop] && source[prop].constructor === Object) {
                        if (!obj[prop] || obj[prop].constructor === Object) {
                            obj[prop] = obj[prop] || {};
                            Common.extend(obj[prop], deepClone, source[prop]);
                        } else {
                            obj[prop] = source[prop];
                        }
                    } else {
                        obj[prop] = source[prop];
                    }
                }
            }
        }

        return obj;
    };

    /**
     * Creates a new clone of the object, if deep is true references will also be cloned
     * @method clone
     * @param {} obj
     * @param {bool} deep
     * @return {} obj cloned
     */
    Common.clone = function(obj, deep) {
        return Common.extend({}, deep, obj);
    };

    /**
     * Description
     * @method keys
     * @param {} obj
     * @return {string[]} keys
     */
    Common.keys = function(obj) {
        if (Object.keys)
            return Object.keys(obj);

        // avoid hasOwnProperty for performance
        var keys = [];
        for (var key in obj)
            keys.push(key);
        return keys;
    };

    /**
     * Description
     * @method values
     * @param {} obj
     * @return {array} Array of the objects property values
     */
    Common.values = function(obj) {
        var values = [];

        if (Object.keys) {
            var keys = Object.keys(obj);
            for (var i = 0; i < keys.length; i++) {
                values.push(obj[keys[i]]);
            }
            return values;
        }

        // avoid hasOwnProperty for performance
        for (var key in obj)
            values.push(obj[key]);
        return values;
    };

    /**
     * Description
     * @method shadeColor
     * @param {string} color
     * @param {number} percent
     * @return {string} A hex colour string made by lightening or darkening color by percent
     */
    Common.shadeColor = function(color, percent) {
        // http://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color
        var colorInteger = parseInt(color.slice(1),16),
            amount = Math.round(2.55 * percent),
            R = (colorInteger >> 16) + amount,
            B = (colorInteger >> 8 & 0x00FF) + amount,
            G = (colorInteger & 0x0000FF) + amount;
        return "#" + (0x1000000 + (R < 255 ? R < 1 ? 0 : R :255) * 0x10000
                + (B < 255 ? B < 1 ? 0 : B : 255) * 0x100
                + (G < 255 ? G < 1 ? 0 : G : 255)).toString(16).slice(1);
    };

    /**
     * Description
     * @method shuffle
     * @param {array} array
     * @return {array} array shuffled randomly
     */
    Common.shuffle = function(array) {
        for (var i = array.length - 1; i > 0; i--) {
            var j = Math.floor(Math.random() * (i + 1));
            var temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        }
        return array;
    };

    /**
     * Description
     * @method choose
     * @param {array} choices
     * @return {object} A random choice object from the array
     */
    Common.choose = function(choices) {
        return choices[Math.floor(Math.random() * choices.length)];
    };

    /**
     * Description
     * @method isElement
     * @param {object} obj
     * @return {boolean} True if the object is a HTMLElement, otherwise false
     */
    Common.isElement = function(obj) {
        // http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object
        try {
            return obj instanceof HTMLElement;
        }
        catch(e){
            return (typeof obj==="object") &&
              (obj.nodeType===1) && (typeof obj.style === "object") &&
              (typeof obj.ownerDocument ==="object");
        }
    };

    /**
     * Description
     * @method clamp
     * @param {number} value
     * @param {number} min
     * @param {number} max
     * @return {number} The value clamped between min and max inclusive
     */
    Common.clamp = function(value, min, max) {
        if (value < min)
            return min;
        if (value > max)
            return max;
        return value;
    };

    /**
     * Description
     * @method sign
     * @param {number} value
     * @return {number} -1 if negative, +1 if 0 or positive
     */
    Common.sign = function(value) {
        return value < 0 ? -1 : 1;
    };

    /**
     * Description
     * @method now
     * @return {number} the current timestamp (high-res if avaliable)
     */
    Common.now = function() {
        // http://stackoverflow.com/questions/221294/how-do-you-get-a-timestamp-in-javascript
        // https://gist.github.com/davidwaterston/2982531

        var perf = window.performance;

        if (perf) {
            perf.now = perf.now || perf.webkitNow || perf.msNow || perf.oNow || perf.mozNow;
            return +(perf.now());
        }

        return +(new Date());
    };

    /**
     * Description
     * @method random
     * @param {number} min
     * @param {number} max
     * @return {number} A random number between min and max inclusive
     */
    Common.random = function(min, max) {
        return min + Math.random() * (max - min);
    };

    /**
     * Converts a CSS hex colour string into an integer
     * @method colorToNumber
     * @param {string} colorString
     * @return {number} An integer representing the CSS hex string
     */
    Common.colorToNumber = function(colorString) {
        colorString = colorString.replace('#','');

        if (colorString.length == 3) {
            colorString = colorString.charAt(0) + colorString.charAt(0)
                        + colorString.charAt(1) + colorString.charAt(1)
                        + colorString.charAt(2) + colorString.charAt(2);
        }

        return parseInt(colorString, 16);
    };

    /**
     * A wrapper for console.log, for providing errors and warnings
     * @method log
     * @param {string} message
     * @param {string} type
     */
    Common.log = function(message, type) {
        if (!console || !console.log)
            return;

        var style;

        switch (type) {

        case 'warn':
            style = 'color: coral';
            break;
        case 'error':
            style = 'color: red';
            break;

        }

        console.log('%c [Matter] ' + type + ': ' + message, style);
    };

})();<|endoftext|>


<commit_before>/**
 *    _____ __
 *   / ___// /_  _____
 *   \__ \/ / / / / _ \
 *  ___/ / / /_/ /  __/
 * /____/_/\__, /\___/
 *       /____/
 *       Copyright 2017 Slye Development Team. All Rights Reserved.
 *       Licence: MIT License
 */

const fs        = require('fs');
const path = require('path');
const EsyError  = require('../libs/errors/esy_error');

global.configs  = global.configs    || {
		file    : 'esy.json',
		configs : {},
		defaults: {},
		run: {}
	};

/**
 * Load configs from given file
 * @param filename
 */
function load(filename = 'esy.json'){
	var e, file;
	if(fs.existsSync(filename)){
		if(!fs.lstatSync(filename).isFile()){
			e   = new EsyError('It seems config src <'+filename+'> is not a file, maybe it\'s a directory or something else.');
			throw e;
		}
		file    = fs.readFileSync(filename);
		try {
			global.configs.configs  = JSON.parse(file);
		} catch (e){
			e   = new EsyError('Config file is not a valid JSON string.');
			throw e;
		}
	}else{
		fs.writeFileSync(filename, JSON.stringify({}, null, 4))
	}
	global.configs.file = path.join(process.cwd(), filename);
}

/**
 * Set object's element to an special value using element's path
 * @param data
 * @param name
 * @param value
 */
function setByPath(data, name, value){
	var path    = name.split('.');
	var d       = data;
	for(var i   = 0;i < path.length - 1;i++){
		if(typeof d[path[i]] !== 'object'){
			if(typeof d[path[i]] == 'undefined')
				d[path[i]]  = {};
			else
				d[path[i]]  = {__val: d[path[i]]}
		}
		d   = d[path[i]];
	}
	if(typeof d[path[path.length - 1]] == 'object'){
		d[path[path.length - 1]].__val  = value;
	}else {
		d[path[path.length - 1]]    = value;
	}
}

/**
 * Set a config's element to an special value
 * @param name
 * @param value
 * @param save
 */
function set(name, value, save = true) {
	if (!save)
		return run(name, value);
	setByPath(global.configs.configs, name, value);
	// Save change instantly
	if (global.configs.file)
		fs.writeFile(global.configs.file, JSON.stringify(global.configs.configs, null, 4))
}

/**
 * Return value from an object
 * @param data
 * @param name
 * @returns {*}
 */
function getByPath(data, name){
	var path    = name.split('.');
	var d       = data;
	for(var i   = 0;i < path.length - 1;i++){
		if(typeof d[path[i]] !== 'object'){
			return undefined;
		}
		d   = d[path[i]];
	}
	if(typeof d[path[path.length - 1]] == 'object') {
		if(d[path[path.length - 1]].__val){
			return d[path[path.length - 1]].__val;
		}else {
			return d[path[path.length - 1]];
		}
	}else {
		if(d[path[path.length - 1]] == undefined){
			return undefined;
		}else {
			return d[path[path.length - 1]];
		}
	}
}

/**
 * Returns value from configs if value does not exists the default value will return.
 * @param name
 * @returns {*}
 */
function get(name) {
	var run = getByPath(global.configs.run, name);
	if (run === undefined) {
		var re = getByPath(global.configs.configs, name);
		var def = getByPath(global.configs.defaults, name);
		if (re === undefined)
			return def;
		if (typeof re == 'object' && typeof def == 'object') {
			var keys = Object.keys(def);
			for (var key of keys) {
				if (!re.hasOwnProperty(key))
					re[key] = def[key]
			}
		}
		return re;
	}
	return run;
}

/**
 * Set default config value
 * @param name
 * @param value
 */
function def(name, value){
	setByPath(global.configs.defaults, name, value);
}

/**
 * Set a config value but don't save it to config's file, (just in run time)
 * @param name
 * @param value
 */
function run(name, value) {
	setByPath(global.configs.run, name, value);
}

module.exports  = {
	load: load,
	set : set,
	get : get,
	def: def,
	run: run,
	file: () => global.configs.file
};<commit_msg>Skip error `Config file is not a valid JSON string`<commit_after>/**
 *    _____ __
 *   / ___// /_  _____
 *   \__ \/ / / / / _ \
 *  ___/ / / /_/ /  __/
 * /____/_/\__, /\___/
 *       /____/
 *       Copyright 2017 Slye Development Team. All Rights Reserved.
 *       Licence: MIT License
 */

const fs        = require('fs');
const path = require('path');
const EsyError  = require('../libs/errors/esy_error');

global.configs  = global.configs    || {
		file    : 'esy.json',
		configs : {},
		defaults: {},
		run: {}
	};

/**
 * Load configs from given file
 * @param filename
 */
function load(filename = 'esy.json'){
	var e, file;
	if(fs.existsSync(filename)){
		if(!fs.lstatSync(filename).isFile()){
			e   = new EsyError('It seems config src <'+filename+'> is not a file, maybe it\'s a directory or something else.');
			throw e;
		}
		file    = fs.readFileSync(filename);
		try {
			global.configs.configs  = JSON.parse(file);
		} catch (e){
			// e   = new EsyError('Config file is not a valid JSON string.');
			// throw e;
			fs.writeFileSync(filename, JSON.stringify({}, null, 4));
		}
	}else{
		fs.writeFileSync(filename, JSON.stringify({}, null, 4))
	}
	global.configs.file = path.join(process.cwd(), filename);
}

/**
 * Set object's element to an special value using element's path
 * @param data
 * @param name
 * @param value
 */
function setByPath(data, name, value){
	var path    = name.split('.');
	var d       = data;
	for(var i   = 0;i < path.length - 1;i++){
		if(typeof d[path[i]] !== 'object'){
			if(typeof d[path[i]] == 'undefined')
				d[path[i]]  = {};
			else
				d[path[i]]  = {__val: d[path[i]]}
		}
		d   = d[path[i]];
	}
	if(typeof d[path[path.length - 1]] == 'object'){
		d[path[path.length - 1]].__val  = value;
	}else {
		d[path[path.length - 1]]    = value;
	}
}

/**
 * Set a config's element to an special value
 * @param name
 * @param value
 * @param save
 */
function set(name, value, save = true) {
	if (!save)
		return run(name, value);
	setByPath(global.configs.configs, name, value);
	// Save change instantly
	if (global.configs.file)
		fs.writeFile(global.configs.file, JSON.stringify(global.configs.configs, null, 4))
}

/**
 * Return value from an object
 * @param data
 * @param name
 * @returns {*}
 */
function getByPath(data, name){
	var path    = name.split('.');
	var d       = data;
	for(var i   = 0;i < path.length - 1;i++){
		if(typeof d[path[i]] !== 'object'){
			return undefined;
		}
		d   = d[path[i]];
	}
	if(typeof d[path[path.length - 1]] == 'object') {
		if(d[path[path.length - 1]].__val){
			return d[path[path.length - 1]].__val;
		}else {
			return d[path[path.length - 1]];
		}
	}else {
		if(d[path[path.length - 1]] == undefined){
			return undefined;
		}else {
			return d[path[path.length - 1]];
		}
	}
}

/**
 * Returns value from configs if value does not exists the default value will return.
 * @param name
 * @returns {*}
 */
function get(name) {
	var run = getByPath(global.configs.run, name);
	if (run === undefined) {
		var re = getByPath(global.configs.configs, name);
		var def = getByPath(global.configs.defaults, name);
		if (re === undefined)
			return def;
		if (typeof re == 'object' && typeof def == 'object') {
			var keys = Object.keys(def);
			for (var key of keys) {
				if (!re.hasOwnProperty(key))
					re[key] = def[key]
			}
		}
		return re;
	}
	return run;
}

/**
 * Set default config value
 * @param name
 * @param value
 */
function def(name, value){
	setByPath(global.configs.defaults, name, value);
}

/**
 * Set a config value but don't save it to config's file, (just in run time)
 * @param name
 * @param value
 */
function run(name, value) {
	setByPath(global.configs.run, name, value);
}

module.exports  = {
	load: load,
	set : set,
	get : get,
	def: def,
	run: run,
	file: () => global.configs.file
};<|endoftext|>


<commit_before>EZ3.Engine = function(canvas, options) {
  this.canvas = canvas || document.createElement('canvas');
  this.canvas.width = canvas.width || 800;
  this.canvas.height = canvas.height || 600;
  try {
    this.gl = canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options);
  } catch(e) {
    throw new Error('WebGl not supported');
  }
  this.resize();
};
<commit_msg>Update engine.js<commit_after>EZ3.Engine = function(canvas, options) {
  this.canvas = canvas || document.createElement('canvas');
  this.canvas.width = canvas.width || 800;
  this.canvas.height = canvas.height || 600;
  try {
    this.gl = canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options);
  } catch(e) {
    throw new Error('WebGl not supported');
  }
  this.setViewport(0, 0, this.canvas.width, this.canvas.height);
};

EZ3.Engine.prototype.setViewport = function(x, y, width, height) {
  this.gl.viewport(x, y, width, height);
};
<|endoftext|>