<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env webextensions, browser */
/* global configInit:false, defaultConfig:false, log:false, ts:false, valid:false */
'use strict';

{
ts.options = Date.now();

const l = (msg, ...args) => browser.i18n.getMessage(msg, args) || `\u25BA${msg}\u25C4`;
const config = {};
const nodes = {};

/**
 * Reports an error to the user.
 *
 * @param err Error The original error object.
 * @param msg string Localisation lookup key.
 */
function userError(err, msg) {
    log(err.toString() + '\n' + err.stack);
    msg; // TODO: use this to present a localised message in the page
}

/**
 * Destructively write a new configuration. The existing configuration will be completely wiped first.
 *
 * @param newConfig Object The new configuration.
 */
function replaceConfig(newConfig) {
    browser.storage.local.clear().then(() => browser.storage.local.set(newConfig)).catch(e => userError(e, 'error.misc'));
}

/**
 * Update preferences associated with the current grid selection to the given value.
 *
 * @param grid HTMLElement Reference to the root node of the grid.
 * @param value string|falsy The value to set, or falsy to delete the existing value.
 */
function bulkModifyConfig(grid, value) {
    const type = grid.dataset.type;
    const prefChanges = {};
    let changed = false;
    for(const selected of grid.querySelectorAll('label[data-pref][data-key] input[type=checkbox]:not([id]):checked')) {
        const row = selected.closest('label[data-pref][data-key]');
        const pref = row.dataset.pref;
        const key = row.dataset.key;
        if(!(pref in valid.schema)
            || (value && key in config[pref] && config[pref][key] === value)
            || (value && !valid.entry(type, value)))
            continue;
        if(!(pref in prefChanges))
            prefChanges[pref] = Object.assign({}, config[pref]);
        if(value)
            prefChanges[pref][key] = value;
        else
            delete prefChanges[pref][key];
        changed = true;
    }
    if(changed)
        browser.storage.local.set(prefChanges);
}

const events = {
    click: {
        /**
         * Exports the current configuration to a file the user can download.
         */
        '#btn-export': () => {
            const blob = new Blob([JSON.stringify(config, null, '\t')], {type: 'application/json', endings: 'native'});
            const url = URL.createObjectURL(blob);
            const a = document.getElementById('link-export');
            a.href = url;
            a.click();
            URL.revokeObjectURL(url);
        },

        /**
         * Triggers a file upload dialog where the user can select a configuration file to load.
         */
        '#btn-import': () => document.getElementById('file-import').click(),

        /**
         * Immediately restores the fresh-install default configuration.
         */
        '#btn-reset': () => replaceConfig(defaultConfig),

        /**
         * Immediately adds the new entry to the configuration.
         * Detects the appropriate preference by matching against the user's input.
         */
        '.grid[data-type] button[name=add]': node => {
            const row = node.closest('footer > div');
            if(!row)
                return;
            const grid = row.closest('.grid[data-type]');
            const type = grid.dataset.type;
            const kNode = row.querySelector('input[data-input=key][data-name=add]');
            const vNode = row.querySelector('[data-input=value][data-name=add] input[type=radio]:checked');
            if(!(kNode && vNode))
                return;
            const key = kNode.value;
            const value = valid.expect[type] && valid.expect[type].choice && valid.expect[type].choice[vNode.value];
            if(!(key && value))
                return;
            const entry = {[key]: value}
            for(const pref in valid.schema) {
                const schema = valid.schema[pref];
                if(typeof schema !== 'object' || schema.v !== type || !schema.k
                    || (typeof config[pref] === 'object' && key in config[pref] && config[pref][key] === value)
                    || !valid.entry(schema, entry))
                    continue;
                browser.storage.local.set({[pref]: Object.assign({}, config[pref], entry)});
                kNode.value = '';
                const defaultChoice = valid.expect[type] && valid.expect[type].choice && valid.expect[type].choice.length;
                if(defaultChoice) {
                    const defaultRadio = row.querySelector('[data-input=value][data-name=add] input[type=radio]' + (defaultChoice ? '[value="0"]' : ':checked'));
                    if(defaultRadio)
                        defaultRadio.checked = defaultChoice;
                }
            }
        },

        /**
         * Immediately removes the currently selected preference(s) from the configuration.
         */
        '.grid[data-type] button[name=remove]': node => {
            bulkModifyConfig(node.closest('.grid[data-type]'), null);
        }
    },
    keyup: {
        'input[type=text][data-name]': (node, ev) => {
            if(!ev || ev.key != 'Enter')
                return;
            const row = node.closest('footer > div');
            if(!row)
                return;
            const btn = row.querySelector('button[name=' + node.dataset.name + ']');
            if(btn)
                btn.click();
        }
    },
    change: {
        /**
         * Immediately updates the relevant boolean preference with the value of the given checkbox.
         */
        'input[type=checkbox][id]': node => {
            const pref = node.id;
            if(pref in config && typeof config[pref] === 'boolean')
                browser.storage.local.set({[pref]: node.checked});
        },

        /**
         * Updates parts of grid UI triggered by checkboxes.
         */
        '.grid input[type=checkbox]:not([id])': node => {
            const row = node.closest('.grid header, .grid label');
            if(!row)
                return;
            const grid = row.closest('.grid');
            const pref = row.nodeName == 'HEADER' ? row.id : row.dataset.pref;
            const selector = 'label[data-pref=' + pref + '] input[type=checkbox]:not([id])';
            switch(row.nodeName) {
                case 'HEADER': {
                    for(const box of grid.querySelectorAll(selector + (node.checked ? ':not(:checked)' : ':checked'))) {
                        box.checked = node.checked;
                    }
                    break;
                }
                case 'LABEL': {
                    const selectAll = nodes[pref].querySelector('.select-all input[type=checkbox]:not([id])');
                    selectAll.checked = grid.querySelector(selector + ':checked');
                    selectAll.indeterminate = selectAll.checked && grid.querySelector(selector + ':not(:checked)');
                    break;
                }
            }
            const footer = grid.querySelector('footer');
            if(!footer)
                return;
            const boxes = grid.querySelectorAll('label input[type=checkbox]:not([id]):checked');
            footer.dataset.activerow = boxes.length ? 'edit' : 'add';
            const sel = footer.querySelector('div [data-name=selection]');
            if(sel)
                sel.textContent = boxes.length ? l('action.selection.label', boxes.length) : '';
            for(const radio of footer.querySelectorAll('div [data-input=value][data-name=edit] input[type=radio]')) {
                radio.checked = false; // reset edit radios every time selection changes
            }
        },

        /**
         * Immediately updates the currently selected preference(s) with the value of this radio button.
         */
        '.grid[data-type] [data-input=value][data-name=edit] input[type=radio]:checked': node => {
            bulkModifyConfig(node.closest('.grid[data-type]'), node.value);
        },

        /**
         * Imports a new configuration from the file the user just uploaded.
         */
        '#file-import': node => {
            const reader = new FileReader();
            reader.onload = ev => {
                try {
                    const newConfig = JSON.parse(ev.target.result);
                    valid.config(newConfig);
                    replaceConfig(newConfig);
                } catch(e) {
                    userError(e, 'error.invalid.import');
                }
            };
            reader.readAsText(node.files[0]);
        }
    }
};

/**
 * Delegates UI events to the relevant handler by matching the target with a selector.
 *
 * @param ev Event The event object.
 */
function onUIEvent(ev) {
    const handlers = events[ev.type];
    if(!handlers)
        return; // should never happen?
    const node = ev.target;
    if(node.id) {
        const handler = handlers['#' + node.id];
        if(handler) // avoids loop
            return handler(node, ev);
    }
    for(const selector in handlers) {
        if(selector[0] !== '#' && node.matches(selector))
            return handlers[selector](node, ev);
    }
}

/**
 * Update UI when user preferences change.
 *
 * @param changes storage.StorageChange An object representing a change to a storage area.
 * @param areaName storage.StorageArea An object representing a storage area.
 */
function onPrefChanged(changes, areaName) {
    if(areaName != 'local')
        return;

    for(const pref in changes) {
        const change = changes[pref];

        if(!(pref in valid.schema)) {
            log(`Unknown ${pref} preference change ignored.`);
            continue;
        }
        const schema = valid.schema[pref];

        if(!valid.entry(schema, change.newValue)) {
            log(`Invalid ${pref} preference change ignored.`);
            continue;
        }
        config[pref] = change.newValue;

        const node = nodes[pref];
        if(node) {
            switch(typeof schema) {
                case 'boolean': {
                    if(node.nodeName == 'INPUT' && node.type == 'checkbox') {
                        node.checked = change.newValue;
                        continue;
                    }
                    break;
                }
                case 'object': {
                    if(node.nodeName == 'HEADER' && nodes['tpl-grid-head-' + schema.k]) {
                        const frag = document.createDocumentFragment();
                        const tpl = nodes['tpl-grid-row'];
                        for(const k in change.newValue) {
                            const row = tpl.cloneNode(true);
                            const label = row.querySelector('label');
                            label.dataset.pref = pref;
                            label.dataset.key = k;
                            label.children[1].textContent = k;
                            label.children[2].textContent = valid.expect[schema.v].choice && !valid.expect[schema.v].obj ? change.newValue[k] : JSON.stringify(change.newValue[k]);
                            frag.appendChild(row);
                        }
                        const selectAll = node.querySelector('.select-all input[type=checkbox]:not([id])');
                        if(selectAll && selectAll.checked)
                            selectAll.click(); // deselect all to restore correct footer
                        for(const n of node.parentNode.querySelectorAll('label[data-pref=' + pref + ']')) {
                            n.remove();
                        }
                        node.classList.toggle('empty', !frag.childElementCount);
                        node.after(frag);
                        continue;
                    }
                    break;
                }
            }
        }
        log(`Unable to display ${typeof change.newValue} preference "${pref}" in ${node ? node.nodeName : 'null'} node.`);
    }
}

/**
 * Translate the contents of textual nodes and attributes below a given root.
 *
 * @param root HTMLElement The ancestor node of all translatable nodes.
 */
function localise(root) {
    for(const node of root.querySelectorAll('input,button,label,summary,aside,span,p,a,th')) {
        const props = ['title', 'placeholder'];
        if(!node.childElementCount)
            props.push('textContent');
        for(const prop of props) {
            if(node[prop])
                node[prop] = l(node[prop]);
        }
    }
}

/**
 * Insert templated but static parts of grids.
 */
function generateTemplatedUI() {
    for(const grid of document.querySelectorAll('.grid[data-type]')) {
        const type = grid.dataset.type;
        const gridFrag = nodes['tpl-grid'].cloneNode(true);
        const main = gridFrag.querySelector('main');
        const inputAddMatch = gridFrag.querySelector('footer input[data-input=key][data-name=add]');
        inputAddMatch.placeholder = l('placeholder.match.' + type);

        for(const pref in valid.schema) {
            const schema = valid.schema[pref];
            if(nodes[pref] || typeof schema !== 'object' || schema.v !== type || !schema.k)
                continue;
            if(valid.expect[schema.k]) {
                if(valid.expect[schema.k].regex)
                    inputAddMatch.pattern = (inputAddMatch.pattern ? inputAddMatch.pattern + '|' : '') + valid.expect[schema.k].regex.source;
                if(valid.expect[schema.k].maxLen)
                    inputAddMatch.maxLength = Math.max(inputAddMatch.maxLength || 0, valid.expect[schema.k].maxLen);
            }
            const frag = nodes['tpl-grid-head'].cloneNode(true);
            const header = frag.querySelector('header');
            header.id = pref;
            for(const col of [schema.k, schema.v]) {
                if(nodes['tpl-grid-head-' + col])
                    header.appendChild(nodes['tpl-grid-head-' + col].cloneNode(true));
            }
            main.appendChild(frag);
            nodes[pref] = header;
        }

        if(valid.expect[type].choice) {
            const inputsFrag = document.createDocumentFragment();
            for(let [k, choice] of valid.expect[type].choice.entries()) {
                if(valid.expect[type].obj)
                    choice = JSON.stringify(choice);
                const radio = document.createElement('input');
                const label = document.createElement('label');
                radio.type = 'radio'; // using radio because select was very buggy in nightly58's inline options, revisit this when more than 2 choices wastes too much screen space
                radio.value = k;
                label.textContent = choice;
                label.prepend(radio);
                inputsFrag.appendChild(label);
            }
            for(const parent of gridFrag.querySelectorAll('[data-input=value][data-name]')) {
                const name = parent.dataset.name + '-' + type;
                const checkedValue = parent.dataset.name == 'add' ? '0' : null;
                const inputs = inputsFrag.cloneNode(true);
                for(const radio of inputs.querySelectorAll('input[type=radio]')) {
                    radio.name = name;
                    radio.checked = radio.value === checkedValue;
                }
                parent.classList.add('radio');
                parent.appendChild(inputs);
            }
        }
        grid.appendChild(gridFrag);
    }
}

/**
 * Promise that resolves once the DOM is ready.
 */
const ready = new Promise((resolve) => {
    if(document.readyState !== 'loading')
        resolve();
    else
        document.addEventListener('readystatechange', resolve, {capture: true, once: true, passive: true});
});

ready.then(() => { // l10n and event delegation (neither depend on config)
    localise(document);

    for(const node of document.querySelectorAll('template[id]')) {
        nodes[node.id] = node.content;
        localise(node.content);
    }

    generateTemplatedUI();

    for(const ev in events) {
        document.body.addEventListener(ev, onUIEvent, false);
    }
});

Promise.all([configInit, ready]).then(([initialConfig]) => {
    log(initialConfig);
    for(const pref in initialConfig) {
        nodes[pref] = document.getElementById(pref);
        initialConfig[pref] = {newValue: initialConfig[pref]};
    }

    browser.storage.onChanged.addListener(onPrefChanged);
    onPrefChanged(initialConfig, 'local');

    log('options.js started', Date.now() - ts.options);
});
}
<commit_msg>Fix editing an existing preference<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env webextensions, browser */
/* global configInit:false, defaultConfig:false, log:false, ts:false, valid:false */
'use strict';

{
ts.options = Date.now();

const l = (msg, ...args) => browser.i18n.getMessage(msg, args) || `\u25BA${msg}\u25C4`;
const config = {};
const nodes = {};

/**
 * Reports an error to the user.
 *
 * @param err Error The original error object.
 * @param msg string Localisation lookup key.
 */
function userError(err, msg) {
    log(err.toString() + '\n' + err.stack);
    msg; // TODO: use this to present a localised message in the page
}

/**
 * Destructively write a new configuration. The existing configuration will be completely wiped first.
 *
 * @param newConfig Object The new configuration.
 */
function replaceConfig(newConfig) {
    browser.storage.local.clear().then(() => browser.storage.local.set(newConfig)).catch(e => userError(e, 'error.misc'));
}

/**
 * Update preferences associated with the current grid selection to the given value.
 *
 * @param grid HTMLElement Reference to the root node of the grid.
 * @param value string|falsy The value to set, or falsy to delete the existing value.
 */
function bulkModifyConfig(grid, value) {
    const type = grid.dataset.type;
    if(valid.expect[type] && valid.expect[type].choice && value in valid.expect[type].choice)
        value = valid.expect[type].choice[value];
    const prefChanges = {};
    let changed = false;
    for(const selected of grid.querySelectorAll('label[data-pref][data-key] input[type=checkbox]:not([id]):checked')) {
        const row = selected.closest('label[data-pref][data-key]');
        const pref = row.dataset.pref;
        const key = row.dataset.key;
        if(!(pref in valid.schema)
            || (value && key in config[pref] && config[pref][key] === value)
            || (value && !valid.entry(type, value)))
            continue;
        if(!(pref in prefChanges))
            prefChanges[pref] = Object.assign({}, config[pref]);
        if(value)
            prefChanges[pref][key] = value;
        else
            delete prefChanges[pref][key];
        changed = true;
    }
    if(changed)
        browser.storage.local.set(prefChanges);
}

const events = {
    click: {
        /**
         * Exports the current configuration to a file the user can download.
         */
        '#btn-export': () => {
            const blob = new Blob([JSON.stringify(config, null, '\t')], {type: 'application/json', endings: 'native'});
            const url = URL.createObjectURL(blob);
            const a = document.getElementById('link-export');
            a.href = url;
            a.click();
            URL.revokeObjectURL(url);
        },

        /**
         * Triggers a file upload dialog where the user can select a configuration file to load.
         */
        '#btn-import': () => document.getElementById('file-import').click(),

        /**
         * Immediately restores the fresh-install default configuration.
         */
        '#btn-reset': () => replaceConfig(defaultConfig),

        /**
         * Immediately adds the new entry to the configuration.
         * Detects the appropriate preference by matching against the user's input.
         */
        '.grid[data-type] button[name=add]': node => {
            const row = node.closest('footer > div');
            if(!row)
                return;
            const grid = row.closest('.grid[data-type]');
            const type = grid.dataset.type;
            const kNode = row.querySelector('input[data-input=key][data-name=add]');
            const vNode = row.querySelector('[data-input=value][data-name=add] input[type=radio]:checked');
            if(!(kNode && vNode))
                return;
            const key = kNode.value;
            const value = valid.expect[type] && valid.expect[type].choice && valid.expect[type].choice[vNode.value];
            if(!(key && value))
                return;
            const entry = {[key]: value}
            for(const pref in valid.schema) {
                const schema = valid.schema[pref];
                if(typeof schema !== 'object' || schema.v !== type || !schema.k
                    || (typeof config[pref] === 'object' && key in config[pref] && config[pref][key] === value)
                    || !valid.entry(schema, entry))
                    continue;
                browser.storage.local.set({[pref]: Object.assign({}, config[pref], entry)});
                kNode.value = '';
                const defaultChoice = valid.expect[type] && valid.expect[type].choice && valid.expect[type].choice.length;
                if(defaultChoice) {
                    const defaultRadio = row.querySelector('[data-input=value][data-name=add] input[type=radio]' + (defaultChoice ? '[value="0"]' : ':checked'));
                    if(defaultRadio)
                        defaultRadio.checked = defaultChoice;
                }
            }
        },

        /**
         * Immediately removes the currently selected preference(s) from the configuration.
         */
        '.grid[data-type] button[name=remove]': node => {
            bulkModifyConfig(node.closest('.grid[data-type]'), null);
        }
    },
    keyup: {
        'input[type=text][data-name]': (node, ev) => {
            if(!ev || ev.key != 'Enter')
                return;
            const row = node.closest('footer > div');
            if(!row)
                return;
            const btn = row.querySelector('button[name=' + node.dataset.name + ']');
            if(btn)
                btn.click();
        }
    },
    change: {
        /**
         * Immediately updates the relevant boolean preference with the value of the given checkbox.
         */
        'input[type=checkbox][id]': node => {
            const pref = node.id;
            if(pref in config && typeof config[pref] === 'boolean')
                browser.storage.local.set({[pref]: node.checked});
        },

        /**
         * Updates parts of grid UI triggered by checkboxes.
         */
        '.grid input[type=checkbox]:not([id])': node => {
            const row = node.closest('.grid header, .grid label');
            if(!row)
                return;
            const grid = row.closest('.grid');
            const pref = row.nodeName == 'HEADER' ? row.id : row.dataset.pref;
            const selector = 'label[data-pref=' + pref + '] input[type=checkbox]:not([id])';
            switch(row.nodeName) {
                case 'HEADER': {
                    for(const box of grid.querySelectorAll(selector + (node.checked ? ':not(:checked)' : ':checked'))) {
                        box.checked = node.checked;
                    }
                    break;
                }
                case 'LABEL': {
                    const selectAll = nodes[pref].querySelector('.select-all input[type=checkbox]:not([id])');
                    selectAll.checked = grid.querySelector(selector + ':checked');
                    selectAll.indeterminate = selectAll.checked && grid.querySelector(selector + ':not(:checked)');
                    break;
                }
            }
            const footer = grid.querySelector('footer');
            if(!footer)
                return;
            const boxes = grid.querySelectorAll('label input[type=checkbox]:not([id]):checked');
            footer.dataset.activerow = boxes.length ? 'edit' : 'add';
            const sel = footer.querySelector('div [data-name=selection]');
            if(sel)
                sel.textContent = boxes.length ? l('action.selection.label', boxes.length) : '';
            for(const radio of footer.querySelectorAll('div [data-input=value][data-name=edit] input[type=radio]')) {
                radio.checked = false; // reset edit radios every time selection changes
            }
        },

        /**
         * Immediately updates the currently selected preference(s) with the value of this radio button.
         */
        '.grid[data-type] [data-input=value][data-name=edit] input[type=radio]:checked': node => {
            bulkModifyConfig(node.closest('.grid[data-type]'), node.value);
        },

        /**
         * Imports a new configuration from the file the user just uploaded.
         */
        '#file-import': node => {
            const reader = new FileReader();
            reader.onload = ev => {
                try {
                    const newConfig = JSON.parse(ev.target.result);
                    valid.config(newConfig);
                    replaceConfig(newConfig);
                } catch(e) {
                    userError(e, 'error.invalid.import');
                }
            };
            reader.readAsText(node.files[0]);
        }
    }
};

/**
 * Delegates UI events to the relevant handler by matching the target with a selector.
 *
 * @param ev Event The event object.
 */
function onUIEvent(ev) {
    const handlers = events[ev.type];
    if(!handlers)
        return; // should never happen?
    const node = ev.target;
    if(node.id) {
        const handler = handlers['#' + node.id];
        if(handler) // avoids loop
            return handler(node, ev);
    }
    for(const selector in handlers) {
        if(selector[0] !== '#' && node.matches(selector))
            return handlers[selector](node, ev);
    }
}

/**
 * Update UI when user preferences change.
 *
 * @param changes storage.StorageChange An object representing a change to a storage area.
 * @param areaName storage.StorageArea An object representing a storage area.
 */
function onPrefChanged(changes, areaName) {
    if(areaName != 'local')
        return;

    for(const pref in changes) {
        const change = changes[pref];

        if(!(pref in valid.schema)) {
            log(`Unknown ${pref} preference change ignored.`);
            continue;
        }
        const schema = valid.schema[pref];

        if(!valid.entry(schema, change.newValue)) {
            log(`Invalid ${pref} preference change ignored.`);
            continue;
        }
        config[pref] = change.newValue;

        const node = nodes[pref];
        if(node) {
            switch(typeof schema) {
                case 'boolean': {
                    if(node.nodeName == 'INPUT' && node.type == 'checkbox') {
                        node.checked = change.newValue;
                        continue;
                    }
                    break;
                }
                case 'object': {
                    if(node.nodeName == 'HEADER' && nodes['tpl-grid-head-' + schema.k]) {
                        const frag = document.createDocumentFragment();
                        const tpl = nodes['tpl-grid-row'];
                        for(const k in change.newValue) {
                            const row = tpl.cloneNode(true);
                            const label = row.querySelector('label');
                            label.dataset.pref = pref;
                            label.dataset.key = k;
                            label.children[1].textContent = k;
                            label.children[2].textContent = valid.expect[schema.v].choice && !valid.expect[schema.v].obj ? change.newValue[k] : JSON.stringify(change.newValue[k]);
                            frag.appendChild(row);
                        }
                        const selectAll = node.querySelector('.select-all input[type=checkbox]:not([id])');
                        if(selectAll && selectAll.checked)
                            selectAll.click(); // deselect all to restore correct footer
                        for(const n of node.parentNode.querySelectorAll('label[data-pref=' + pref + ']')) {
                            n.remove();
                        }
                        node.classList.toggle('empty', !frag.childElementCount);
                        node.after(frag);
                        continue;
                    }
                    break;
                }
            }
        }
        log(`Unable to display ${typeof change.newValue} preference "${pref}" in ${node ? node.nodeName : 'null'} node.`);
    }
}

/**
 * Translate the contents of textual nodes and attributes below a given root.
 *
 * @param root HTMLElement The ancestor node of all translatable nodes.
 */
function localise(root) {
    for(const node of root.querySelectorAll('input,button,label,summary,aside,span,p,a,th')) {
        const props = ['title', 'placeholder'];
        if(!node.childElementCount)
            props.push('textContent');
        for(const prop of props) {
            if(node[prop])
                node[prop] = l(node[prop]);
        }
    }
}

/**
 * Insert templated but static parts of grids.
 */
function generateTemplatedUI() {
    for(const grid of document.querySelectorAll('.grid[data-type]')) {
        const type = grid.dataset.type;
        const gridFrag = nodes['tpl-grid'].cloneNode(true);
        const main = gridFrag.querySelector('main');
        const inputAddMatch = gridFrag.querySelector('footer input[data-input=key][data-name=add]');
        inputAddMatch.placeholder = l('placeholder.match.' + type);

        for(const pref in valid.schema) {
            const schema = valid.schema[pref];
            if(nodes[pref] || typeof schema !== 'object' || schema.v !== type || !schema.k)
                continue;
            if(valid.expect[schema.k]) {
                if(valid.expect[schema.k].regex)
                    inputAddMatch.pattern = (inputAddMatch.pattern ? inputAddMatch.pattern + '|' : '') + valid.expect[schema.k].regex.source;
                if(valid.expect[schema.k].maxLen)
                    inputAddMatch.maxLength = Math.max(inputAddMatch.maxLength || 0, valid.expect[schema.k].maxLen);
            }
            const frag = nodes['tpl-grid-head'].cloneNode(true);
            const header = frag.querySelector('header');
            header.id = pref;
            for(const col of [schema.k, schema.v]) {
                if(nodes['tpl-grid-head-' + col])
                    header.appendChild(nodes['tpl-grid-head-' + col].cloneNode(true));
            }
            main.appendChild(frag);
            nodes[pref] = header;
        }

        if(valid.expect[type].choice) {
            const inputsFrag = document.createDocumentFragment();
            for(let [k, choice] of valid.expect[type].choice.entries()) {
                if(valid.expect[type].obj)
                    choice = JSON.stringify(choice);
                const radio = document.createElement('input');
                const label = document.createElement('label');
                radio.type = 'radio'; // using radio because select was very buggy in nightly58's inline options, revisit this when more than 2 choices wastes too much screen space
                radio.value = k;
                label.textContent = choice;
                label.prepend(radio);
                inputsFrag.appendChild(label);
            }
            for(const parent of gridFrag.querySelectorAll('[data-input=value][data-name]')) {
                const name = parent.dataset.name + '-' + type;
                const checkedValue = parent.dataset.name == 'add' ? '0' : null;
                const inputs = inputsFrag.cloneNode(true);
                for(const radio of inputs.querySelectorAll('input[type=radio]')) {
                    radio.name = name;
                    radio.checked = radio.value === checkedValue;
                }
                parent.classList.add('radio');
                parent.appendChild(inputs);
            }
        }
        grid.appendChild(gridFrag);
    }
}

/**
 * Promise that resolves once the DOM is ready.
 */
const ready = new Promise((resolve) => {
    if(document.readyState !== 'loading')
        resolve();
    else
        document.addEventListener('readystatechange', resolve, {capture: true, once: true, passive: true});
});

ready.then(() => { // l10n and event delegation (neither depend on config)
    localise(document);

    for(const node of document.querySelectorAll('template[id]')) {
        nodes[node.id] = node.content;
        localise(node.content);
    }

    generateTemplatedUI();

    for(const ev in events) {
        document.body.addEventListener(ev, onUIEvent, false);
    }
});

Promise.all([configInit, ready]).then(([initialConfig]) => {
    log(initialConfig);
    for(const pref in initialConfig) {
        nodes[pref] = document.getElementById(pref);
        initialConfig[pref] = {newValue: initialConfig[pref]};
    }

    browser.storage.onChanged.addListener(onPrefChanged);
    onPrefChanged(initialConfig, 'local');

    log('options.js started', Date.now() - ts.options);
});
}
<|endoftext|>


<commit_before>function Slide(node, config){
	var defaultPara = {
		mode: 'horizontal',  //滚动方向（水平或竖直）
		speed: 500,  //滚动速度
		perGroup: 1,  //显示数量
		slidePerView: 1,  //每次滚动的数量
		autoPlay: 0,  //自动滚动的时间间隔，大于0时有效
		loop: true,  //是否循环滚动
		pagination: null,  //分页器
		pageClickable: true,  //分页器是否可点击
		fullPage: false  //是否全屏滚动
	};
	$.extend(defaultPara, config);
	this.block = $(node),
	this.mode = defaultPara.mode,
	this.speed = defaultPara.speed,
	this.perGroup = defaultPara.perGroup,
	this.slidePerView = defaultPara.slidePerView,
	this.autoPlay = defaultPara.autoPlay,
	this.loop = defaultPara.loop,
	this.pagination = $(defaultPara.pagination),
	this.pageClickable = defaultPara.pageClickable,
	this.fullPage = defaultPara.fullPage;

	this.list = this.block.find('ul');
	var _li = this.list.find('li');
	this.liWidth = _li.width(),
	this.liHeight = _li.height();

	var	_length = _li.length,
	_slideLength = Math.ceil((_length - this.perGroup) / this.slidePerView) + 1,
	_timer = null,
	_slideIndex = 0,
	_canShowPagination = this.pagination && this.perGroup === 1 && this.slidePerView === 1, //是否展示分页器
	_that = this;

//初始化
var _init = function(){
		//设定轮播样式
		if(_that.fullPage){
			var body = $("body");
			_li.width(body.width());
			_li.height(body.height());
			if(_that.mode === 'horizontal'){
				_that.liWidth = _li.width();
				_that.list.width(_that.liWidth * _length);
				_that.list.addClass('slide-horizontal');
			}
			else if(_that.mode === 'vertical'){
				_that.liHeight = _li.height();
				_that.list.height(_that.liHeight * _length);
				_that.list.addClass('slide-vertical');
			}
		}
		else{
			if(_that.mode === 'horizontal'){
				_that.block.width(_that.liWidth * _that.perGroup);
				_that.list.width(_that.liWidth * _length);
				_that.list.addClass('slide-horizontal');
			}
			else if(_that.mode === 'vertical'){
				_that.block.height(_that.liHeight * _that.perGroup);
				_that.list.height(_that.liHeight * _length);
				_that.list.addClass('slide-vertical');
			}
		}

		//添加分页器
		if(_canShowPagination){
			_createPagination();
		}
		//自动播放
		if(_that.autoPlay){
			_timer = setInterval(function(){
				_that.slideNext(true);
			}, _that.autoPlay);
		}
		//绑定鼠标滚轮事件
		if(_that.fullPage){
			$(document).on("mousewheel DOMMouseScroll", _bindMouseWheel);
		}
	}

	this.slidePrev = function(){
		clearInterval(_timer);
		if(_slideIndex > 0){
			_slideIndex --;
			_slideAnimation(this.slidePerView);
		}
		else{
			if(this.loop){
				this.slideTo(_slideLength - 1);
			}
		}
	};

	this.slideNext = function(notClear){
		if(!notClear){
			clearInterval(_timer);
		}
		if(_slideIndex < _slideLength - 1){
			_slideIndex ++;
			_slideAnimation(-this.slidePerView);
			if(_slideIndex === _slideLength - 1 && !this.loop){
				clearInterval(_timer);
			}
		}
		else{
			if(this.loop){
				this.slideTo(0, notClear);
			}
		}
	};

	this.slideTo = function(num, notClear){
		if(!notClear){
			clearInterval(_timer);
		}
		var _delta = num - _slideIndex;
		_slideIndex = num;
		_slideAnimation(-_delta * _that.slidePerView);
	}

//初始化分页
var _createPagination = function(){
	var pageHtml = '';
	for(var i = 0; i < _length; i ++){
		pageHtml += '';
	}
	_that.pagination.append(pageHtml);
	_that.pagination.find('a').eq(0).addClass('on');
	//绑定分页器事件
	if(_that.pageClickable){
		_pageBind();
	}
}

//分页器变换
var _paginationChange = function(){
	_that.pagination.find('a').eq(_slideIndex)._onlyClass('on');
}

//绑定分页器事件
var _pageBind = function(){
	_that.pagination.find('a').on('click', function(){
		var dotIndex = $(this).index();
		_that.slideTo(dotIndex);
	});
}

//绑定鼠标滚轮事件
var _bindMouseWheel = function(e){
	e.preventDefault();
	var value = e.originalEvent.wheelDelta || -e.originalEvent.detail;
	value > 0 ?
	_that.slidePrev():
	_that.slideNext();
	return false;
}

//执行滚动
var _slideAnimation = function(num){
	if(_that.mode === 'horizontal'){
		_that.list.animate({left: '+=' + num * _that.liWidth + 'px'}, _that.speed);
	}
	else if(_that.mode === 'vertical'){
		_that.list.animate({top: '+=' + num * _that.liHeight + 'px'}, _that.speed);
	}
	if(_canShowPagination){
		_paginationChange();
	}
}

$.fn._onlyClass = function(obj){
	$(this).addClass(obj).siblings().removeClass(obj);
	return $(this);
}

_init();
}
<commit_msg>优化代码，修复全屏滚动的bug。<commit_after>function Slide(node, config){
	var defaultPara = {
		mode: 'horizontal',  //滚动方向（水平或竖直）
		speed: 500,  //滚动速度
		perGroup: 1,  //显示数量
		slidePerView: 1,  //每次滚动的数量
		autoPlay: 0,  //自动滚动的时间间隔，大于0时有效
		loop: true,  //是否循环滚动
		pagination: null,  //分页器
		pageClickable: true,  //分页器是否可点击
		fullPage: false  //是否全屏滚动
	};
	$.extend(defaultPara, config);
	this.block = $(node),
	this.mode = defaultPara.mode,
	this.speed = defaultPara.speed,
	this.perGroup = defaultPara.perGroup,
	this.slidePerView = defaultPara.slidePerView,
	this.autoPlay = defaultPara.autoPlay,
	this.loop = defaultPara.loop,
	this.pagination = $(defaultPara.pagination),
	this.pageClickable = defaultPara.pageClickable,
	this.fullPage = defaultPara.fullPage;

	this.list = this.block.find('ul');
	var _li = this.list.find('li');
	this.liWidth = _li.width(),
	this.liHeight = _li.height();

	var	_length = _li.length,
	_slideLength = Math.ceil((_length - this.perGroup) / this.slidePerView) + 1,
	_timer = null,
	_slideIndex = 0,
	_pageDot = null,
	_canShowPagination = this.pagination && this.perGroup === 1 && this.slidePerView === 1, //是否展示分页器
	_that = this;

//初始化
var _init = function(){
		//初始化轮播样式
		_setStyle();

		//添加分页器
		if(_canShowPagination){
			_createPagination();
		}
		//自动播放
		if(_that.autoPlay){
			_timer = setInterval(function(){
				_that.slideNext(true);
			}, _that.autoPlay);
		}
		//绑定鼠标滚轮事件
		if(_that.fullPage){
			$(document).on("mousewheel DOMMouseScroll", _bindMouseWheel);
		}
	}

	this.slidePrev = function(){
		clearInterval(_timer);
		if(_slideIndex > 0){
			_slideIndex --;
			_slideAnimation(this.slidePerView);
		}
		else{
			if(this.loop){
				this.slideTo(_slideLength - 1);
			}
		}
	};

	this.slideNext = function(notClear){
		if(!notClear){
			clearInterval(_timer);
		}
		if(_slideIndex < _slideLength - 1){
			_slideIndex ++;
			_slideAnimation(-this.slidePerView);
			if(_slideIndex === _slideLength - 1 && !this.loop){
				clearInterval(_timer);
			}
		}
		else{
			if(this.loop){
				this.slideTo(0, notClear);
			}
		}
	};

	this.slideTo = function(num, notClear){
		if(!notClear){
			clearInterval(_timer);
		}
		var _delta = num - _slideIndex;
		_slideIndex = num;
		_slideAnimation(-_delta * _that.slidePerView);
	}

	//初始化样式
	var _setStyle = function(){
		var body = $("body");
		if(_that.mode === 'horizontal'){
			if(_that.fullPage){
				_li.width(body.width());
				_li.height(body.height());
				_that.liWidth = _li.width();
			}
			else{
				_that.block.width(_that.liWidth * _that.perGroup);
			}
			_that.list.width(_that.liWidth * _length);
			_that.list.addClass('slide-horizontal');
		}
		else if(_that.mode === 'vertical'){
			if(_that.fullPage){
				_li.width(body.width());
				_li.height(body.height());
				_that.liHeight = _li.height();
			}
			else{
				_that.block.height(_that.liHeight * _that.perGroup);
			}
			_that.list.height(_that.liHeight * _length);
			_that.list.addClass('slide-vertical');
		}
	};

//初始化分页
var _createPagination = function(){
	var pageHtml = '';
	for(var i = 0; i < _length; i ++){
		pageHtml += '';
	}
	_that.pagination.append(pageHtml);
	_pageDot = _that.pagination.find('a');
	_pageDot.eq(0).addClass('on');
	//绑定分页器事件
	if(_that.pageClickable){
		_pageBind();
	}
}

//分页器变换
var _paginationChange = function(){
	_pageDot.eq(_slideIndex)._onlyClass('on');
}

//绑定分页器事件
var _pageBind = function(){
	_pageDot.on('click', function(){
		var dotIndex = $(this).index();
		_that.slideTo(dotIndex);
	});
}

//绑定鼠标滚轮事件
var _bindMouseWheel = function(e){
	e.preventDefault();
	var value = e.originalEvent.wheelDelta || -e.originalEvent.detail;
	value > 0 ?
	_that.slidePrev():
	_that.slideNext();
	return false;
}

//执行滚动
var _slideAnimation = function(num){
	if(_that.mode === 'horizontal'){
		_that.list.animate({left: '+=' + num * _that.liWidth + 'px'}, _that.speed);
	}
	else if(_that.mode === 'vertical'){
		_that.list.animate({top: '+=' + num * _that.liHeight + 'px'}, _that.speed);
	}
	if(_canShowPagination){
		_paginationChange();
	}
}

$.fn._onlyClass = function(obj){
	$(this).addClass(obj).siblings().removeClass(obj);
	return $(this);
}

_init();
}
<|endoftext|>


<commit_before>/*!
 * pio-preview - easy Print.io product previews
 * http://github.com/fromkeith/pio-preview
 * (c) 2015 MIT License
 */
!function(p,e){"undefined"!=typeof module&&module.exports?module.exports=e(require("angular")):"function"==typeof define&&define.amd?define(["angular"],e):e(p.angular)}(this,function(p){"use strict";return p.module("pio-preview",[]).directive("ppMousepad",function(){return{scope:{ppSrc:"@"},restrict:"E",template:'

'}}).directive("ppFramed",function(){return{scope:{ppSrc:"@"},restrict:"E",template:'

'}}).directive("ppMug",function(){return{scope:{ppSrc:"@"},restrict:"E",template:'

'}}).directive("ppPhone",function(){return{scope:{ppSrc:"@",ppType:"@"},restrict:"E",template:'

',link:function(p,e,s){(void 0===p.ppType||""===p.ppType)&&(p.ppType="iPhone4s")}}})});<commit_msg>update dist.<commit_after>/*!
 * pio-preview - easy Print.io product previews
 * http://github.com/fromkeith/pio-preview
 * (c) 2015 MIT License
 */
!function(p,e){"undefined"!=typeof module&&module.exports?module.exports=e(require("angular")):"function"==typeof define&&define.amd?define(["angular"],e):e(p.angular)}(this,function(p){"use strict";return p.module("pio-preview",[]).directive("ppMousepad",["$compile",function(p){return{scope:{ppSrc:"="},restrict:"E",template:'

',link:function(p,e,c){function t(){p.ppSrc&&(p.ppStyle="background-image: url("+p.ppSrc+")")}p.ppSrc&&t(),p.$watch("ppSrc",function(){t()})}}}]).directive("ppFramed",function(){return{scope:{ppSrc:"="},restrict:"E",template:'

',link:function(p,e,c){function t(){p.ppSrc&&(p.ppStyle="background-image: url("+p.ppSrc+")")}p.ppSrc&&t(),p.$watch("ppSrc",function(){t()})}}}).directive("ppMug",function(){return{scope:{ppSrc:"="},restrict:"E",template:'

',link:function(p,e,c){function t(){p.ppSrc&&(p.ppStyle="background-image: url("+p.ppSrc+")")}p.ppSrc&&t(),p.$watch("ppSrc",function(){t()})}}}).directive("ppPhone",function(){return{scope:{ppSrc:"=",ppType:"="},restrict:"E",template:'

',link:function(p,e,c){function t(){p.ppSrc&&((void 0===p.ppType||""===p.ppType)&&(p.ppType="iPhone4s"),p.ppStyle="background-image: url("+p.ppSrc+")",p.ppBack="https://az412349.vo.msecnd.net/product-phonecases/"+p.ppType+"-back.png",p.ppOverlay="https://az412349.vo.msecnd.net/product-phonecases/"+p.ppType+"-overlay.png")}p.ppSrc&&t(),p.$watch("ppSrc",function(){t()}),p.$watch("ppType",function(){t()})}}})});<|endoftext|>


<commit_before>var button = document.getElementById("button");

button.click(function generate() {
    var number = random(0,26);
    var div = document.getElementById('tongue_twister');

    alert(div+"Working");

    if(number<1){
        for(var forA=1; forA<8; forA++){
            div.text(words.a[floor(random(0,words.a.length))],10,forA*50);
        }
    }
    else if(number<2){
        for(var forB=1; forB<8; forB++){
            div.text(words.b[floor(random(0,words.b.length))],10,forB*50);
        }
    }
    else if(number<3){
        for(var forC=1; forC<8; forC++){
            div.text(words.c[floor(random(0,words.c.length))],10,forC*50);
        }
    }else if(number<4){
        for(var forD=1; forD<8; forD++){
            div.text(words.d[floor(random(0,words.d.length))],10,forD*50);
        }
    }else if(number<5){
        for(var forE=1; forE<8; forE++){
            div.text(words.e[floor(random(0,words.e.length))],10,forE*50);
        }
    }else if(number<6){
        for(var forF=1; forF<8; forF++){
            div.text(words.f[floor(random(0,words.f.length))],10,forF*50);
        }
    }else if(number<7){
        for(var forG=1; forG<8; forG++){
            div.text(words.g[floor(random(0,words.g.length))],10,forG*50);
        }
    }else if(number<8){
        for(var forH=1; forH<8; forH++){
            div.text(words.h[floor(random(0,words.h.length))],10,forH*50);
        }
    }else if(number<9){
        for(var forI=1; forI<8; forI++){
            div.text(words.i[floor(random(0,words.i.length))],10,forI*50);
        }
    }else if(number<10){
        for(var forJ=1; forJ<8; forJ++){
            div.text(words.j[floor(random(0,words.j.length))],10,forJ*50);
        }
    }else if(number<11){
        for(var forK=1; forK<8; forK++){
            div.text(words.k[floor(random(0,words.k.length))],10,forK*50);
        }
    }else if(number<12){
        for(var forL=1; forL<8; forL++){
            div.text(words.l[floor(random(0,words.l.length))],10,forL*50);
        }
    }else if(number<13){
        for(var forM=1; forM<8; forM++){
            div.text(words.m[floor(random(0,words.m.length))],10,forM*50);
        }
    }else if(number<14){
        for(var forN=1; forN<8; forN++){
            div.text(words.n[floor(random(0,words.n.length))],10,forN*50);
        }
    }else if(number<15){
        for(var forO=1; forO<8; forO++){
            div.text(words.o[floor(random(0,words.o.length))],10,forO*50);
        }
    }else if(number<16){
        for(var forP=1; forP<8; forP++){
            div.text(words.p[floor(random(0,words.p.length))],10,forP*50);
        }
    }else if(number<17){
        for(var forQ=1; forQ<8; forQ++){
            div.text(words.q[floor(random(0,words.q.length))],10,forQ*50);
        }
    }else if(number<18){
        for(var forR=1; forR<8; forR++){
            div.text(words.r[floor(random(0,words.r.length))],10,forR*50);
        }
    }else if(number<19){
        for(var forS=1; forS<8; forS++){
            div.text(words.s[floor(random(0,words.s.length))],10,forS*50);
        }
    }else if(number<20){
        for(var forT=1; forT<8; forT++){
            div.text(words.t[floor(random(0,words.t.length))],10,forT*50);
        }
    }else if(number<21){
        for(var forU=1; forU<8; forU++){
            div.text(words.u[floor(random(0,words.u.length))],10,forU*50);
        }
    }else if(number<22){
        for(var forV=1; forV<8; forV++){
            div.text(words.v[floor(random(0,words.v.length))],10,forV*50);
        }
    }else if(number<23){
        for(var forW=1; forW<8; forW++){
            div.text(words.w[floor(random(0,words.w.length))],10,forW*50);
        }
    }else if(number<24){
        for(var forX=1; forX<8; forX++){
            div.text(words.x[floor(random(0,words.x.length))],10,forX*50);
        }
    }else if(number<25){
        for(var forY=1; forY<8; forY++){
            div.text(words.y[floor(random(0,words.y.length))],10,forY*50);
        }
    }else {
        for(var forZ=1; forZ<8; forZ++){
            div.text(words.z[floor(random(0,words.z.length))],10,forZ*50);
        }
    }
});
<commit_msg>entered debugging<commit_after>var button = document.getElementById("button");

button.click(function generate() {
    alert("hi");
    /*var number = random(0,26);
    var div = document.getElementById('tongue_twister');

    alert(div+"Working");

    if(number<1){
        for(var forA=1; forA<8; forA++){
            div.text(words.a[floor(random(0,words.a.length))],10,forA*50);
        }
    }
    else if(number<2){
        for(var forB=1; forB<8; forB++){
            div.text(words.b[floor(random(0,words.b.length))],10,forB*50);
        }
    }
    else if(number<3){
        for(var forC=1; forC<8; forC++){
            div.text(words.c[floor(random(0,words.c.length))],10,forC*50);
        }
    }else if(number<4){
        for(var forD=1; forD<8; forD++){
            div.text(words.d[floor(random(0,words.d.length))],10,forD*50);
        }
    }else if(number<5){
        for(var forE=1; forE<8; forE++){
            div.text(words.e[floor(random(0,words.e.length))],10,forE*50);
        }
    }else if(number<6){
        for(var forF=1; forF<8; forF++){
            div.text(words.f[floor(random(0,words.f.length))],10,forF*50);
        }
    }else if(number<7){
        for(var forG=1; forG<8; forG++){
            div.text(words.g[floor(random(0,words.g.length))],10,forG*50);
        }
    }else if(number<8){
        for(var forH=1; forH<8; forH++){
            div.text(words.h[floor(random(0,words.h.length))],10,forH*50);
        }
    }else if(number<9){
        for(var forI=1; forI<8; forI++){
            div.text(words.i[floor(random(0,words.i.length))],10,forI*50);
        }
    }else if(number<10){
        for(var forJ=1; forJ<8; forJ++){
            div.text(words.j[floor(random(0,words.j.length))],10,forJ*50);
        }
    }else if(number<11){
        for(var forK=1; forK<8; forK++){
            div.text(words.k[floor(random(0,words.k.length))],10,forK*50);
        }
    }else if(number<12){
        for(var forL=1; forL<8; forL++){
            div.text(words.l[floor(random(0,words.l.length))],10,forL*50);
        }
    }else if(number<13){
        for(var forM=1; forM<8; forM++){
            div.text(words.m[floor(random(0,words.m.length))],10,forM*50);
        }
    }else if(number<14){
        for(var forN=1; forN<8; forN++){
            div.text(words.n[floor(random(0,words.n.length))],10,forN*50);
        }
    }else if(number<15){
        for(var forO=1; forO<8; forO++){
            div.text(words.o[floor(random(0,words.o.length))],10,forO*50);
        }
    }else if(number<16){
        for(var forP=1; forP<8; forP++){
            div.text(words.p[floor(random(0,words.p.length))],10,forP*50);
        }
    }else if(number<17){
        for(var forQ=1; forQ<8; forQ++){
            div.text(words.q[floor(random(0,words.q.length))],10,forQ*50);
        }
    }else if(number<18){
        for(var forR=1; forR<8; forR++){
            div.text(words.r[floor(random(0,words.r.length))],10,forR*50);
        }
    }else if(number<19){
        for(var forS=1; forS<8; forS++){
            div.text(words.s[floor(random(0,words.s.length))],10,forS*50);
        }
    }else if(number<20){
        for(var forT=1; forT<8; forT++){
            div.text(words.t[floor(random(0,words.t.length))],10,forT*50);
        }
    }else if(number<21){
        for(var forU=1; forU<8; forU++){
            div.text(words.u[floor(random(0,words.u.length))],10,forU*50);
        }
    }else if(number<22){
        for(var forV=1; forV<8; forV++){
            div.text(words.v[floor(random(0,words.v.length))],10,forV*50);
        }
    }else if(number<23){
        for(var forW=1; forW<8; forW++){
            div.text(words.w[floor(random(0,words.w.length))],10,forW*50);
        }
    }else if(number<24){
        for(var forX=1; forX<8; forX++){
            div.text(words.x[floor(random(0,words.x.length))],10,forX*50);
        }
    }else if(number<25){
        for(var forY=1; forY<8; forY++){
            div.text(words.y[floor(random(0,words.y.length))],10,forY*50);
        }
    }else {
        for(var forZ=1; forZ<8; forZ++){
            div.text(words.z[floor(random(0,words.z.length))],10,forZ*50);
        }
    }*/
});
<|endoftext|>


<commit_before>/*
 * changes to previous injection implementations
 * - no support for data-injection anymore, switch to new data-inject
 * - no support for data-href-next anymore, switch to data-inject: next-href
 * - XXX: add support for forms, see remnants in inject1 and ajaxify
 */
define([
    "jquery",
    "../core/parser",
    "../core/logger",
    "../lib/ajax",
    "../registry",
    "URIjs/URI",
    "jquery_ext", // for :scrollable for autoLoading-visible
    "URIjs/jquery.URI"
], function($, Parser, logger, ajax, registry, URI) {
    var log = logger.getLogger('pat.inject'),
        parser = new Parser("inject");

    //parser.add_argument('selector', 'body');
    parser.add_argument('selector');
    //XXX: (yet) unsupported: parser.add_argument('target', '$selector');
    parser.add_argument('target');
    parser.add_argument('type');
    parser.add_argument('next-href');
    //XXX: (yet) unsupported: parser.add_argument('source', '$selector');
    parser.add_argument('source');
    // XXX: this should not be here but the parser would bail on
    // unknown parameters and expand/collapsible need to pass the url
    // to us
    parser.add_argument('url');
    parser.add_argument('class');

    var _ = {
        name: "inject",
        trigger: "a.pat-inject, form.pat-inject",
        init: function($el, opts) {
            if ($el.length > 1)
                return $el.each(function() { _.init($(this), opts); });

            var cfgs = _.extractConfig($el, opts);
            $el.data('patterns.inject', cfgs);

            // In case next-href is specified the anchor's href will
            // be set to it after the injection is triggered. In case
            // the next href already exists, we do not activate the
            // injection but instead just change the anchors href.
            //
            // XXX: This is used in only one project for linked
            // fullcalendars, it's sanity is wonky and we should
            // probably solve it differently. -- Maybe it's cool
            // after all.
            var $nexthref = $(cfgs[0].nextHref);
            if ($el.is('a') && $nexthref.length > 0) {
                log.debug('Skipping as next href already exists', $nexthref);
                // XXX: reconsider how the injection enters exhausted state
                return $el.attr({href: (window.location.href.split('#')[0] || '') +
                                 cfgs[0].nextHref});
            }

            // setup event handlers
            if ($el.is('a'))
                $el.on("click.pat-inject", _.onClick);
            else if ($el.is('form'))
                $el.on("submit.pat-inject", _.onSubmit);

            // XXX: hack to support the old autoLoading-visible class
            if ($el.hasClass("autoLoading-visible"))
                _._initAutoloadVisible($el);

            log.debug('initialised:', $el);

            return $el;
        },
        destroy: function($el) {
            $el.off('.pat-inject');
            $el.data('patterns.inject', null);
            return $el;
        },
        onClick: function(ev) {
            var cfgs = $(this).data('patterns.inject'),
                $el = $(this);
            if (ev)
                ev.preventDefault();
            $el.trigger('patterns-inject-triggered');
            _.execute(cfgs, $el);
        },
        onSubmit: function(ev) {
            var cfgs = $(this).data('patterns.inject'),
                $el = $(this);
            if (ev)
                ev.preventDefault();
            $el.trigger('patterns-inject-triggered');
            _.execute(cfgs, $el);
        },
        extractConfig: function($el, opts) {
            opts = $.extend({}, opts);

            var urlparts, cfgs, defaultSelector;
            // opts has priority, fallback to href/action
            opts.url = opts.url || $el.attr('href') || $el.attr('action') || "";

            // separate selector from url
            urlparts = opts.url.split('#');
            opts.url = urlparts[0];

            // if no selector, check for selector as part of original url
            defaultSelector = urlparts[1] && '#' + urlparts[1] || 'body';

            if (urlparts.length > 2) {
                log.warn('Ignoring additional source ids:', urlparts.slice(2));
            }

            cfgs = parser.parse($el, opts, true);
            cfgs.forEach(function(cfg) {
                cfg.selector = cfg.selector || defaultSelector;
            });
            return cfgs;
        },
        // verify and post-process config
        // XXX: this should return a command instead of messing around on the config
        verifyConfig: function(cfgs) {
            var url = cfgs[0].url;

            // verification for each cfg in the array needs to succeed
            return cfgs.every(function(cfg) {
                // in case of multi-injection, all injections need to use
                // the same url
                if (cfg.url !== url) {
                    log.error('Unsupported different urls for multi-inject');
                    return false;
                }

                // defaults
                cfg.source = cfg.source || cfg.selector;
                cfg.target = cfg.target || cfg.selector;

                if (!_._extractModifiers(cfg))
                    return false;

                // make sure target exist
                cfg.$target = cfg.$target || $(cfg.target);
                if (cfg.$target.length === 0) {
                    if (!cfg.target) {
                        log.error('Need target selector', cfg);
                        return false;
                    }
                    cfg.$target = _._createTarget(cfg.target);
                    cfg.$injected = cfg.$target;
                }
                return true;
            });
        },
        _extractModifiers: function(cfg) {
            var source_re = /^(.+?)(::element)?$/,
                target_re = /^(.+?)(::element)?(::after|::before)?$/,
                source_match = source_re.exec(cfg.source),
                target_match = target_re.exec(cfg.target),
                targetMod, targetPosition;

            // source content or element?
            cfg.source = source_match[1];
            // XXX: turn into source processor
            cfg.sourceMod = source_match[2] ? "element" : "content";

            // will be added while the ajax request is in progress
            cfg.targetLoadClasses = "injecting";

            // target content or element?
            targetMod = target_match[2] ? "element" : "content";
            cfg.target = target_match[1];
            cfg.targetLoadClasses += " injecting-" + targetMod;

            // position relative to target
            targetPosition = (target_match[3] || "::").slice(2);
            if (targetPosition)
                cfg.targetLoadClasses += " injecting-" + targetPosition;

            cfg.action = targetMod + targetPosition;

            // Once we start detecting illegal combinations, we'll
            // return false in case of error
            return true;
        },

        // create a target that matches the selector
        //
        // XXX: so far we only support #target and create a div with
        // that id appended to the body.
        _createTarget: function(selector) {
            var $target;
            if (selector.slice(0,1) !== '#') {
                log.error('only id supported for non-existing target');
                return null;
            }
            $target = $('
').attr({id: selector.slice(1)});
            $('body').append($target);
            return $target;
        },
        execute: function(cfgs, $el) {
            // get a kinda deep copy, we scribble on it
            cfgs = cfgs.map(function(cfg) {
                return $.extend({}, cfg);
            });

            // XXX: this need to get functional, returning a command
            // for internal use, instead of passing on cfgs
            if (!_.verifyConfig(cfgs))
                return;

            // possibility for spinners on targets
            cfgs.forEach(function(cfg) {
                cfg.$target.addClass(cfg.targetLoadClasses);
            });

            var onSuccess = function(ev) {
                var data = ev && ev.jqxhr && ev.jqxhr.responseText;
                if (!data) {
                    log.warn('No response content, aborting', ev);
                    return;
                }
                // list of $source objects, one for each cfg
                var sources = cfgs.map(function(cfg) { return cfg.source; }),
                    sources$ = _._sourcesFromHtml(data, cfgs[0].url, sources);

                cfgs.forEach(function(cfg, idx) {
                    var $source = sources$[idx];

                    if (cfg.sourceMod === "content")
                        $source = $source.contents();

                    // perform injection
                    cfg.$target.each(function() {
                        var $target = $(this),
                            $src = $source.clone(),
                            $injected = cfg.$injected || $src;
                        if (_._inject($src, $target, cfg.action, $el)) {
                            $injected.filter(function() {
                                // setting data on textnode fails in IE8
                                return this.nodeType !== 3; //Node.TEXT_NODE
                            }).data('pat-injected', {origin: cfg.url});
                            $injected.addClass(cfg["class"])
                                .trigger('patterns-injected', cfg);
                        }
                    });
                });

                if (cfgs[0].nextHref) {
                    $el.attr({href: (window.location.href.split('#')[0] || '') +
                              cfgs[0].nextHref});
                    _.destroy($el);

                    // XXX: this used to be the case, but I don't see
                    // why that would be a good idea.
                    //
                    // jump to new href target
                    //if (!$el.hasClass("autoLoading-visible"))
                    //    window.location.href = $el.attr('href');
                }
                $el.off('pat-ajax-success.pat-inject');
            };

            $el.on('pat-ajax-success.pat-inject', onSuccess);

            ajax($el, {
                url: cfgs[0].url
            });
        },
        _inject: function($source, $target, action, $trigger) {
            // action to jquery method mapping, except for "content"
            // and "element"
            var method = {
                contentbefore: "prepend",
                contentafter:  "append",
                elementbefore: "before",
                elementafter:  "after"
            }[action];

            if ($source.length === 0) {
                log.warn('Aborting injection, source not found:', $source);
                return false;
            }
            if ($target.length === 0) {
                log.warn('Aborting injection, target not found:', $target);
                return false;
            }

            if (action === "content") {
                $target.empty().append($source);
            } else if (action === "element") {
                if ($target[0] === $trigger[0] ||
                    $target.find($trigger).length > 0) {
                    // delay removing the trigger element until all event
                    // handlers have fired
                    $target.hide().after($source);

                    // use a named function to support concurrent injections
                    var remove = function(ev) {
                        if (ev.target === $trigger[0]) {
                            $target.remove();
                            $(document)
                                .off("pat-ajax-success.pat-inject", remove);
                        }
                    };
                    $(document).on("pat-ajax-success.pat-inject", remove);
                } else {
                    $target.replaceWith($source);
                }
            } else {
                $target[method]($source);
            }

            return true;
        },
        _sourcesFromHtml: function(html, url, sources) {
            var $html = _._parseRawHtml(html, url);
            return sources.map(function(source) {
                if (source === "body")
                    source = "#__original_body";

                var $source = $html.find(source);

                if ($source.length === 0)
                    log.warn('No source elements for selector:', source, $html);

                return $source;
            });
        },

        _link_attributes: {
            A: "href",
            FORM: "action",
            IMG: "src"
        },

        _parseRawHtml: function(html, url) {
            url = url || "";

            // remove script tags and head and replace body by a div
            var clean_html = html
                    .replace(/


<commit_before>/*
 * Copyright (c) 2011-2012, McGill University
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided this the following conditions are met:
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of McGill University nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL McGill University  BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Author: Nicolas Kassis <nic.kassis@gmail.com>
 */

BrainCanvas.mincData = (function() {
  "use strict";

  var cachedSlices = {};

  /*
   * some utilities
   */
  function rotateUint16Array90Left(array,width,height){
    var new_array = new Uint16Array(width*height);

    for(var i = 0; i< width; i++){
      for(var j = 0; j< height; j++)  {
        new_array[i*height+j] = array[j*width+(width-i)];

      }
    }
    return new_array;
  }

  function rotateUint16Array90Right(array,width,height){
    var new_array = new Uint16Array(width*height);

    for(var i = 0; i< width; i++){
      for(var j=0; j< height; j++)  {
        new_array[i*height+j] = array[(height-j)*width+i];
      }
    }
    return new_array;
  }

  var minc_data_proto = {
    parseHeader: function(data) {
      this.header = data;
      this.order = data.order;
      if(this.order.length === 4) {
        this.order = this.order.slice(1,this.order.length);
        this.time = data.time;
      }
      this.xspace = data.xspace;
      this.yspace = data.yspace;
      this.zspace = data.zspace;
      this.xspace.name = "xspace";
      this.yspace.name = "yspace";
      this.zspace.name = "zspace";

      this.xspace.space_length = parseFloat(this.xspace.space_length);
      this.yspace.space_length = parseFloat(this.yspace.space_length);
      this.zspace.space_length = parseFloat(this.zspace.space_length);

      this.xspace.start = parseFloat(this.xspace.start);
      this.yspace.start = parseFloat(this.yspace.start);
      this.zspace.start = parseFloat(this.zspace.start);

      this.xspace.step = parseFloat(this.xspace.step);
      this.yspace.step = parseFloat(this.yspace.step);
      this.zspace.step = parseFloat(this.zspace.step);

      if(this.order.length === 4) {
        this.time.space_length = parseFloat(this.time.space_length);
        this.time.start = parseFloat(this.time.start);
        this.time.step = parseFloat(this.time.step);
      }

      //figure out height and length of each slices in each direction
      //width and length are the same, I'm doing this until I replace
      //all references to length in code. Bad naming duh! :(
      this[this.order[0]].height        = parseFloat(this[this.order[1]].space_length);
      this[this.order[0]].height_space  = this[this.order[1]];
      this[this.order[0]].length        = parseFloat(this[this.order[2]].space_length);
      this[this.order[0]].length_space  = this[this.order[2]];
      this[this.order[0]].width         = parseFloat(this[this.order[2]].space_length);
      this[this.order[0]].width_space   = this[this.order[2]];

      this[this.order[1]].height=parseFloat(this[this.order[2]].space_length);
      this[this.order[1]].height_space=this[this.order[2]];
      this[this.order[1]].length=parseFloat(this[this.order[0]].space_length);
      this[this.order[1]].length_space = this[this.order[0]];
      this[this.order[1]].width=parseFloat(this[this.order[0]].space_length);
      this[this.order[1]].width_space = this[this.order[0]];

      this[this.order[2]].height=parseFloat(this[this.order[1]].space_length);
      this[this.order[2]].height_space=this[this.order[1]];
      this[this.order[2]].length=parseFloat(this[this.order[0]].space_length);
      this[this.order[2]].length_space = this[this.order[0]];
      this[this.order[2]].width=parseFloat(this[this.order[0]].space_length);
      this[this.order[2]].width_space = this[this.order[0]];

      //calculate the offsets for each element of a slice
      this[this.order[0]].offset=parseFloat(this[this.order[1]].space_length)*parseFloat(this[this.order[2]].space_length);
      this[this.order[1]].offset=parseFloat(this[this.order[0]].space_length);
      this[this.order[2]].offset=parseFloat(this[this.order[0]].space_length);
      this[this.order[0]].slice_length = this[this.order[0]].height*this[this.order[0]].length;
    },
    /*
     * Warning: This function can get a little crazy
     * We are trying to get a slice out of the array. To do this we need to be careful this
     * we check for the orientation of the slice (steps positive or negative affect the orientation)
     *
     */
    slice: function(axis, number, time) {
      var slice;
      time = time || 0;

      cachedSlices[axis] = cachedSlices[axis] || [];
      cachedSlices[axis][time] =  cachedSlices[axis][time] || [];
      if(this[axis].step < 0) {
        number = this[axis].space_length - number;
      }
      if(cachedSlices[axis][time][number] !== undefined) {
        slice = cachedSlices[axis][time][number];
        slice.alpha = 1;
        slice.number = number;
        return slice;
      }

      if(this.order === undefined ) {
        return false;
      }

      var time_offset = 0;

      if(this.time) {
        time_offset = time * this[this.order[0]].height*this[this.order[0]].length * parseFloat(this[this.order[0]].space_length);
      }

      var length_step = this[axis].length_space.step;
      var height_step = this[axis].height_space.step;
      slice = {};
      var slice_data;
      var slice_length, height, row_length, element_offset, row_offset, slice_offset;
      var i, j, k;

      if(this.order[0] === axis) {
        slice_length = this[axis].height*this[axis].length;
        height = this[axis].height;
        row_length = this[axis].length;
        element_offset = 1;
        row_offset = row_length;
        slice_offset = slice_length;
        slice_data = new Uint16Array(slice_length);

        if(length_step > 0) {
          if(height_step > 0) {
            for (i = 0; i < slice_length; i++) {
              slice_data[i]=this.data[time_offset+ slice_offset*number+i];
            }
          } else {
            for(i = height; i > 0; i--) {
              for(j = 0; j < row_length; j++) {
                slice_data[(height-i)*row_length+j] = this.data[time_offset+slice_offset*number+i*row_length + j];
              }
            }
          }
        } else {
          if(height_step < 0) {
            for(i = 0; i < height; i++) {
              for(j = 0; j < row_length; j++) {
                slice_data[i*row_length+j] = this.data[time_offset+slice_offset*number+i*row_length + row_length - j];
              }
            }
          } else {
            for(i = height; i > 0; i--) {
              for(j = 0; j < row_length; j++) {
                slice_data[(height-i)*row_length+j] = this.data[time_offset+slice_offset*number+i*row_length + row_length - j];
              }
            }
          }
        }

      } else if (this.order[1] === axis ) {

        height = this[axis].height;
        slice_length = this[axis].height*this[axis].length;
        row_length = this[axis].length;
        element_offset = 1;
        row_offset = this[this.order[0]].slice_length;
        slice_offset = this[this.order[0]].length;
        slice_data = new Uint8Array(slice_length);

        if(height_step < 0) {
          for(j = 0; j<height; j++) {
            for(k = 0; k< row_length; k++){
              slice_data[j*(row_length)+k] = this.data[time_offset+number*slice_offset+row_offset*k+j];
            }
          }
        } else {
          for (j = height; j >= 0; j--) {
            for(k = 0; k < row_length; k++) {
              slice_data[(height-j)*(row_length)+k] = this.data[time_offset+number*slice_offset+row_offset*k+j];
            }
          }
        }

      } else {
        height = this[axis].height;
        slice_length = this[axis].height*this[axis].length;
        row_length = this[axis].length;
        element_offset = this[this.order[0]].slice_length;
        row_offset= this[this.order[0]].length;
        slice_offset = 1;
        slice_data = new Uint16Array(slice_length);

        for(j = 0; j<height; j++) {
          for(k = 0; k< row_length; k++){
            slice_data[j*(row_length)+k] = this.data[time_offset+number+this[this.order[0]].length*j+k*this[this.order[0]].slice_length];
          }
        }
      }

      //set the spaces on each axis
      slice.x = this[axis].length_space;
      slice.y = this[axis].height_space;
      slice.width = row_length;
      slice.height = height;

      //Checks if the slices are need to be rotated
      //xspace should have yspace on the x axis and zspace on the y axis
      if(axis === "xspace" && this.xspace.height_space.name === "yspace"){
        if (this.zspace.step < 0){
          slice_data = rotateUint16Array90Right(slice_data,slice.width,slice.height);
        } else {
          slice_data = rotateUint16Array90Left(slice_data,slice.width,slice.height);
        }
        slice.x = this[axis].height_space;
        slice.y = this[axis].length_space;
        slice.width = height;
        slice.height = row_length;

      }
      //yspace should be XxZ
      if(axis === "yspace" && this.yspace.height_space.name === "xspace"){
        if(this.zspace.step < 0){
          slice_data = rotateUint16Array90Right(slice_data,slice.width,slice.height);
        }else {
          slice_data = rotateUint16Array90Left(slice_data,slice.width,slice.height);
        }
        slice.x = this[axis].height_space;
        slice.y = this[axis].length_space;
        slice.width = height;
        slice.height = row_length;

      }
      //zspace should be XxY
      if(axis === "zspace" && this.zspace.height_space.name === "xspace"){
        slice_data = rotateUint16Array90Left(slice_data,slice.width,slice.height);
        slice.x = this[axis].length_space;
        slice.y = this[axis].height_space;
        slice.width = height;
        slice.height = row_length;

      }

      slice.data = slice_data;
      //set the spaces on each axis
      slice.x = slice.x || this[axis].length_space;
      slice.y = slice.y || this[axis].height_space;
      slice.width = slice.width || row_length;
      slice.height = slice.height || height;
      cachedSlices[axis][time][number] = slice;
      return slice;
    }
  };

  // The actual factory function!!
  return function(filename, headers, data) {

    var minc_data = Object.create(minc_data_proto);

    minc_data.parseHeader(headers);
    minc_data.data = data;

    return minc_data;
  };
})();

<commit_msg>Fixed bug in minc data caching.<commit_after>/*
 * Copyright (c) 2011-2012, McGill University
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided this the following conditions are met:
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of McGill University nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL McGill University  BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Author: Nicolas Kassis <nic.kassis@gmail.com>
 */

BrainCanvas.mincData = (function() {
  "use strict";

  /*
   * some utilities
   */
  function rotateUint16Array90Left(array,width,height){
    var new_array = new Uint16Array(width*height);

    for(var i = 0; i< width; i++){
      for(var j = 0; j< height; j++)  {
        new_array[i*height+j] = array[j*width+(width-i)];

      }
    }
    return new_array;
  }

  function rotateUint16Array90Right(array,width,height){
    var new_array = new Uint16Array(width*height);

    for(var i = 0; i< width; i++){
      for(var j=0; j< height; j++)  {
        new_array[i*height+j] = array[(height-j)*width+i];
      }
    }
    return new_array;
  }

  var minc_data_proto = {
    parseHeader: function(data) {
      this.header = data;
      this.order = data.order;
      if(this.order.length === 4) {
        this.order = this.order.slice(1,this.order.length);
        this.time = data.time;
      }
      this.xspace = data.xspace;
      this.yspace = data.yspace;
      this.zspace = data.zspace;
      this.xspace.name = "xspace";
      this.yspace.name = "yspace";
      this.zspace.name = "zspace";

      this.xspace.space_length = parseFloat(this.xspace.space_length);
      this.yspace.space_length = parseFloat(this.yspace.space_length);
      this.zspace.space_length = parseFloat(this.zspace.space_length);

      this.xspace.start = parseFloat(this.xspace.start);
      this.yspace.start = parseFloat(this.yspace.start);
      this.zspace.start = parseFloat(this.zspace.start);

      this.xspace.step = parseFloat(this.xspace.step);
      this.yspace.step = parseFloat(this.yspace.step);
      this.zspace.step = parseFloat(this.zspace.step);

      if(this.order.length === 4) {
        this.time.space_length = parseFloat(this.time.space_length);
        this.time.start = parseFloat(this.time.start);
        this.time.step = parseFloat(this.time.step);
      }

      //figure out height and length of each slices in each direction
      //width and length are the same, I'm doing this until I replace
      //all references to length in code. Bad naming duh! :(
      this[this.order[0]].height        = parseFloat(this[this.order[1]].space_length);
      this[this.order[0]].height_space  = this[this.order[1]];
      this[this.order[0]].length        = parseFloat(this[this.order[2]].space_length);
      this[this.order[0]].length_space  = this[this.order[2]];
      this[this.order[0]].width         = parseFloat(this[this.order[2]].space_length);
      this[this.order[0]].width_space   = this[this.order[2]];

      this[this.order[1]].height=parseFloat(this[this.order[2]].space_length);
      this[this.order[1]].height_space=this[this.order[2]];
      this[this.order[1]].length=parseFloat(this[this.order[0]].space_length);
      this[this.order[1]].length_space = this[this.order[0]];
      this[this.order[1]].width=parseFloat(this[this.order[0]].space_length);
      this[this.order[1]].width_space = this[this.order[0]];

      this[this.order[2]].height=parseFloat(this[this.order[1]].space_length);
      this[this.order[2]].height_space=this[this.order[1]];
      this[this.order[2]].length=parseFloat(this[this.order[0]].space_length);
      this[this.order[2]].length_space = this[this.order[0]];
      this[this.order[2]].width=parseFloat(this[this.order[0]].space_length);
      this[this.order[2]].width_space = this[this.order[0]];

      //calculate the offsets for each element of a slice
      this[this.order[0]].offset=parseFloat(this[this.order[1]].space_length)*parseFloat(this[this.order[2]].space_length);
      this[this.order[1]].offset=parseFloat(this[this.order[0]].space_length);
      this[this.order[2]].offset=parseFloat(this[this.order[0]].space_length);
      this[this.order[0]].slice_length = this[this.order[0]].height*this[this.order[0]].length;
    },
    /*
     * Warning: This function can get a little crazy
     * We are trying to get a slice out of the array. To do this we need to be careful this
     * we check for the orientation of the slice (steps positive or negative affect the orientation)
     *
     */
    slice: function(axis, number, time) {
      var slice;
      var cachedSlices = this.cachedSlices;
      time = time || 0;

      cachedSlices[axis] = cachedSlices[axis] || [];
      cachedSlices[axis][time] =  cachedSlices[axis][time] || [];
      if(this[axis].step < 0) {
        number = this[axis].space_length - number;
      }
      if(cachedSlices[axis][time][number] !== undefined) {
        slice = cachedSlices[axis][time][number];
        slice.alpha = 1;
        slice.number = number;
        return slice;
      }

      if(this.order === undefined ) {
        return false;
      }

      var time_offset = 0;

      if(this.time) {
        time_offset = time * this[this.order[0]].height*this[this.order[0]].length * parseFloat(this[this.order[0]].space_length);
      }

      var length_step = this[axis].length_space.step;
      var height_step = this[axis].height_space.step;
      slice = {};
      var slice_data;
      var slice_length, height, row_length, element_offset, row_offset, slice_offset;
      var i, j, k;

      if(this.order[0] === axis) {
        slice_length = this[axis].height*this[axis].length;
        height = this[axis].height;
        row_length = this[axis].length;
        element_offset = 1;
        row_offset = row_length;
        slice_offset = slice_length;
        slice_data = new Uint16Array(slice_length);

        if(length_step > 0) {
          if(height_step > 0) {
            for (i = 0; i < slice_length; i++) {
              slice_data[i]=this.data[time_offset+ slice_offset*number+i];
            }
          } else {
            for(i = height; i > 0; i--) {
              for(j = 0; j < row_length; j++) {
                slice_data[(height-i)*row_length+j] = this.data[time_offset+slice_offset*number+i*row_length + j];
              }
            }
          }
        } else {
          if(height_step < 0) {
            for(i = 0; i < height; i++) {
              for(j = 0; j < row_length; j++) {
                slice_data[i*row_length+j] = this.data[time_offset+slice_offset*number+i*row_length + row_length - j];
              }
            }
          } else {
            for(i = height; i > 0; i--) {
              for(j = 0; j < row_length; j++) {
                slice_data[(height-i)*row_length+j] = this.data[time_offset+slice_offset*number+i*row_length + row_length - j];
              }
            }
          }
        }

      } else if (this.order[1] === axis ) {

        height = this[axis].height;
        slice_length = this[axis].height*this[axis].length;
        row_length = this[axis].length;
        element_offset = 1;
        row_offset = this[this.order[0]].slice_length;
        slice_offset = this[this.order[0]].length;
        slice_data = new Uint8Array(slice_length);

        if(height_step < 0) {
          for(j = 0; j<height; j++) {
            for(k = 0; k< row_length; k++){
              slice_data[j*(row_length)+k] = this.data[time_offset+number*slice_offset+row_offset*k+j];
            }
          }
        } else {
          for (j = height; j >= 0; j--) {
            for(k = 0; k < row_length; k++) {
              slice_data[(height-j)*(row_length)+k] = this.data[time_offset+number*slice_offset+row_offset*k+j];
            }
          }
        }

      } else {
        height = this[axis].height;
        slice_length = this[axis].height*this[axis].length;
        row_length = this[axis].length;
        element_offset = this[this.order[0]].slice_length;
        row_offset= this[this.order[0]].length;
        slice_offset = 1;
        slice_data = new Uint16Array(slice_length);

        for(j = 0; j<height; j++) {
          for(k = 0; k< row_length; k++){
            slice_data[j*(row_length)+k] = this.data[time_offset+number+this[this.order[0]].length*j+k*this[this.order[0]].slice_length];
          }
        }
      }

      //set the spaces on each axis
      slice.x = this[axis].length_space;
      slice.y = this[axis].height_space;
      slice.width = row_length;
      slice.height = height;

      //Checks if the slices are need to be rotated
      //xspace should have yspace on the x axis and zspace on the y axis
      if(axis === "xspace" && this.xspace.height_space.name === "yspace"){
        if (this.zspace.step < 0){
          slice_data = rotateUint16Array90Right(slice_data,slice.width,slice.height);
        } else {
          slice_data = rotateUint16Array90Left(slice_data,slice.width,slice.height);
        }
        slice.x = this[axis].height_space;
        slice.y = this[axis].length_space;
        slice.width = height;
        slice.height = row_length;

      }
      //yspace should be XxZ
      if(axis === "yspace" && this.yspace.height_space.name === "xspace"){
        if(this.zspace.step < 0){
          slice_data = rotateUint16Array90Right(slice_data,slice.width,slice.height);
        }else {
          slice_data = rotateUint16Array90Left(slice_data,slice.width,slice.height);
        }
        slice.x = this[axis].height_space;
        slice.y = this[axis].length_space;
        slice.width = height;
        slice.height = row_length;

      }
      //zspace should be XxY
      if(axis === "zspace" && this.zspace.height_space.name === "xspace"){
        slice_data = rotateUint16Array90Left(slice_data,slice.width,slice.height);
        slice.x = this[axis].length_space;
        slice.y = this[axis].height_space;
        slice.width = height;
        slice.height = row_length;

      }

      slice.data = slice_data;
      //set the spaces on each axis
      slice.x = slice.x || this[axis].length_space;
      slice.y = slice.y || this[axis].height_space;
      slice.width = slice.width || row_length;
      slice.height = slice.height || height;
      cachedSlices[axis][time][number] = slice;
      return slice;
    }
  };

  // The actual factory function!!
  return function(filename, headers, data) {

    var minc_data = Object.create(minc_data_proto);
    minc_data.cachedSlices = {};
    minc_data.parseHeader(headers);
    minc_data.data = data;

    return minc_data;
  };
})();

<|endoftext|>