<commit_before>// Polyfills
import 'babel-polyfill';
import 'whatwg-fetch';

// Core elements
import { tangram, initMap, loadScene } from './map/map';
import { initEditor } from './editor/editor';

// Addons
import { showSceneLoadingIndicator, hideSceneLoadingIndicator } from './map/loading';
import ErrorModal from './modals/modal.error';
import WidgetsManager from './widgets/widgets-manager';
import SuggestManager from './editor/suggest';
import ErrorsManager from './editor/errors';
import GlslSandbox from './glsl/sandbox';
import GlslHelpers from './glsl/helpers';
import ColorPalette from './widgets/color-palette';
import LocalStorage from './storage/localstorage';

// Import Utils
import { subscribeMixin } from './tools/mixin';
import { parseQuery } from './tools/helpers';
import { StopWatch, debounce, createObjectURL } from './tools/common';
import { selectLines, isStrEmpty } from './editor/codemirror/tools';
import { getNodes, parseYamlString } from './editor/codemirror/yaml-tangram';
import { injectAPIKeys, suppressAPIKeys } from './editor/api-keys';

// Import UI elements
import { initDivider } from './ui/divider';
import './file/drop';
import './menus/menu';
import './ui/tooltip';

const query = parseQuery(window.location.search.slice(1));

const DEFAULT_SCENE = 'data/scenes/default.yaml';
const STORAGE_LAST_EDITOR_CONTENT = 'last-content';

let initialLoad = true;

class TangramPlay {
    constructor () {
        subscribeMixin(this);

        //Benchmark & Debuggin
        // if (options.benchark) {
        //     window.watch = new StopWatch();
        //     window.watch.start();
        // }

        this.editor = initEditor('editor');
        initMap();
        this.addons = {};

        // Wrap this.updateContent() in a debounce function
        this.updateContent = debounce(this.updateContent, 500);

        // Bind callback function to correct context
        this._watchEditorForChanges = this._watchEditorForChanges.bind(this);

        // TODO: Manage history / routing in its own module
        window.onpopstate = (e) => {
            if (e.state && e.state.sceneUrl) {
                this.load({ url: e.state.sceneUrl });
            }
        };

        setTimeout(() => {
            if (query['lines']) {
                this.selectLines(query['lines']);
            }
        }, 500);

        // If the user bails for whatever reason, hastily shove the contents of
        // the editor into some kind of storage. This overwrites whatever was
        // there before. Note that there is not really a way of handling unload
        // with our own UI and logic, since this allows for widespread abuse
        // of normal browser functionality.
        window.addEventListener('beforeunload', (event) => {
            // TODO:
            // Don't take original url or original base path from
            // Tangram (it may be wrong). Instead, remember this
            // in a "session" variable
            let sceneData = {
                original_url: tangram.scene.config_source,
                original_base_path: tangram.scene.config_path,
                contents: this.getContent(),
                is_clean: this.editor.isClean()
            };
            saveSceneContentsToLocalMemory(sceneData);
        });
    }

    //  ADDONS
    initAddons () {
        this.addons.widgetsManager = new WidgetsManager('data/tangram-api.json');
        this.addons.suggestManager = new SuggestManager('data/tangram-api.json');
        // this.addons.glslSandbox = new GlslSandbox();
        this.addons.glslHelpers = new GlslHelpers();
        this.addons.errorsManager = new ErrorsManager();
        this.addons.colorPalette = new ColorPalette();
    }

    /**
     * This function is the canonical way to load a scene in Tangram Play.
     * We want to avoid situations where we load scene files directly
     * into either Tangram or in CodeMirror and then have to handle
     * updating other parts of Tangram Play. Instead, we want Tangram Play
     * to ingest new scenes in a single way so that all the different parts
     * of the application can be updated predictably. The load function takes
     * either a URL path (for remote / external scenes), or the contents
     * of a Tangram YAML file itself.
     *
     * @param {Object} scene - an object containing one of two properties:
     *      scene.url - a URL path to load a scene from
     *      scene.contents - Tangram YAML as a text blob
     *      Do not pass in both! Currently `url` takes priority, but
     *      this is not guaranteed behaviour.
     */
    load (scene) {
        // Turn on loading indicator. This is turned off later
        // when Tangram reports that it's done.
        showSceneLoadingIndicator();

        // Turn off watching for changes in editor.
        this.editor.off('changes', this._watchEditorForChanges);

        // Either we are passed a url path, or scene file contents
        if (scene.url) {
            window.fetch(scene.url)
                .then(response => {
                    if (!response.ok) {
                        if (response.status === 404) {
                            throw new Error('The scene you requested could not be found.');
                        }
                        else {
                            throw new Error('Something went wrong loading the scene!');
                        }
                    }

                    return response.text();
                })
                .then(body => {
                    scene.contents = body;
                    this._doLoadProcess(scene);
                })
                .catch(error => {
                    const errorModal = new ErrorModal(error.message);
                    errorModal.show();
                    hideSceneLoadingIndicator();

                    // TODO: editor should not be attached to this
                    if (initialLoad === true) {
                        showUnloadedState(this.editor);
                        this.editor.doc.markClean();
                    }
                });
        }
        else if (scene.contents) {
            this._doLoadProcess(scene);
        }
    }

    _doLoadProcess (scene) {
        let url = scene.url || createObjectURL(scene.contents);

        // Send url to map and contents to editor
        // TODO: get contents from Tangram instead of another xhr request.
        // console.log(scene)
        loadScene(url, {
            reset: true,
            basePath: scene['original_base_path']
        });
        this._setSceneContentsInEditor(scene);

        hideUnloadedState();

        // This should only be true once
        initialLoad = false;

        // Update history
        let locationPrefix = '.';
        if (scene.url) {
            locationPrefix += '?scene=' + scene.url;
        }

        window.history.pushState({
            sceneUrl: (scene.url) ? scene.url : null
        }, null, locationPrefix + window.location.hash);

        // Trigger Events
        // Event object is empty right now.
        this.trigger('sceneload', {});
    }

    // Update widgets & content after a batch of changes
    _watchEditorForChanges (cm, changes) {
        this.updateContent();
    }

    _setSceneContentsInEditor (sceneData) {
        // Remove any instances of Tangram Play's default API key
        let contents = suppressAPIKeys(sceneData.contents);

        // Set content in CodeMirror
        this.editor.setValue(contents);
        this.editor.clearHistory();
        if (!!sceneData['is_clean']) {
            this.editor.doc.markClean();
        }

        // Turn change watching back on.
        this.editor.on('changes', this._watchEditorForChanges);
    }

    // SET
    setValue (node, str) {
        // Force space between the ':' and the value
        if (node.value === '') {
            str = ' ' + str;
        }

        // Calculate begining character of the value
        //               key:_[anchor]value
        //               ^ ^^^^
        //               | ||||__ + anchor.length
        //               | |||___ + 1
        //               | | `--- + 1
        //  range.from.ch  key.lenght

        let from = { line: node.range.from.line,
                     ch: node.range.from.ch + node.anchor.length + node.key.length + 2 };

        this.editor.doc.replaceRange(str, from, node.range.to);
    }

    // If editor is updated, send it to the map.
    updateContent () {
        const content = this.getContent();
        const url = createObjectURL(content);
        loadScene(url);
    }

    // GET
    // Get the contents of the editor (with injected API keys)
    getContent () {
        let content = this.editor.getValue();
        //  If API keys are missing, inject one
        content = injectAPIKeys(content);
        return content;
    }

    getNodes (from, to) {
        let nodes = [];

        if (from.line === to.line) {
            // If the searched nodes are in a same line
            let line = from.line;
            let inLineNodes = this.getNodesOnLine(line);

            for (let node of inLineNodes) {
                if (node.range.to.ch > from.ch || node.range.from < to.ch) {
                    nodes.push(node);
                }
            }
        }
        else {
            // If the searched nodes are in a range of lines
            for (let i = from.line; i <= to.line; i++) {
                let inLineNodes = this.getNodesOnLine(i);

                for (let node of inLineNodes) {
                    if (node.range.from.line === from.line) {
                        // Is in the beginning line
                        if (node.range.to.ch > from.ch) {
                            nodes.push(node);
                        }
                    }
                    else if (node.range.to.line === to.line) {
                        // is in the end line
                        if (node.range.from.ch < to.ch) {
                            nodes.push(node);
                        }
                    }
                    else {
                        // is in the sandwich lines
                        nodes.push(node);
                    }
                }
            }
        }
        return nodes;
    }

    getNodesOnLine (nLine) {
        if (isStrEmpty(this.editor.getLine(nLine))) {
            return [];
        }
        return getNodes(this.editor, nLine);
    }

    getNodesForAddress (address) {
        // NOTE:
        // This is an expensive process because for each call need to iterate through each line until it founds the right
        // address. Could be optimize if we store addresses in a map... but then the question is about how to keep it sync
        //
        let lastState;
        for (let line = 0; line < this.editor.getDoc().size; line++) {
            if (!this.editor.getLineHandle(line).stateAfter || !this.editor.getLineHandle(line).stateAfter.yamlState) {
                // If the line is NOT parsed.
                // ======================================================
                //
                // NOTE:
                // Manually parse it in a temporal buffer to avoid conflicts with codemirror parser.
                // This means outside the Line Handle
                //
                // Copy the last parsed state
                var state = JSON.parse(JSON.stringify(lastState));
                state.line = line;

                // Parse the current state
                parseYamlString(this.editor.getLineHandle(line).text, state, 4);

                // Iterate through keys in this line
                for (let key of state.nodes) {
                    if (key.address === address) {
                        return key;
                    }
                }
                // if nothing was found. Record the state and try again
                lastState = state;
                // TODO:
                // We might want to have two different parsers, a simpler one without keys and just address for
                // the higliting and another more roboust that keep tracks of: pairs (key/values), their ranges (from-to positions),
                // address and a some functions like getValue, setValue which could be use by widgets or others addons to modify content
            }
            else {
                // it the line HAVE BEEN parsed (use the stateAfter)
                // ======================================================
                lastState = this.editor.getLineHandle(line).stateAfter.yamlState;
                let keys = this.getNodesOnLine(line);
                for (let key of keys) {
                    if (key.address === address) {
                        return key;
                    }
                }
            }
        }
        console.log('Fail searching', address);
    }

    // Other actions
    selectLines (strRange) {
        selectLines(this.editor, strRange);
    }
}

function showUnloadedState (editor) {
    document.querySelector('.map-view').classList.add('map-view-not-loaded');
    editor.setValue('No scene loaded.');
}

function hideUnloadedState () {
    document.querySelector('.map-view').classList.remove('map-view-not-loaded');
}

// Determine what is the scene url and content to load during start-up
function determineScene () {
    let scene = {};

    // If there is a query, return it
    let query = parseQuery(window.location.search.slice(1));
    if (query.scene) {
        scene.url = query.scene;
        return scene;
    }

    // Else if there is something saved in memory (LocalStorage), return that
    let sceneData = getSceneContentsFromLocalMemory();
    if (sceneData) {
        return sceneData;
    }

    // Else load the default scene file.
    scene.url = DEFAULT_SCENE;
    return scene;
}

function saveSceneContentsToLocalMemory (sceneData) {
    // Expects an object of format:
    // {
    //     original_url: 'http://valid.url/path/scene.yaml',
    //     original_base_path: 'http://valid.url/path/',
    //     contents: 'Contents of scene.yaml',
    //     is_clean: boolean value; false indicates original contents were modified without saving
    // }
    LocalStorage.setItem(STORAGE_LAST_EDITOR_CONTENT, JSON.stringify(sceneData));
}

function getSceneContentsFromLocalMemory () {
    let sceneData = JSON.parse(LocalStorage.getItem(STORAGE_LAST_EDITOR_CONTENT));

    if (sceneData) {
        let contents = sceneData.contents;

        // TODO: Verify that contents are valid/parse-able YAML before returning it.
        // Throw away saved contents if it's "Loading...", "No scene loaded." or empty.
        // If we check for parse-ability, we won't need to hard-code the Loading check
        // (The alternative strategy is to not have the placeholder)
        if (contents && contents !== 'Loading...' && contents !== 'No scene loaded.' && contents.trim().length > 0) {
            return sceneData;
        }
    }

    return null;
}

let tangramPlay = new TangramPlay();

export default tangramPlay;
export let editor = tangramPlay.editor;

// LOAD SCENE FILE
let scene = determineScene();
tangramPlay.load(scene);
tangramPlay.initAddons();

// This is called here because right now divider position relies on
// editor and map being set up already
initDivider();

// for debug
window.tangramPlay = tangramPlay;
<commit_msg>Fix bug where all scenes are marked "dirty"<commit_after>// Polyfills
import 'babel-polyfill';
import 'whatwg-fetch';

// Core elements
import { tangram, initMap, loadScene } from './map/map';
import { initEditor } from './editor/editor';

// Addons
import { showSceneLoadingIndicator, hideSceneLoadingIndicator } from './map/loading';
import ErrorModal from './modals/modal.error';
import WidgetsManager from './widgets/widgets-manager';
import SuggestManager from './editor/suggest';
import ErrorsManager from './editor/errors';
import GlslSandbox from './glsl/sandbox';
import GlslHelpers from './glsl/helpers';
import ColorPalette from './widgets/color-palette';
import LocalStorage from './storage/localstorage';

// Import Utils
import { subscribeMixin } from './tools/mixin';
import { parseQuery } from './tools/helpers';
import { StopWatch, debounce, createObjectURL } from './tools/common';
import { selectLines, isStrEmpty } from './editor/codemirror/tools';
import { getNodes, parseYamlString } from './editor/codemirror/yaml-tangram';
import { injectAPIKeys, suppressAPIKeys } from './editor/api-keys';

// Import UI elements
import { initDivider } from './ui/divider';
import './file/drop';
import './menus/menu';
import './ui/tooltip';

const query = parseQuery(window.location.search.slice(1));

const DEFAULT_SCENE = 'data/scenes/default.yaml';
const STORAGE_LAST_EDITOR_CONTENT = 'last-content';

let initialLoad = true;

class TangramPlay {
    constructor () {
        subscribeMixin(this);

        //Benchmark & Debuggin
        // if (options.benchark) {
        //     window.watch = new StopWatch();
        //     window.watch.start();
        // }

        this.editor = initEditor('editor');
        initMap();
        this.addons = {};

        // Wrap this.updateContent() in a debounce function
        this.updateContent = debounce(this.updateContent, 500);

        // Bind callback function to correct context
        this._watchEditorForChanges = this._watchEditorForChanges.bind(this);

        // TODO: Manage history / routing in its own module
        window.onpopstate = (e) => {
            if (e.state && e.state.sceneUrl) {
                this.load({ url: e.state.sceneUrl });
            }
        };

        setTimeout(() => {
            if (query['lines']) {
                this.selectLines(query['lines']);
            }
        }, 500);

        // If the user bails for whatever reason, hastily shove the contents of
        // the editor into some kind of storage. This overwrites whatever was
        // there before. Note that there is not really a way of handling unload
        // with our own UI and logic, since this allows for widespread abuse
        // of normal browser functionality.
        window.addEventListener('beforeunload', (event) => {
            // TODO:
            // Don't take original url or original base path from
            // Tangram (it may be wrong). Instead, remember this
            // in a "session" variable
            let sceneData = {
                original_url: tangram.scene.config_source,
                original_base_path: tangram.scene.config_path,
                contents: this.getContent(),
                is_clean: this.editor.isClean()
            };
            saveSceneContentsToLocalMemory(sceneData);
        });
    }

    //  ADDONS
    initAddons () {
        this.addons.widgetsManager = new WidgetsManager('data/tangram-api.json');
        this.addons.suggestManager = new SuggestManager('data/tangram-api.json');
        // this.addons.glslSandbox = new GlslSandbox();
        this.addons.glslHelpers = new GlslHelpers();
        this.addons.errorsManager = new ErrorsManager();
        this.addons.colorPalette = new ColorPalette();
    }

    /**
     * This function is the canonical way to load a scene in Tangram Play.
     * We want to avoid situations where we load scene files directly
     * into either Tangram or in CodeMirror and then have to handle
     * updating other parts of Tangram Play. Instead, we want Tangram Play
     * to ingest new scenes in a single way so that all the different parts
     * of the application can be updated predictably. The load function takes
     * either a URL path (for remote / external scenes), or the contents
     * of a Tangram YAML file itself.
     *
     * @param {Object} scene - an object containing one of two properties:
     *      scene.url - a URL path to load a scene from
     *      scene.contents - Tangram YAML as a text blob
     *      Do not pass in both! Currently `url` takes priority, but
     *      this is not guaranteed behaviour.
     */
    load (scene) {
        // Turn on loading indicator. This is turned off later
        // when Tangram reports that it's done.
        showSceneLoadingIndicator();

        // Turn off watching for changes in editor.
        this.editor.off('changes', this._watchEditorForChanges);

        // Either we are passed a url path, or scene file contents
        if (scene.url) {
            window.fetch(scene.url)
                .then(response => {
                    if (!response.ok) {
                        if (response.status === 404) {
                            throw new Error('The scene you requested could not be found.');
                        }
                        else {
                            throw new Error('Something went wrong loading the scene!');
                        }
                    }

                    return response.text();
                })
                .then(body => {
                    scene.contents = body;
                    this._doLoadProcess(scene);
                })
                .catch(error => {
                    const errorModal = new ErrorModal(error.message);
                    errorModal.show();
                    hideSceneLoadingIndicator();

                    // TODO: editor should not be attached to this
                    if (initialLoad === true) {
                        showUnloadedState(this.editor);
                        this.editor.doc.markClean();
                    }
                });
        }
        else if (scene.contents) {
            this._doLoadProcess(scene);
        }
    }

    _doLoadProcess (scene) {
        let url = scene.url || createObjectURL(scene.contents);

        // Send url to map and contents to editor
        // TODO: get contents from Tangram instead of another xhr request.
        // console.log(scene)
        loadScene(url, {
            reset: true,
            basePath: scene['original_base_path']
        });
        this._setSceneContentsInEditor(scene);

        hideUnloadedState();

        // This should only be true once
        initialLoad = false;

        // Update history
        let locationPrefix = '.';
        if (scene.url) {
            locationPrefix += '?scene=' + scene.url;
        }

        window.history.pushState({
            sceneUrl: (scene.url) ? scene.url : null
        }, null, locationPrefix + window.location.hash);

        // Trigger Events
        // Event object is empty right now.
        this.trigger('sceneload', {});
    }

    // Update widgets & content after a batch of changes
    _watchEditorForChanges (cm, changes) {
        this.updateContent();
    }

    _setSceneContentsInEditor (sceneData) {
        // Remove any instances of Tangram Play's default API key
        let contents = suppressAPIKeys(sceneData.contents);

        // Set content in CodeMirror
        this.editor.setValue(contents);
        this.editor.clearHistory();

        // Mark as "clean" if the contents are freshly loaded
        // (there is no is_clean property defined) or if contents
        // have been restored with the is_clean property set to "true"
        // This is converted from JSON so the value is a string, not
        // a Boolean. Otherwise, the document has not been previously
        // saved and it is left in the "dirty" state.
        if (typeof sceneData['is_clean'] === 'undefined' || sceneData['is_clean'] === 'true') {
            this.editor.doc.markClean();
        }

        // Turn change watching back on.
        this.editor.on('changes', this._watchEditorForChanges);
    }

    // SET
    setValue (node, str) {
        // Force space between the ':' and the value
        if (node.value === '') {
            str = ' ' + str;
        }

        // Calculate begining character of the value
        //               key:_[anchor]value
        //               ^ ^^^^
        //               | ||||__ + anchor.length
        //               | |||___ + 1
        //               | | `--- + 1
        //  range.from.ch  key.lenght

        let from = { line: node.range.from.line,
                     ch: node.range.from.ch + node.anchor.length + node.key.length + 2 };

        this.editor.doc.replaceRange(str, from, node.range.to);
    }

    // If editor is updated, send it to the map.
    updateContent () {
        const content = this.getContent();
        const url = createObjectURL(content);
        loadScene(url);
    }

    // GET
    // Get the contents of the editor (with injected API keys)
    getContent () {
        let content = this.editor.getValue();
        //  If API keys are missing, inject one
        content = injectAPIKeys(content);
        return content;
    }

    getNodes (from, to) {
        let nodes = [];

        if (from.line === to.line) {
            // If the searched nodes are in a same line
            let line = from.line;
            let inLineNodes = this.getNodesOnLine(line);

            for (let node of inLineNodes) {
                if (node.range.to.ch > from.ch || node.range.from < to.ch) {
                    nodes.push(node);
                }
            }
        }
        else {
            // If the searched nodes are in a range of lines
            for (let i = from.line; i <= to.line; i++) {
                let inLineNodes = this.getNodesOnLine(i);

                for (let node of inLineNodes) {
                    if (node.range.from.line === from.line) {
                        // Is in the beginning line
                        if (node.range.to.ch > from.ch) {
                            nodes.push(node);
                        }
                    }
                    else if (node.range.to.line === to.line) {
                        // is in the end line
                        if (node.range.from.ch < to.ch) {
                            nodes.push(node);
                        }
                    }
                    else {
                        // is in the sandwich lines
                        nodes.push(node);
                    }
                }
            }
        }
        return nodes;
    }

    getNodesOnLine (nLine) {
        if (isStrEmpty(this.editor.getLine(nLine))) {
            return [];
        }
        return getNodes(this.editor, nLine);
    }

    getNodesForAddress (address) {
        // NOTE:
        // This is an expensive process because for each call need to iterate through each line until it founds the right
        // address. Could be optimize if we store addresses in a map... but then the question is about how to keep it sync
        //
        let lastState;
        for (let line = 0; line < this.editor.getDoc().size; line++) {
            if (!this.editor.getLineHandle(line).stateAfter || !this.editor.getLineHandle(line).stateAfter.yamlState) {
                // If the line is NOT parsed.
                // ======================================================
                //
                // NOTE:
                // Manually parse it in a temporal buffer to avoid conflicts with codemirror parser.
                // This means outside the Line Handle
                //
                // Copy the last parsed state
                var state = JSON.parse(JSON.stringify(lastState));
                state.line = line;

                // Parse the current state
                parseYamlString(this.editor.getLineHandle(line).text, state, 4);

                // Iterate through keys in this line
                for (let key of state.nodes) {
                    if (key.address === address) {
                        return key;
                    }
                }
                // if nothing was found. Record the state and try again
                lastState = state;
                // TODO:
                // We might want to have two different parsers, a simpler one without keys and just address for
                // the higliting and another more roboust that keep tracks of: pairs (key/values), their ranges (from-to positions),
                // address and a some functions like getValue, setValue which could be use by widgets or others addons to modify content
            }
            else {
                // it the line HAVE BEEN parsed (use the stateAfter)
                // ======================================================
                lastState = this.editor.getLineHandle(line).stateAfter.yamlState;
                let keys = this.getNodesOnLine(line);
                for (let key of keys) {
                    if (key.address === address) {
                        return key;
                    }
                }
            }
        }
        console.log('Fail searching', address);
    }

    // Other actions
    selectLines (strRange) {
        selectLines(this.editor, strRange);
    }
}

function showUnloadedState (editor) {
    document.querySelector('.map-view').classList.add('map-view-not-loaded');
    editor.setValue('No scene loaded.');
}

function hideUnloadedState () {
    document.querySelector('.map-view').classList.remove('map-view-not-loaded');
}

// Determine what is the scene url and content to load during start-up
function determineScene () {
    let scene = {};

    // If there is a query, return it
    let query = parseQuery(window.location.search.slice(1));
    if (query.scene) {
        scene.url = query.scene;
        return scene;
    }

    // Else if there is something saved in memory (LocalStorage), return that
    let sceneData = getSceneContentsFromLocalMemory();
    if (sceneData) {
        return sceneData;
    }

    // Else load the default scene file.
    scene.url = DEFAULT_SCENE;
    return scene;
}

function saveSceneContentsToLocalMemory (sceneData) {
    // Expects an object of format:
    // {
    //     original_url: 'http://valid.url/path/scene.yaml',
    //     original_base_path: 'http://valid.url/path/',
    //     contents: 'Contents of scene.yaml',
    //     is_clean: boolean value; false indicates original contents were modified without saving
    // }
    LocalStorage.setItem(STORAGE_LAST_EDITOR_CONTENT, JSON.stringify(sceneData));
}

function getSceneContentsFromLocalMemory () {
    let sceneData = JSON.parse(LocalStorage.getItem(STORAGE_LAST_EDITOR_CONTENT));

    if (sceneData) {
        let contents = sceneData.contents;

        // TODO: Verify that contents are valid/parse-able YAML before returning it.
        // Throw away saved contents if it's "Loading...", "No scene loaded." or empty.
        // If we check for parse-ability, we won't need to hard-code the Loading check
        // (The alternative strategy is to not have the placeholder)
        if (contents && contents !== 'Loading...' && contents !== 'No scene loaded.' && contents.trim().length > 0) {
            return sceneData;
        }
    }

    return null;
}

let tangramPlay = new TangramPlay();

export default tangramPlay;
export let editor = tangramPlay.editor;

// LOAD SCENE FILE
let scene = determineScene();
tangramPlay.load(scene);
tangramPlay.initAddons();

// This is called here because right now divider position relies on
// editor and map being set up already
initDivider();

// for debug
window.tangramPlay = tangramPlay;
<|endoftext|>


<commit_before>var gulp = require('gulp');
var jshint = require('gulp-jshint');

// 监听的 js 文件目录
var jsFiles = [
	'src/*.js',
	'src/main/*.js',
	'src/mvvm/*.js',
	'src/mvvm/parsers/*.js'
];

// js 语法检测配置
var jshintConfig = {
	'asi'      : true,
	'curly'    : true,
	'latedef'  : true,
	'forin'    : false,
	'noarg'    : false,
	'sub'      : true,
	'undef'    : true,
	'unused'   : 'vars',
	'boss'     : true,
	'eqnull'   : true,
	'browser'  : true,
	'laxcomma' : true,
	'devel'    : true,
	'smarttabs': true,
	// Aviod warning The Function constructor is a form of eval.
	'evil'     : true,
	// Avoid warning The '__proto__' property is deprecated.
	'proto'    : true,
	// Available in es6
	'esversion': 6,
	'predef'   : ['module', 'require', 'define']
}

gulp.task('jshint', function () {
	gulp.src(jsFiles)
		.pipe(jshint(jshintConfig))
		.pipe(jshint.reporter('default'));
});

// gulp start 开启任务监听
gulp.task('start', function () {
	// 初始化时开启一次
	gulp.start('jshint');

	// 监听 js 文件变化
	gulp.watch(jsFiles, function () {
		gulp.start('jshint');
	});
});
<commit_msg>Update jshint.<commit_after>var gulp = require('gulp');
var jshint = require('gulp-jshint');

// 监听的 js 文件目录
var jsFiles = [
	'src/*.js',
	'src/main/*.js',
	'src/mvvm/*.js',
	'src/mvvm/*/*.js',
	'src/mvvm/*/*/*.js'
];

// js 语法检测配置
var jshintConfig = {
	'asi'      : true,
	'curly'    : true,
	'latedef'  : true,
	'forin'    : false,
	'noarg'    : false,
	'sub'      : true,
	'undef'    : true,
	'unused'   : 'vars',
	'boss'     : true,
	'eqnull'   : true,
	'browser'  : true,
	'laxcomma' : true,
	'devel'    : true,
	'smarttabs': true,
	// Aviod warning The Function constructor is a form of eval.
	'evil'     : true,
	// Avoid warning The '__proto__' property is deprecated.
	'proto'    : true,
	// Available in es6
	'esversion': 6
}

gulp.task('jshint', function () {
	gulp.src(jsFiles)
		.pipe(jshint(jshintConfig))
		.pipe(jshint.reporter('default'));
});

// gulp start 开启任务监听
gulp.task('start', function () {
	// 初始化时开启一次
	gulp.start('jshint');

	// 监听 js 文件变化
	gulp.watch(jsFiles, function () {
		gulp.start('jshint');
	});
});
<|endoftext|>


<commit_before>
ElmRuntime.Render.Element = function() {
'use strict';

var Utils = ElmRuntime.use(ElmRuntime.Render.Utils);
var newElement = Utils.newElement;
var colorToCss = Utils.colorToCss;
var addTransform = Utils.addTransform;
var removeTransform = Utils.removeTransform;
var fromList = Utils.fromList;
var eq = Utils.eq;

function setProps(elem, node) {
    var props = elem.props;

    var element = elem.element;
    var width = props.width - (element.adjustWidth || 0);
    var height = props.height - (element.adjustHeight || 0);
    node.style.width  = (width |0) + 'px';
    node.style.height = (height|0) + 'px';

    if (props.opacity !== 1) {
        node.style.opacity = props.opacity;
    }

    if (props.color.ctor === 'Just') {
        node.style.backgroundColor = colorToCss(props.color._0);
    }

    if (props.tag !== '') {
        node.id = props.tag;
    }

    if (props.hover.ctor !== '_Tuple0') {
        addHover(node, props.hover);
    }

    if (props.click.ctor !== '_Tuple0') {
        addClick(node, props.click);
    }

    if (props.href !== '') {
        var anchor = newElement('a');
        anchor.href = props.href;
        anchor.style.display = 'block';
        anchor.style.pointerEvents = 'auto';
        anchor.appendChild(node);
        node = anchor;
    }

    return node;
}

function addClick(e, handler) {
    e.style.pointerEvents = 'auto';
    e.elm_click_handler = handler;
    function trigger(ev) {
        e.elm_click_handler(Utils.Tuple0);
        ev.stopPropagation();
    }
    e.elm_click_trigger = trigger;
    e.addEventListener('click', trigger);
}

function removeClick(e, handler) {
    if (e.elm_click_trigger) {
        e.removeEventListener('click', e.elm_click_trigger);
        e.elm_click_trigger = null;
        e.elm_click_handler = null;
    }
}

function addHover(e, handler) {
    e.style.pointerEvents = 'auto';
    e.elm_hover_handler = handler;
    e.elm_hover_count = 0;

    function over(evt) {
        if (e.elm_hover_count++ > 0) return;
        e.elm_hover_handler(true);
        evt.stopPropagation();
    }
    function out(evt) {
        if (e.contains(evt.toElement || evt.relatedTarget)) return;
        e.elm_hover_count = 0;
        e.elm_hover_handler(false);
        evt.stopPropagation();
    }
    e.elm_hover_over = over;
    e.elm_hover_out = out;
    e.addEventListener('mouseover', over);
    e.addEventListener('mouseout', out);
}

function removeHover(e) {
    e.elm_hover_handler = null;
    if (e.elm_hover_over) {
        e.removeEventListener('mouseover', e.elm_hover_over);
        e.elm_hover_over = null;
    }
    if (e.elm_hover_out) {
        e.removeEventListener('mouseout', e.elm_hover_out);
        e.elm_hover_out = null;
    }
}

function image(props, img) {
    switch (img._0.ctor) {
    case 'Plain':   return plainImage(img._3);
    case 'Fitted':  return fittedImage(props.width, props.height, img._3);
    case 'Cropped': return croppedImage(img,props.width,props.height,img._3);
    case 'Tiled':   return tiledImage(img._3);
    }
}

function plainImage(src) {
    var img = newElement('img');
    img.src = src;
    img.name = src;
    img.style.display = "block";
    return img;
}

function tiledImage(src) {
    var div = newElement('div');
    div.style.backgroundImage = 'url(' + src + ')';
    return div;
}

function fittedImage(w, h, src) {
    var div = newElement('div');
    div.style.background = 'url(' + src + ') no-repeat center';
    div.style.webkitBackgroundSize = 'cover';
    div.style.MozBackgroundSize = 'cover';
    div.style.OBackgroundSize = 'cover';
    div.style.backgroundSize = 'cover';
    return div;
}

function croppedImage(elem, w, h, src) {
    var pos = elem._0._0;
    var e = newElement('div');
    e.style.overflow = "hidden";

    var img = newElement('img');
    img.onload = function() {
        var sw = w / elem._1, sh = h / elem._2;
        img.style.width = ((this.width * sw)|0) + 'px';
        img.style.height = ((this.height * sh)|0) + 'px';
        img.style.marginLeft = ((- pos._0 * sw)|0) + 'px';
        img.style.marginTop = ((- pos._1 * sh)|0) + 'px';
    };
    img.src = src;
    img.name = src;
    e.appendChild(img);
    return e;
}

function goOut(e) { e.style.position = 'absolute'; return e; }
function goDown(e) { return e }
function goRight(e) { e.style.styleFloat = e.style.cssFloat = "left"; return e; }

var directionTable = {
    DUp    : goDown,
    DDown  : goDown,
    DLeft  : goRight,
    DRight : goRight,
    DIn    : goOut,
    DOut   : goOut
};
function needsReversal(dir) {
    return dir == 'DUp' || dir == 'DLeft' || dir == 'DIn';
}

function flow(dir,elist) {
    var array = fromList(elist);
    var container = newElement('div');
    var goDir = directionTable[dir];
    if (goDir == goOut) {
        container.style.pointerEvents = 'none';
    }
    if (needsReversal(dir)) {
        array.reverse();
    }
    var len = array.length;
    for (var i = 0; i < len; ++i) {
        container.appendChild(goDir(render(array[i])));
    }
    return container;
}

function toPos(pos) {
    switch(pos.ctor) {
    case "Absolute": return  pos._0 + "px";
    case "Relative": return (pos._0 * 100) + "%";
    }
}

// must clear right, left, top, bottom, and transform
// before calling this function
function setPos(pos,elem,e) {
    var element = elem.element;
    var props = elem.props;
    var w = props.width + (element.adjustWidth ? element.adjustWidth : 0);
    var h = props.height + (element.adjustHeight ? element.adjustHeight : 0);

    e.style.position = 'absolute';
    e.style.margin = 'auto';
    var transform = '';
    switch(pos.horizontal.ctor) {
    case 'P': e.style.right = toPos(pos.x); e.style.removeProperty('left'); break;
    case 'Z': transform = 'translateX(' + ((-w/2)|0) + 'px) ';
    case 'N': e.style.left = toPos(pos.x); e.style.removeProperty('right'); break;
    }
    switch(pos.vertical.ctor) {
    case 'N': e.style.bottom = toPos(pos.y); e.style.removeProperty('top'); break;
    case 'Z': transform += 'translateY(' + ((-h/2)|0) + 'px)';
    case 'P': e.style.top = toPos(pos.y); e.style.removeProperty('bottom'); break;
    }
    if (transform !== '') addTransform(e.style, transform);
    return e;
}

function container(pos,elem) {
    var e = render(elem);
    setPos(pos, elem, e);
    var div = newElement('div');
    div.style.position = 'relative';
    div.style.overflow = 'hidden';
    div.appendChild(e);
    return div;
}

function rawHtml(elem) {
    var html = elem.html;
    var args = elem.args;
    var guid = elem.guid;
    var align = elem.align;

    var div = newElement('div');
    div.innerHTML = html;
    div.style.visibility = "hidden";
    if (align) div.style.textAlign = align;
    document.body.appendChild(div);

    for (var i = args.length; i--; ) {
        var arg = args[i];
        var span = document.getElementById('md-' + guid + '-' + i);
        if (arg.isText) {
            span.innerHTML = arg;
        } else {
            span.style.display = 'block';
            span.style.width = arg.props.width + 'px';
            span.style.height = arg.props.height + 'px';
            span.appendChild(render(arg));
        }
    }
    document.body.removeChild(div);
    div.style.visibility = 'visible';
    div.style.pointerEvents = 'auto';
    return div;
}

function render(elem) {
    return setProps(elem, makeElement(elem));
}
function makeElement(e) {
    var elem = e.element;
    switch(elem.ctor) {
    case 'Image':     return image(e.props, elem);
    case 'Flow':      return flow(elem._0.ctor, elem._1);
    case 'Container': return container(elem._0, elem._1);
    case 'Spacer':    return newElement('div');
    case 'RawHtml':   return rawHtml(elem);
    case 'Custom':    return elem.render(elem.model);
    }
}

function update(node, curr, next) {
    if (node.tagName === 'A') {
        node = node.firstChild;
    }
    if (curr.props.id === next.props.id) {
        return updateProps(node, curr, next);
    }
    if (curr.element.ctor !== next.element.ctor) {
        node.parentNode.replaceChild(render(next),node);
        return true;
    }
    var nextE = next.element, currE = curr.element;
    switch(nextE.ctor) {
    case "Spacer": break;
    case "RawHtml":
        // only markdown blocks have guids, so this must be a text block
        if (nextE.guid === null) {
            if(currE.html.valueOf() !== nextE.html.valueOf()) {
                node.innerHTML = nextE.html;
            }
            break;
        }
        if (nextE.guid !== currE.guid) {
            node.parentNode.replaceChild(render(next),node);
            return true;
        }
        var nargs = nextE.args;
        var cargs = currE.args;
        for (var i = nargs.length; i--; ) {
            var narg = nargs[i];
            var carg = cargs[i]
            if (narg == carg) continue;
            var span = document.getElementById('md-' + currE.guid + '-' + i);
            if (narg.isElement) {
                if (carg.isElement) {
                    update(span, carg, narg);
                } else {
                    span.style.display = 'block';
                    var e = render(narg);
                    span.innerHTML = '';
                    span.appendChild(e);
                }
            } else {
                span.style.display = 'inline';
                span.innerHTML = narg;
            }
        }
        break;
    case "Image":
        if (nextE._0.ctor === 'Plain') {
            if (nextE._3 !== currE._3) node.src = nextE._3;
        } else if (!eq(nextE,currE) ||
                   next.props.width !== curr.props.width ||
                   next.props.height !== curr.props.height) {
            node.parentNode.replaceChild(render(next),node);
            return true;
        }
        break;
    case "Flow":
        var arr = fromList(nextE._1);
        for (var i = arr.length; i--; ) { arr[i] = arr[i].element.ctor; }
        if (nextE._0.ctor !== currE._0.ctor) {
            node.parentNode.replaceChild(render(next),node);
            return true;
        }
        var nexts = fromList(nextE._1);
        var kids = node.childNodes;
        if (nexts.length !== kids.length) {
            node.parentNode.replaceChild(render(next),node);
            return true;
        }
        var currs = fromList(currE._1);
        var dir = nextE._0.ctor;
        var goDir = directionTable[dir];
        var toReverse = needsReversal(dir);
        var len = kids.length;
        for (var i = len; i-- ;) {
            update(kids[toReverse ? len - i - 1 : i],currs[i],nexts[i]);
            goDir(kids[i]);
        }
        break;
    case "Container":
        update(node.firstChild, currE._1, nextE._1);
        setPos(nextE._0, nextE._1, node.firstChild);
        break;
    case "Custom":
        if (currE.type === nextE.type) {
            var done = nextE.update(node, currE.model, nextE.model);
            if (done) return;
        } else {
            return node.parentNode.replaceChild(render(next), node);
        }
    }
    updateProps(node, curr, next);
}

function updateProps(node, curr, next) {
    var nextProps = next.props;
    var currProps = curr.props;

    var element = next.element;
    var width = nextProps.width - (element.adjustWidth || 0);
    var height = nextProps.height - (element.adjustHeight || 0);
    if (width !== currProps.width) {
        node.style.width = (width|0) + 'px';
    }
    if (height !== currProps.height) {
        node.style.height = (height|0) + 'px';
    }

    if (nextProps.opacity !== currProps.opacity) {
        node.style.opacity = nextProps.opacity;
    }

    var nextColor = nextProps.color.ctor === 'Just'
        ? colorToCss(nextProps.color._0)
        : 'transparent';
    if (node.style.backgroundColor !== nextColor) {
        node.style.backgroundColor = nextColor;
    }

    if (nextProps.tag !== currProps.tag) {
        node.id = nextProps.tag;
    }

    if (nextProps.href !== currProps.href) {
        if (currProps.href === '') {
            // add a surrounding href
            var anchor = newElement('a');
            anchor.href = nextProps.href;
            anchor.style.display = 'block';
            anchor.style.pointerEvents = 'auto';

            node.parentNode.replaceChild(anchor, node);
            anchor.appendChild(node);
        } else if (nextProps.href === '') {
            // remove the surrounding href
            var anchor = node.parentNode;
            anchor.parentNode.replaceChild(node, anchor);
        } else {
            // just update the link
            node.parentNode.href = nextProps.href;
        }
    }

    // update click and hover handlers
    var removed = false;

    // update hover handlers
    if (nextProps.hover.ctor === '_Tuple0') {
        removed = true;
        removeHover(node);
    } else if (node.elm_hover_handler) {
        node.elm_hover_handler = nextProps.hover;
    } else {
        addHover(node, nextProps.hover);
    }

    // update click handlers
    if (nextProps.click.ctor === '_Tuple0') {
        removed = true;
        removeClick(node);
    } else if (node.elm_click_handler) {
        node.elm_click_handler = nextProps.click;
    } else {
        addClick(node, nextProps.click);
    }

    // stop capturing clicks if
    if (removed
        && nextProps.hover.ctor === '_Tuple0'
        && nextProps.click.ctor === '_Tuple0')
    {
        node.style.pointerEvents = 'none';
    }
}

return { render:render, update:update };

};
<commit_msg>Attempt to resolve #785, pointer-events being set to 'none' by accident<commit_after>
ElmRuntime.Render.Element = function() {
'use strict';

var Utils = ElmRuntime.use(ElmRuntime.Render.Utils);
var newElement = Utils.newElement;
var colorToCss = Utils.colorToCss;
var addTransform = Utils.addTransform;
var removeTransform = Utils.removeTransform;
var fromList = Utils.fromList;
var eq = Utils.eq;

function setProps(elem, node) {
    var props = elem.props;

    var element = elem.element;
    var width = props.width - (element.adjustWidth || 0);
    var height = props.height - (element.adjustHeight || 0);
    node.style.width  = (width |0) + 'px';
    node.style.height = (height|0) + 'px';

    if (props.opacity !== 1) {
        node.style.opacity = props.opacity;
    }

    if (props.color.ctor === 'Just') {
        node.style.backgroundColor = colorToCss(props.color._0);
    }

    if (props.tag !== '') {
        node.id = props.tag;
    }

    if (props.hover.ctor !== '_Tuple0') {
        addHover(node, props.hover);
    }

    if (props.click.ctor !== '_Tuple0') {
        addClick(node, props.click);
    }

    if (props.href !== '') {
        var anchor = newElement('a');
        anchor.href = props.href;
        anchor.style.display = 'block';
        anchor.style.pointerEvents = 'auto';
        anchor.appendChild(node);
        node = anchor;
    }

    return node;
}

function addClick(e, handler) {
    e.style.pointerEvents = 'auto';
    e.elm_click_handler = handler;
    function trigger(ev) {
        e.elm_click_handler(Utils.Tuple0);
        ev.stopPropagation();
    }
    e.elm_click_trigger = trigger;
    e.addEventListener('click', trigger);
}

function removeClick(e, handler) {
    if (e.elm_click_trigger) {
        e.removeEventListener('click', e.elm_click_trigger);
        e.elm_click_trigger = null;
        e.elm_click_handler = null;
    }
}

function addHover(e, handler) {
    e.style.pointerEvents = 'auto';
    e.elm_hover_handler = handler;
    e.elm_hover_count = 0;

    function over(evt) {
        if (e.elm_hover_count++ > 0) return;
        e.elm_hover_handler(true);
        evt.stopPropagation();
    }
    function out(evt) {
        if (e.contains(evt.toElement || evt.relatedTarget)) return;
        e.elm_hover_count = 0;
        e.elm_hover_handler(false);
        evt.stopPropagation();
    }
    e.elm_hover_over = over;
    e.elm_hover_out = out;
    e.addEventListener('mouseover', over);
    e.addEventListener('mouseout', out);
}

function removeHover(e) {
    e.elm_hover_handler = null;
    if (e.elm_hover_over) {
        e.removeEventListener('mouseover', e.elm_hover_over);
        e.elm_hover_over = null;
    }
    if (e.elm_hover_out) {
        e.removeEventListener('mouseout', e.elm_hover_out);
        e.elm_hover_out = null;
    }
}

function image(props, img) {
    switch (img._0.ctor) {
    case 'Plain':   return plainImage(img._3);
    case 'Fitted':  return fittedImage(props.width, props.height, img._3);
    case 'Cropped': return croppedImage(img,props.width,props.height,img._3);
    case 'Tiled':   return tiledImage(img._3);
    }
}

function plainImage(src) {
    var img = newElement('img');
    img.src = src;
    img.name = src;
    img.style.display = "block";
    return img;
}

function tiledImage(src) {
    var div = newElement('div');
    div.style.backgroundImage = 'url(' + src + ')';
    return div;
}

function fittedImage(w, h, src) {
    var div = newElement('div');
    div.style.background = 'url(' + src + ') no-repeat center';
    div.style.webkitBackgroundSize = 'cover';
    div.style.MozBackgroundSize = 'cover';
    div.style.OBackgroundSize = 'cover';
    div.style.backgroundSize = 'cover';
    return div;
}

function croppedImage(elem, w, h, src) {
    var pos = elem._0._0;
    var e = newElement('div');
    e.style.overflow = "hidden";

    var img = newElement('img');
    img.onload = function() {
        var sw = w / elem._1, sh = h / elem._2;
        img.style.width = ((this.width * sw)|0) + 'px';
        img.style.height = ((this.height * sh)|0) + 'px';
        img.style.marginLeft = ((- pos._0 * sw)|0) + 'px';
        img.style.marginTop = ((- pos._1 * sh)|0) + 'px';
    };
    img.src = src;
    img.name = src;
    e.appendChild(img);
    return e;
}

function goOut(e) { e.style.position = 'absolute'; return e; }
function goDown(e) { return e }
function goRight(e) { e.style.styleFloat = e.style.cssFloat = "left"; return e; }

var directionTable = {
    DUp    : goDown,
    DDown  : goDown,
    DLeft  : goRight,
    DRight : goRight,
    DIn    : goOut,
    DOut   : goOut
};
function needsReversal(dir) {
    return dir == 'DUp' || dir == 'DLeft' || dir == 'DIn';
}

function flow(dir,elist) {
    var array = fromList(elist);
    var container = newElement('div');
    var goDir = directionTable[dir];
    if (goDir == goOut) {
        container.style.pointerEvents = 'none';
    }
    if (needsReversal(dir)) {
        array.reverse();
    }
    var len = array.length;
    for (var i = 0; i < len; ++i) {
        container.appendChild(goDir(render(array[i])));
    }
    return container;
}

function toPos(pos) {
    switch(pos.ctor) {
    case "Absolute": return  pos._0 + "px";
    case "Relative": return (pos._0 * 100) + "%";
    }
}

// must clear right, left, top, bottom, and transform
// before calling this function
function setPos(pos,elem,e) {
    var element = elem.element;
    var props = elem.props;
    var w = props.width + (element.adjustWidth ? element.adjustWidth : 0);
    var h = props.height + (element.adjustHeight ? element.adjustHeight : 0);

    e.style.position = 'absolute';
    e.style.margin = 'auto';
    var transform = '';
    switch(pos.horizontal.ctor) {
    case 'P': e.style.right = toPos(pos.x); e.style.removeProperty('left'); break;
    case 'Z': transform = 'translateX(' + ((-w/2)|0) + 'px) ';
    case 'N': e.style.left = toPos(pos.x); e.style.removeProperty('right'); break;
    }
    switch(pos.vertical.ctor) {
    case 'N': e.style.bottom = toPos(pos.y); e.style.removeProperty('top'); break;
    case 'Z': transform += 'translateY(' + ((-h/2)|0) + 'px)';
    case 'P': e.style.top = toPos(pos.y); e.style.removeProperty('bottom'); break;
    }
    if (transform !== '') addTransform(e.style, transform);
    return e;
}

function container(pos,elem) {
    var e = render(elem);
    setPos(pos, elem, e);
    var div = newElement('div');
    div.style.position = 'relative';
    div.style.overflow = 'hidden';
    div.appendChild(e);
    return div;
}

function rawHtml(elem) {
    var html = elem.html;
    var args = elem.args;
    var guid = elem.guid;
    var align = elem.align;

    var div = newElement('div');
    div.innerHTML = html;
    div.style.visibility = "hidden";
    if (align) div.style.textAlign = align;
    document.body.appendChild(div);

    for (var i = args.length; i--; ) {
        var arg = args[i];
        var span = document.getElementById('md-' + guid + '-' + i);
        if (arg.isText) {
            span.innerHTML = arg;
        } else {
            span.style.display = 'block';
            span.style.width = arg.props.width + 'px';
            span.style.height = arg.props.height + 'px';
            span.appendChild(render(arg));
        }
    }
    document.body.removeChild(div);
    div.style.visibility = 'visible';
    div.style.pointerEvents = 'auto';
    return div;
}

function render(elem) {
    return setProps(elem, makeElement(elem));
}
function makeElement(e) {
    var elem = e.element;
    switch(elem.ctor) {
    case 'Image':     return image(e.props, elem);
    case 'Flow':      return flow(elem._0.ctor, elem._1);
    case 'Container': return container(elem._0, elem._1);
    case 'Spacer':    return newElement('div');
    case 'RawHtml':   return rawHtml(elem);
    case 'Custom':    return elem.render(elem.model);
    }
}

function update(node, curr, next) {
    if (node.tagName === 'A') {
        node = node.firstChild;
    }
    if (curr.props.id === next.props.id) {
        return updateProps(node, curr, next);
    }
    if (curr.element.ctor !== next.element.ctor) {
        node.parentNode.replaceChild(render(next),node);
        return true;
    }
    var nextE = next.element, currE = curr.element;
    switch(nextE.ctor) {
    case "Spacer": break;
    case "RawHtml":
        // only markdown blocks have guids, so this must be a text block
        if (nextE.guid === null) {
            if(currE.html.valueOf() !== nextE.html.valueOf()) {
                node.innerHTML = nextE.html;
            }
            break;
        }
        if (nextE.guid !== currE.guid) {
            node.parentNode.replaceChild(render(next),node);
            return true;
        }
        var nargs = nextE.args;
        var cargs = currE.args;
        for (var i = nargs.length; i--; ) {
            var narg = nargs[i];
            var carg = cargs[i]
            if (narg == carg) continue;
            var span = document.getElementById('md-' + currE.guid + '-' + i);
            if (narg.isElement) {
                if (carg.isElement) {
                    update(span, carg, narg);
                } else {
                    span.style.display = 'block';
                    var e = render(narg);
                    span.innerHTML = '';
                    span.appendChild(e);
                }
            } else {
                span.style.display = 'inline';
                span.innerHTML = narg;
            }
        }
        break;
    case "Image":
        if (nextE._0.ctor === 'Plain') {
            if (nextE._3 !== currE._3) node.src = nextE._3;
        } else if (!eq(nextE,currE) ||
                   next.props.width !== curr.props.width ||
                   next.props.height !== curr.props.height) {
            node.parentNode.replaceChild(render(next),node);
            return true;
        }
        break;
    case "Flow":
        var arr = fromList(nextE._1);
        for (var i = arr.length; i--; ) { arr[i] = arr[i].element.ctor; }
        if (nextE._0.ctor !== currE._0.ctor) {
            node.parentNode.replaceChild(render(next),node);
            return true;
        }
        var nexts = fromList(nextE._1);
        var kids = node.childNodes;
        if (nexts.length !== kids.length) {
            node.parentNode.replaceChild(render(next),node);
            return true;
        }
        var currs = fromList(currE._1);
        var dir = nextE._0.ctor;
        var goDir = directionTable[dir];
        var toReverse = needsReversal(dir);
        var len = kids.length;
        for (var i = len; i-- ;) {
            update(kids[toReverse ? len - i - 1 : i],currs[i],nexts[i]);
            goDir(kids[i]);
        }
        break;
    case "Container":
        update(node.firstChild, currE._1, nextE._1);
        setPos(nextE._0, nextE._1, node.firstChild);
        break;
    case "Custom":
        if (currE.type === nextE.type) {
            var done = nextE.update(node, currE.model, nextE.model);
            if (done) return;
        } else {
            return node.parentNode.replaceChild(render(next), node);
        }
    }
    updateProps(node, curr, next);
}

function updateProps(node, curr, next) {
    var nextProps = next.props;
    var currProps = curr.props;

    var element = next.element;
    var width = nextProps.width - (element.adjustWidth || 0);
    var height = nextProps.height - (element.adjustHeight || 0);
    if (width !== currProps.width) {
        node.style.width = (width|0) + 'px';
    }
    if (height !== currProps.height) {
        node.style.height = (height|0) + 'px';
    }

    if (nextProps.opacity !== currProps.opacity) {
        node.style.opacity = nextProps.opacity;
    }

    var nextColor = nextProps.color.ctor === 'Just'
        ? colorToCss(nextProps.color._0)
        : 'transparent';
    if (node.style.backgroundColor !== nextColor) {
        node.style.backgroundColor = nextColor;
    }

    if (nextProps.tag !== currProps.tag) {
        node.id = nextProps.tag;
    }

    if (nextProps.href !== currProps.href) {
        if (currProps.href === '') {
            // add a surrounding href
            var anchor = newElement('a');
            anchor.href = nextProps.href;
            anchor.style.display = 'block';
            anchor.style.pointerEvents = 'auto';

            node.parentNode.replaceChild(anchor, node);
            anchor.appendChild(node);
        } else if (nextProps.href === '') {
            // remove the surrounding href
            var anchor = node.parentNode;
            anchor.parentNode.replaceChild(node, anchor);
        } else {
            // just update the link
            node.parentNode.href = nextProps.href;
        }
    }

    // update click and hover handlers
    var removed = false;

    // update hover handlers
    if (currProps.hover.ctor === '_Tuple0') {
        if (nextProps.hover.ctor !== '_Tuple0') {
            addHover(node, nextProps.hover);
        }
    }
    else {
        if (nextProps.hover.ctor === '_Tuple0') {
            removed = true;
            removeHover(node);
        }
        else {
            node.elm_hover_handler = nextProps.hover;
        }
    }

    // update click handlers
    if (currProps.click.ctor === '_Tuple0') {
        if (nextProps.click.ctor !== '_Tuple0') {
            addClick(node, nextProps.click);
        }
    }
    else {
        if (nextProps.click.ctor === '_Tuple0') {
            removed = true;
            removeClick(node);
        } else {
            node.elm_click_handler = nextProps.click;
        }
    }

    // stop capturing clicks if
    if (removed
        && nextProps.hover.ctor === '_Tuple0'
        && nextProps.click.ctor === '_Tuple0')
    {
        node.style.pointerEvents = 'none';
    }
}

return { render:render, update:update };

};
<|endoftext|>


<commit_before>angular.module('just.service', [])
    .service('i18nService', ['$translate','tmhDynamicLocale','settings', function($translate, tmhDynamicLocale, settings) {
        this.useLanguage = function(code) {
            $translate.use(code);
            tmhDynamicLocale.set(code);
        };
        this.supportedLanguages = function() {
            return settings.translated_languages;
        };
    }]);
<commit_msg>Fix more indent.<commit_after>angular.module('just.service', [])
  .service('i18nService', ['$translate','tmhDynamicLocale','settings', function($translate, tmhDynamicLocale, settings) {
    this.useLanguage = function(code) {
      $translate.use(code);
      tmhDynamicLocale.set(code);
    };
    this.supportedLanguages = function() {
      return settings.translated_languages;
    };
  }]);
<|endoftext|>


<commit_before>$.widget("mobile.progressbar", {
    options: {
        outerTheme: null,
        innerTheme: null,
        mini: false,
        value: 0,
        max: 100,
        counter: true,
        indefinite: false
    },
    min: 0,
    _create: function () {
        var control = this.element,
            parentTheme = $.mobile.getInheritedTheme(control, "c"),
            outerTheme = this.options.outerTheme || parentTheme,
            innerTheme = this.options.indefinite ? "indefinite" : this.options.innerTheme || parentTheme,
            miniClass = this.options.mini ? " ui-tolito-progressbar-mini" : "",
            counter = this.options.counter;
        this.element.addClass(['ui-tolito-progressbar ', " ui-tolito-progressbar-outer-", outerTheme,
                ' ui-tolito-progressbar-corner-all', miniClass
        ].join(""))
            .attr({
            role: "progressbar",
            "min-value": this.min,
            "max-value": this.options.max,
            "content-value": this._value()
        });
        if (counter && !this.options.indefinite) {
            this.labelContent = ($("

")
                .text(this._value())
                .addClass('ui-tolito-progressbar-label ui-tolito-progressbar-corner-all'))
                .appendTo(this.element);
        }
        this.valueContent = ($("

")
            .addClass(['ui-tolito-progressbar-bg ', " ui-tolito-progressbar-active-", innerTheme,
                ' ui-tolito-progressbar-corner-all'
        ].join("")))
            .appendTo(this.element);
        if (!this.options.indefinite) {
            this._refreshValue();
            this.oldValue = this._value();
        }
    },
    _destroy: function () {
        this.element.removeClass()
            .removeAttr("role")
            .removeAttr("min-value")
            .removeAttr("max-value")
            .removeAttr("content-value");
        if ((typeof this.labelContent !== "undefined")) {
            this.labelContent.remove();
        }
        this.valueContent.remove();
    },
    value: function (newValue) {
        if (newValue === undefined) {
            return this._value();
        }
        this._setOption("value", newValue);
        return this;
    },
    _setOption: function (key, value) {
        this.options.value = value;
        if (key === "value") {
            this._refreshValue();
            if (this._value() === this.options.max) {
                this.element.trigger("complete");
            }
        }
    },
    _value: function () {
        var val = this.options.value;
        if (typeof val !== "number") {
            val = 0;
        }
        return Math.min(this.options.max, Math.max(this.min, val));
    },
    _percentage: function () {
        return 100 * this._value() / this.options.max;
    },
    _refreshValue: function () {
        var value = this.value();
        this.oldValue = value;
        this.valueContent.css("width", [this._percentage(), '%'].join(""));
        if ((typeof this.labelContent !== "undefined")) {
            this.labelContent.text([this._percentage(), '%'].join(""))
        }
        this.element.attr("content-value", value);
    }
});

var ERR_1 = '[Error]: Tolito is already built.',
    ERR_2 = '[Error]: Tolito is already running.',
    ERR_3 = '[Error]: Tolito is already stopped.',
    ERR_4 = '[Error]: Tolito is indefinite.',
    ERR_5 = '[Error]: Tolito element id is undefined',
    ERR_6 = '[Error]: Tolito is not built yet.';

TolitoConstructor = function (elementId) {
    if (elementId === undefined) {
        throw ERR_5;
    }
    this._id = elementId;
    this._defaultOuterTheme = null;
    this._defaultInnerTheme = null;
    this._defaultMax = 100;
    this._defaultStartFrom = 0;
    this._defaultInterval = 100;
    this._isBuilt = false;
    this._mini = false;
    this._isRunning = false;
    this._indefinite = false;
    return this;
}

TolitoProgressBar = function (arg) {
    return new TolitoConstructor(arg);
}

TolitoConstructor.prototype = {
    logOptions: function () {
        ((typeof console === "undefined") ? {
            log: function () {}
        } : console)
            .log(["id: '", this.getId(), "' outerTheme: '", this.getOuterTheme(), "' innerTheme: '", this.getInnerTheme(),
                "' max: '", this.getMax(), "' mini: '", this.getMini(), "' startFrom: '", this.getStartFrom(),
                "' interval: '", this.getInterval(), "' showCounter: '", this.getShowCounter(), "'", "' indefinite: '",
                this.getIndefinite(), "'"
        ].join(""));
        return this;
    },
    getId: function () {
        return this._id;
    },
    setOuterTheme: function (newTheme) {
        if (this._isBuilt) {
            throw ERR_1;
        } else {
            this._outerTheme = newTheme;
            return this;
        }
    },
    getOuterTheme: function () {
        return this._outerTheme || this._defaultOuterTheme;
    },
    setInnerTheme: function (newInnerTheme) {
        if (this._isBuilt) {
            throw ERR_1;
        } else {
            this._innerTheme = newInnerTheme;
            return this;
        }
    },
    getInnerTheme: function () {
        return this._innerTheme || this._defaultInnerTheme;
    },
    setStartFrom: function (newStartFrom) {
        if (this._isBuilt) {
            throw ERR_1;
        } else {
            this._startFrom = newStartFrom;
            return this;
        }
    },
    getStartFrom: function () {
        return this._startFrom || this._defaultStartFrom;
    },
    setMax: function (newMax) {
        if (this._isBuilt) {
            throw ERR_1;
        } else {
            this._max = newMax;
            return this;
        }
    },
    getMax: function () {
        return this._max || this._defaultMax;
    },
    isMini: function (newMini) {
        if (this._isBuilt) {
            throw ERR_1;
        } else {
            this._mini = newMini;
            return this;
        }
    },
    getMini: function () {
        return this._mini;
    },
    isIndefinite: function (newIndefinite) {
        if (this._isBuilt) {
            throw ERR_1;
        } else {
            this._indefinite = newIndefinite;
            return this;
        }
    },
    getIndefinite: function () {
        return this._indefinite;
    },
    showCounter: function (newShowCounter) {
        if (this._isBuilt) {
            throw ERR_1;
        } else {
            this._showCounter = newShowCounter;
            return this;
        }
    },
    getShowCounter: function () {
        return this._showCounter;
    },
    setInterval: function (newInterval) {
        if (this._isBuilt) {
            throw ERR_1;
        } else {
            this._interval = newInterval;
            return this;
        }
    },
    getInterval: function () {
        return this._interval || this._defaultInterval;
    },
    build: function () {
        if (this._isBuilt) {
            throw ERR_1;
        } else {
            $(['#', this.getId()].join(""))
                .progressbar({
                outerTheme: this.getOuterTheme(),
                innerTheme: this.getInnerTheme(),
                value: this.getStartFrom(),
                max: this.getMax(),
                mini: this.getMini(),
                indefinite: this.getIndefinite(),
                counter: this.getShowCounter()
            });
            this._isBuilt = true;
            return this;
        }

    },
    run: function () {
        if (this._isRunning) {
            throw ERR_2;
        } else if (this._indefinite) {
            throw ERR_4;
        } else {
            (function loop(instance) {
                instance.fillProgressBar = setTimeout((function (inst) {
                    return function () {
                        var thisValue = $(['#', inst.getId()].join(""))
                            .progressbar('option', 'value'),
                            counter = !isNaN(thisValue) ? (thisValue + 1) : 1;
                        if (counter > inst.getMax()) {
                            clearTimeout(inst.fillProgressBar);
                        } else {
                            $(['#', inst.getId()].join(""))
                                .progressbar({
                                value: counter
                            });
                            loop(inst);
                        }
                    }
                })(instance), instance.getInterval());
            })(this);
            this._isRunning = true;
            return this;
        }
    },
    stop: function () {
        if (!this._isRunning) {
            throw ERR_3;
        } else {
            clearTimeout(this.fillProgressBar);
            this._isRunning = false;
            return this;
        }
    },
    setValue: function (val) {
        if (this._indefinite) {
            throw ERR_4;
        } else {
            $(['#', this.getId()].join(""))
                .progressbar({
                value: val
            });
            return this;
        }
    },
    destroy: function () {
        if (!this._isBuilt) {
            throw ERR_6;
        } else {
            if (this.fillProgressBar) {
                clearTimeout(this.fillProgressBar);
            }
            $(document).off('complete', ['#', this.getId()].join(""));
            $(['#', this.getId()].join("")).progressbar('destroy');
            return null;
        }
    }
};
<commit_msg>1.0.5 JS Remove empty line<commit_after>$.widget("mobile.progressbar", {
    options: {
        outerTheme: null,
        innerTheme: null,
        mini: false,
        value: 0,
        max: 100,
        counter: true,
        indefinite: false
    },
    min: 0,
    _create: function () {
        var control = this.element,
            parentTheme = $.mobile.getInheritedTheme(control, "c"),
            outerTheme = this.options.outerTheme || parentTheme,
            innerTheme = this.options.indefinite ? "indefinite" : this.options.innerTheme || parentTheme,
            miniClass = this.options.mini ? " ui-tolito-progressbar-mini" : "",
            counter = this.options.counter;
        this.element.addClass(['ui-tolito-progressbar ', " ui-tolito-progressbar-outer-", outerTheme,
                ' ui-tolito-progressbar-corner-all', miniClass
        ].join(""))
            .attr({
            role: "progressbar",
            "min-value": this.min,
            "max-value": this.options.max,
            "content-value": this._value()
        });
        if (counter && !this.options.indefinite) {
            this.labelContent = ($("

")
                .text(this._value())
                .addClass('ui-tolito-progressbar-label ui-tolito-progressbar-corner-all'))
                .appendTo(this.element);
        }
        this.valueContent = ($("

")
            .addClass(['ui-tolito-progressbar-bg ', " ui-tolito-progressbar-active-", innerTheme,
                ' ui-tolito-progressbar-corner-all'
        ].join("")))
            .appendTo(this.element);
        if (!this.options.indefinite) {
            this._refreshValue();
            this.oldValue = this._value();
        }
    },
    _destroy: function () {
        this.element.removeClass()
            .removeAttr("role")
            .removeAttr("min-value")
            .removeAttr("max-value")
            .removeAttr("content-value");
        if ((typeof this.labelContent !== "undefined")) {
            this.labelContent.remove();
        }
        this.valueContent.remove();
    },
    value: function (newValue) {
        if (newValue === undefined) {
            return this._value();
        }
        this._setOption("value", newValue);
        return this;
    },
    _setOption: function (key, value) {
        this.options.value = value;
        if (key === "value") {
            this._refreshValue();
            if (this._value() === this.options.max) {
                this.element.trigger("complete");
            }
        }
    },
    _value: function () {
        var val = this.options.value;
        if (typeof val !== "number") {
            val = 0;
        }
        return Math.min(this.options.max, Math.max(this.min, val));
    },
    _percentage: function () {
        return 100 * this._value() / this.options.max;
    },
    _refreshValue: function () {
        var value = this.value();
        this.oldValue = value;
        this.valueContent.css("width", [this._percentage(), '%'].join(""));
        if ((typeof this.labelContent !== "undefined")) {
            this.labelContent.text([this._percentage(), '%'].join(""))
        }
        this.element.attr("content-value", value);
    }
});

var ERR_1 = '[Error]: Tolito is already built.',
    ERR_2 = '[Error]: Tolito is already running.',
    ERR_3 = '[Error]: Tolito is already stopped.',
    ERR_4 = '[Error]: Tolito is indefinite.',
    ERR_5 = '[Error]: Tolito element id is undefined',
    ERR_6 = '[Error]: Tolito is not built yet.';

TolitoConstructor = function (elementId) {
    if (elementId === undefined) {
        throw ERR_5;
    }
    this._id = elementId;
    this._defaultOuterTheme = null;
    this._defaultInnerTheme = null;
    this._defaultMax = 100;
    this._defaultStartFrom = 0;
    this._defaultInterval = 100;
    this._isBuilt = false;
    this._mini = false;
    this._isRunning = false;
    this._indefinite = false;
    return this;
}

TolitoProgressBar = function (arg) {
    return new TolitoConstructor(arg);
}

TolitoConstructor.prototype = {
    logOptions: function () {
        ((typeof console === "undefined") ? {
            log: function () {}
        } : console)
            .log(["id: '", this.getId(), "' outerTheme: '", this.getOuterTheme(), "' innerTheme: '", this.getInnerTheme(),
                "' max: '", this.getMax(), "' mini: '", this.getMini(), "' startFrom: '", this.getStartFrom(),
                "' interval: '", this.getInterval(), "' showCounter: '", this.getShowCounter(), "'", "' indefinite: '",
                this.getIndefinite(), "'"
        ].join(""));
        return this;
    },
    getId: function () {
        return this._id;
    },
    setOuterTheme: function (newTheme) {
        if (this._isBuilt) {
            throw ERR_1;
        } else {
            this._outerTheme = newTheme;
            return this;
        }
    },
    getOuterTheme: function () {
        return this._outerTheme || this._defaultOuterTheme;
    },
    setInnerTheme: function (newInnerTheme) {
        if (this._isBuilt) {
            throw ERR_1;
        } else {
            this._innerTheme = newInnerTheme;
            return this;
        }
    },
    getInnerTheme: function () {
        return this._innerTheme || this._defaultInnerTheme;
    },
    setStartFrom: function (newStartFrom) {
        if (this._isBuilt) {
            throw ERR_1;
        } else {
            this._startFrom = newStartFrom;
            return this;
        }
    },
    getStartFrom: function () {
        return this._startFrom || this._defaultStartFrom;
    },
    setMax: function (newMax) {
        if (this._isBuilt) {
            throw ERR_1;
        } else {
            this._max = newMax;
            return this;
        }
    },
    getMax: function () {
        return this._max || this._defaultMax;
    },
    isMini: function (newMini) {
        if (this._isBuilt) {
            throw ERR_1;
        } else {
            this._mini = newMini;
            return this;
        }
    },
    getMini: function () {
        return this._mini;
    },
    isIndefinite: function (newIndefinite) {
        if (this._isBuilt) {
            throw ERR_1;
        } else {
            this._indefinite = newIndefinite;
            return this;
        }
    },
    getIndefinite: function () {
        return this._indefinite;
    },
    showCounter: function (newShowCounter) {
        if (this._isBuilt) {
            throw ERR_1;
        } else {
            this._showCounter = newShowCounter;
            return this;
        }
    },
    getShowCounter: function () {
        return this._showCounter;
    },
    setInterval: function (newInterval) {
        if (this._isBuilt) {
            throw ERR_1;
        } else {
            this._interval = newInterval;
            return this;
        }
    },
    getInterval: function () {
        return this._interval || this._defaultInterval;
    },
    build: function () {
        if (this._isBuilt) {
            throw ERR_1;
        } else {
            $(['#', this.getId()].join(""))
                .progressbar({
                outerTheme: this.getOuterTheme(),
                innerTheme: this.getInnerTheme(),
                value: this.getStartFrom(),
                max: this.getMax(),
                mini: this.getMini(),
                indefinite: this.getIndefinite(),
                counter: this.getShowCounter()
            });
            this._isBuilt = true;
            return this;
        }
    },
    run: function () {
        if (this._isRunning) {
            throw ERR_2;
        } else if (this._indefinite) {
            throw ERR_4;
        } else {
            (function loop(instance) {
                instance.fillProgressBar = setTimeout((function (inst) {
                    return function () {
                        var thisValue = $(['#', inst.getId()].join(""))
                            .progressbar('option', 'value'),
                            counter = !isNaN(thisValue) ? (thisValue + 1) : 1;
                        if (counter > inst.getMax()) {
                            clearTimeout(inst.fillProgressBar);
                        } else {
                            $(['#', inst.getId()].join(""))
                                .progressbar({
                                value: counter
                            });
                            loop(inst);
                        }
                    }
                })(instance), instance.getInterval());
            })(this);
            this._isRunning = true;
            return this;
        }
    },
    stop: function () {
        if (!this._isRunning) {
            throw ERR_3;
        } else {
            clearTimeout(this.fillProgressBar);
            this._isRunning = false;
            return this;
        }
    },
    setValue: function (val) {
        if (this._indefinite) {
            throw ERR_4;
        } else {
            $(['#', this.getId()].join(""))
                .progressbar({
                value: val
            });
            return this;
        }
    },
    destroy: function () {
        if (!this._isBuilt) {
            throw ERR_6;
        } else {
            if (this.fillProgressBar) {
                clearTimeout(this.fillProgressBar);
            }
            $(document).off('complete', ['#', this.getId()].join(""));
            $(['#', this.getId()].join("")).progressbar('destroy');
            return null;
        }
    }
};
<|endoftext|>