<commit_before>// keep this global reference around to ease debugging
var debugHook = (function () {
    const fromBaseDir = modulePath => require('path').join(require('electron').remote.app.getAppPath(), modulePath);

    // included with side effects to overwrite some globals
    require(fromBaseDir('src/js/renderer/polyfills.js'));

    // external imports
    const remote = require('electron').remote;
    const _ = require('lodash');
    const settings = require('electron-settings');
    const $ = jQuery = require("jquery-migrate"); // TODO: complete migration to jQuery v3 depends on bootbox support
    $.migrateTrace = false;
    require(fromBaseDir('libs/jqbtk.js')); // extends jQuery, no further global side effects
    const bootbox = require('bootbox');

    // project imports
    const Snapshot = require(fromBaseDir('src/js/renderer/Snapshot.js'));
    const SnapshotPrinter = require(fromBaseDir('src/js/renderer/SnapshotPrinter.js'));
    const SnapshotMailer = require(fromBaseDir('src/js/renderer/SnapshotMailer.js'));
    const SnapshotListUpdater = require(fromBaseDir('src/js/renderer/SnapshotListUpdater.js'));
    const SliderInitializer = require(fromBaseDir('src/js/renderer/SliderInitializer.js'));
    const notify = require(fromBaseDir('src/js/renderer/notify.js'));
    const preprocessSnapshotList = require(fromBaseDir('src/js/renderer/preprocessSnapshotList.js'));
    const packageJson = require(fromBaseDir('package.json'));
    const migrateOldSettings = require(fromBaseDir('src/js/renderer/migrateOldSettings.js'));
    const utils = require(fromBaseDir('src/js/renderer/utils.js'));

    // initialization
    migrateOldSettings();
    const miscConfig = settings.getAll();
    let snapshots; // to be initialized after preprocessing the SNAPSHOT list

    const snapshotMailer = new SnapshotMailer(miscConfig.mail);
    snapshotMailer.testConnection()
        .then(() => notify.success('Mail server is ready to take messages', ""))
        .catch(error => notify.error('Connection to mail server failed', 'Please check the error message', error));

    let mode;

    if (miscConfig.hideCursor) {
        const lastCSS = document.styleSheets[document.styleSheets.length - 1];
        lastCSS.insertRule("* { cursor: none; }", lastCSS.cssRules.length);
    }

    window.onresize = () => {
        const sx = $(window).width() / 1920;
        const sy = $(window).height() / 1080;
        const s = Math.min(sx, sy);
        const fullHdCentered = document.getElementById("full-hd-centered");
        fullHdCentered.style.transform = `scale(${s})`;
        fullHdCentered.style.left = `${($(window).width() - 1920 * s) / 2.0}px`;
        fullHdCentered.style.top = `${($(window).height() - 1080 * s) / 2.0}px`;
    };
    window.onresize();

    document.body.ondblclick = () => {
        if (mode === "slider") {
            const win = remote.getCurrentWindow();
            const sx = win.getContentSize()[0] / 1920;
            const sy = win.getContentSize()[0] / 1080;
            const s = Math.min(sx, sy);
            win.setContentSize(Math.floor(1920 * s), Math.floor(1080 * s), true);
        }
    };
    document.body.ondblclick();

    function initKeyHandlers(swiper, slideContents) {
        document.onkeydown = e => {
            if (mode === "slider") {
                const actions = {
                    "i": about,
                    "r": () => window.location.reload(),
                    "ArrowLeft": () => swiper.slidePrev(), // left arrow
                    "ArrowRight": () => swiper.slideNext(), // right arrow
                    "ArrowDown": () => slideContents[swiper.realIndex].nested.swiper.slideNext(), // down arrow
                    "ArrowUp": () => slideContents[swiper.realIndex].nested.swiper.slidePrev() // up arrow
                };
                actions[miscConfig.snapshots.autoUpdate.hotkey] = switchToUpdateMode;
                if (typeof actions[e.key] !== 'undefined')
                    actions[e.key]();
            }
        };
    }

    function about() {
        notify.info(
            `About ${packageJson.productName}`,
            `License: ${packageJson.license}<br/>Version: ${packageJson.version}`,
            process.versions
        );
    }

    function initIdleHandlers(idleAction) {
        let idle_timer = null;
        const init_idle_timer = delay => {
            if (idle_timer != null)
                clearTimeout(idle_timer);
            idle_timer = setTimeout(idle_handler, delay);
        };
        const non_idle_handler = () => {
            console.log("non_idle_handler");
            init_idle_timer(miscConfig.idleModeDelayMs);
        };
        const idle_handler = () => {
            console.log("idle_handler");
            idleAction();
            init_idle_timer(miscConfig.idleModeDelayBetweenActionsMs);
        };

        document.body.addEventListener('mouseup', non_idle_handler, true);
        document.body.addEventListener('mousemove', non_idle_handler, true);
        document.body.addEventListener('mousedown', non_idle_handler, true);
        document.body.addEventListener("touchstart", non_idle_handler, true);
        document.body.addEventListener("touchend", non_idle_handler, true);
        document.body.addEventListener("touchcancel", non_idle_handler, true);
        document.body.addEventListener("touchmove", non_idle_handler, true);
        document.body.addEventListener("onscroll", non_idle_handler, true);
        document.body.addEventListener("keydown", non_idle_handler, true);
        document.body.addEventListener("keyup", non_idle_handler, true);
        document.body.addEventListener("keypress", non_idle_handler, true);

        init_idle_timer(miscConfig.idleModeDelayMs);
    }

    function createIdleAction(swiper, slideContents) {
        return () => {
            bootbox.hideAll();
            slideContents[(swiper.realIndex + 1) % snapshots.length].nested.swiper.slideTo(0, 0);
            slideContents[(swiper.realIndex + 2) % snapshots.length].nested.swiper.slideTo(0, 0);
            swiper.slideNext(miscConfig.idleAnimationDurationMs, true);
        }
    }

    function switchToSlider() {
        bootbox.hideAll();
        mode = "slider";
    }

    function switchToUpdateMode() {
        mode = "update";

        bootbox.confirm({
            title: 'Confirm update',
            message: '
Are you sure that you want to update the list of available SNAPSHOTS?
',
            callback: function (result) {
                if (result)
                    updateSnapshotList();
                switchToSlider();
            }
        });
    }

    function switchToMailMode(snapshot) {
        mode = "mail";
        bootbox.confirm({
            title: 'Please enter your email address',
            message: '
Select one of the input fields to open the on screen keyboard. Click outside the on screen keyboard to hide it.

Name:<input type="text" class="keyboard form-control" id="emailName">

Email:<input type="text" class="keyboard form-control" id="emailAddress">

',
            callback: result => {
                if (result) {
                    const recipientName = $('#emailName').val().trim();
                    const recipientAddress = $('#emailAddress').val().trim();
                    snapshotMailer.send(snapshot.metadata, recipientAddress, recipientName === '' ? undefined : recipientName)
                        .then(info => notify.success('Email sent successfully', "", info))
                        .catch(error => notify.error('Email could not be sent', 'Please check the error message', error));
                }
                switchToSlider();
            }
        });
        $("#emailName").keyboard();
        $("#emailAddress").keyboard();
    }

    function switchToPrintMode(snapshot) {
        mode = "print";
        bootbox.confirm({
            title: 'Confirm printing',
            message: `<p class="dialog">Do you want to print: <em>"<b>${snapshot.metadata.authors.map(a => a.trim()).join(", ")}:</b> ${snapshot.metadata.title.trim()}"</em>?<p>`,
            callback: result => {
                if (result)
                    print(snapshot);
                switchToSlider();
            }
        });
    }

    function print(snapshot) {
        return SnapshotPrinter.print(snapshot.cachedPdf)
            .then(() => notify.success('File has been sent to the printer'))
            .catch(error => notify.error('Print job failed', 'Please check the error message', error));
    }

    async function updateSnapshotList() {
        const errorNotificationTitle = "Update of SNAPSHOT list failed";
        const successNotificationTitle = "Update of SNAPSHOT list successful";

        try {
            const newSnapshotList = await SnapshotListUpdater.update(miscConfig.snapshots.autoUpdate.url);
            if (_.isEqual(newSnapshotList.snapshots, miscConfig.snapshots.articles)) {
                notify.success(successNotificationTitle, "No change detected.", newSnapshotList);
            } else {
                settings.set("snapshots.articles", newSnapshotList.snapshots, {
                    prettify: true
                });
                notify.success(successNotificationTitle, "Reloading …", newSnapshotList)
                    .then(() => location.reload());
            }
        } catch (error) {
            switch (error.errorCode) {
                case SnapshotListUpdater.UpdateError.ERROR_DOWNLOAD_FAILED:
                    notify.error(errorNotificationTitle, "The list could not be downloaded.", error.cause);
                    break;
                case SnapshotListUpdater.UpdateError.ERROR_VERSION_MISMATCH:
                    notify.error(errorNotificationTitle, "Online data has wrong version tag.", error.cause);
                    break;
                case SnapshotListUpdater.UpdateError.ERROR_VALIDATION_FAILED:
                    notify.error(errorNotificationTitle, "The downloaded list is invalid.", error.cause);
                    break;
            }
        }
    }

    // GO!
    new Promise(resolve => $(document).ready(resolve))
        .then(miscConfig.snapshots.autoUpdate.enable ? () => {
            notify.info("Starting update of SNAPSHOT list");
            return updateSnapshotList().reflect();
        } : Promise.resolve())
        .then(async () => {
            const startTime = performance.now();
            const articles = preprocessSnapshotList(miscConfig.snapshots);
            snapshots = articles.map(article => new Snapshot(article, {
                cacheDir: utils.getPath('cache'),
                defaultWidth: 717,
                defaultHeight: 1014
            }));
            notify.updateInitProgressBar("#progressCache", {
                max: snapshots.length
            });
            notify.updateInitProgressBar("#progressDocs", {
                max: snapshots.length
            });

            const cachePDFPromises = [];
            const initDocumentPromises = [];
            const initNestedSlidesPromises = [];
            const initCompletePromises = [];
            let totalNumPages = 0;
            snapshots.forEach(snapshot => {
                let article = snapshot.metadata;

                let cachePromise = snapshot.cacheDocumentPromise.catch(e => {
                    notify.error("Initialization error", `Could not cache "${article.authors.join(", ")}: ${article.title}" (doi: ${article.doi})`, e);
                    throw e;
                });
                let initDocumentPromise = snapshot.initDocumentPromise.catch(e => {
                    notify.error("Initialization error", `Failed to load "${article.authors.join(", ")}: ${article.title}" (doi: ${article.doi})`, e);
                    throw e;
                });
                let cachePagePromises = snapshot.cachePagePromises.catch(e => {
                    notify.error("Initialization error",
                        `Failed to render pages of "${article.authors.join(", ")}: ${article.title}" (doi: ${article.doi})`, e);
                    throw e;
                });

                cachePromise.finally(() => notify.updateInitProgressBar("#progressCache", {
                    now: 1,
                    offset: true
                }));
                cachePagePromises.finally(() =>
                    notify.updateInitProgressBar("#progressDocs", {
                        now: 1,
                        offset: true
                    })
                );

                initDocumentPromise = initDocumentPromise.then(document => {
                    totalNumPages += document.numPages;
                    notify.updateInitProgressBar("#progressPages", {
                        max: totalNumPages
                    });
                    notify.updateInitProgressBar("#progressRendered", {
                        max: totalNumPages
                    });
                    return document;
                });

                let initCompletePromise = cachePagePromises
                    .catch(e => {
                        console.log("Problem processing snapshot", snapshot.metadata, e);
                    })
                    .then(actualCachePagePromises => Promise.all(actualCachePagePromises));

                snapshot.initPagePromises.then(promises => promises.forEach(promise => promise.finally(
                    () => notify.updateInitProgressBar("#progressPages", {
                        now: 1,
                        offset: true
                    }))));

                snapshot.cachePagePromises.then(promises => promises.forEach(promise => promise.finally(
                    () => notify.updateInitProgressBar("#progressRendered", {
                        now: 1,
                        offset: true
                    }))));

                cachePDFPromises.push(cachePromise);
                initDocumentPromises.push(initDocumentPromise);
                initNestedSlidesPromises.push(cachePagePromises);
                initCompletePromises.push(initCompletePromise);
            });

            // create the slider and add it to the DOM (final initialization is done upon insertion into the DOM)
            const {swiper, slideContents} = await SliderInitializer.createTopLevelSlider(snapshots, {
                onMail: miscConfig.mail.enable ? snapshot => switchToMailMode(snapshot) : undefined,
                onPrint: miscConfig.print.enable ? snapshot => switchToPrintMode(snapshot) : undefined,
            });
            document.querySelector('#full-hd-centered').appendChild(swiper.el);

            Promise.all(initNestedSlidesPromises).catch(() => notify.warning("Initialization incomplete", "Some SNAPSHOTS will be unavailable."));
            Promise.all(initCompletePromises)
                .then(notify.closeInitProgressBar)
                .then(() => {
                    initIdleHandlers(createIdleAction(swiper, slideContents));
                    initKeyHandlers(swiper, slideContents);
                    switchToSlider();

                    console.log(`loading took ${(performance.now() - startTime) / 1000.0}s`)
                });
        });

})();
<commit_msg>Split key handlers into two: global and slider<commit_after>// keep this global reference around to ease debugging
var debugHook = (function () {
    const fromBaseDir = modulePath => require('path').join(require('electron').remote.app.getAppPath(), modulePath);

    // included with side effects to overwrite some globals
    require(fromBaseDir('src/js/renderer/polyfills.js'));

    // external imports
    const remote = require('electron').remote;
    const _ = require('lodash');
    const settings = require('electron-settings');
    const $ = jQuery = require("jquery-migrate"); // TODO: complete migration to jQuery v3 depends on bootbox support
    $.migrateTrace = false;
    require(fromBaseDir('libs/jqbtk.js')); // extends jQuery, no further global side effects
    const bootbox = require('bootbox');

    // project imports
    const Snapshot = require(fromBaseDir('src/js/renderer/Snapshot.js'));
    const SnapshotPrinter = require(fromBaseDir('src/js/renderer/SnapshotPrinter.js'));
    const SnapshotMailer = require(fromBaseDir('src/js/renderer/SnapshotMailer.js'));
    const SnapshotListUpdater = require(fromBaseDir('src/js/renderer/SnapshotListUpdater.js'));
    const SliderInitializer = require(fromBaseDir('src/js/renderer/SliderInitializer.js'));
    const notify = require(fromBaseDir('src/js/renderer/notify.js'));
    const preprocessSnapshotList = require(fromBaseDir('src/js/renderer/preprocessSnapshotList.js'));
    const packageJson = require(fromBaseDir('package.json'));
    const migrateOldSettings = require(fromBaseDir('src/js/renderer/migrateOldSettings.js'));
    const utils = require(fromBaseDir('src/js/renderer/utils.js'));

    // initialization
    migrateOldSettings();
    const miscConfig = settings.getAll();
    let snapshots; // to be initialized after preprocessing the SNAPSHOT list

    const snapshotMailer = new SnapshotMailer(miscConfig.mail);
    snapshotMailer.testConnection()
        .then(() => notify.success('Mail server is ready to take messages', ""))
        .catch(error => notify.error('Connection to mail server failed', 'Please check the error message', error));

    let mode;

    if (miscConfig.hideCursor) {
        const lastCSS = document.styleSheets[document.styleSheets.length - 1];
        lastCSS.insertRule("* { cursor: none; }", lastCSS.cssRules.length);
    }

    window.onresize = () => {
        const sx = $(window).width() / 1920;
        const sy = $(window).height() / 1080;
        const s = Math.min(sx, sy);
        const fullHdCentered = document.getElementById("full-hd-centered");
        fullHdCentered.style.transform = `scale(${s})`;
        fullHdCentered.style.left = `${($(window).width() - 1920 * s) / 2.0}px`;
        fullHdCentered.style.top = `${($(window).height() - 1080 * s) / 2.0}px`;
    };
    window.onresize();

    document.body.ondblclick = () => {
        if (mode === "slider") {
            const win = remote.getCurrentWindow();
            const sx = win.getContentSize()[0] / 1920;
            const sy = win.getContentSize()[0] / 1080;
            const s = Math.min(sx, sy);
            win.setContentSize(Math.floor(1920 * s), Math.floor(1080 * s), true);
        }
    };
    document.body.ondblclick();

    function initGlobalKeyHandlers() {
        const actions = {
            "i": about,
            "r": () => window.location.reload(),
        };
        document.addEventListener('keydown', e => {
            if (typeof actions[e.key] !== 'undefined')
                actions[e.key]();
        });
    }

    function initSliderKeyHandlers(swiper, slideContents) {
        const actions = {
            "ArrowLeft": () => swiper.slidePrev(), // left arrow
            "ArrowRight": () => swiper.slideNext(), // right arrow
            "ArrowDown": () => slideContents[swiper.realIndex].nested.swiper.slideNext(), // down arrow
            "ArrowUp": () => slideContents[swiper.realIndex].nested.swiper.slidePrev() // up arrow
        };
        actions[miscConfig.snapshots.autoUpdate.hotkey] = switchToUpdateMode;
        document.addEventListener('keydown', e => {
            if (mode === 'slider' && typeof actions[e.key] !== 'undefined')
                actions[e.key]();
        });
    }

    function about() {
        notify.info(
            `About ${packageJson.productName}`,
            `License: ${packageJson.license}<br/>Version: ${packageJson.version}`,
            process.versions
        );
    }

    function initIdleHandlers(idleAction) {
        let idle_timer = null;
        const init_idle_timer = delay => {
            if (idle_timer != null)
                clearTimeout(idle_timer);
            idle_timer = setTimeout(idle_handler, delay);
        };
        const non_idle_handler = () => {
            console.log("non_idle_handler");
            init_idle_timer(miscConfig.idleModeDelayMs);
        };
        const idle_handler = () => {
            console.log("idle_handler");
            idleAction();
            init_idle_timer(miscConfig.idleModeDelayBetweenActionsMs);
        };

        document.body.addEventListener('mouseup', non_idle_handler, true);
        document.body.addEventListener('mousemove', non_idle_handler, true);
        document.body.addEventListener('mousedown', non_idle_handler, true);
        document.body.addEventListener("touchstart", non_idle_handler, true);
        document.body.addEventListener("touchend", non_idle_handler, true);
        document.body.addEventListener("touchcancel", non_idle_handler, true);
        document.body.addEventListener("touchmove", non_idle_handler, true);
        document.body.addEventListener("onscroll", non_idle_handler, true);
        document.body.addEventListener("keydown", non_idle_handler, true);
        document.body.addEventListener("keyup", non_idle_handler, true);
        document.body.addEventListener("keypress", non_idle_handler, true);

        init_idle_timer(miscConfig.idleModeDelayMs);
    }

    function createIdleAction(swiper, slideContents) {
        return () => {
            bootbox.hideAll();
            slideContents[(swiper.realIndex + 1) % snapshots.length].nested.swiper.slideTo(0, 0);
            slideContents[(swiper.realIndex + 2) % snapshots.length].nested.swiper.slideTo(0, 0);
            swiper.slideNext(miscConfig.idleAnimationDurationMs, true);
        }
    }

    function switchToSlider() {
        bootbox.hideAll();
        mode = "slider";
    }

    function switchToUpdateMode() {
        mode = "update";

        bootbox.confirm({
            title: 'Confirm update',
            message: '
Are you sure that you want to update the list of available SNAPSHOTS?
',
            callback: function (result) {
                if (result)
                    updateSnapshotList();
                switchToSlider();
            }
        });
    }

    function switchToMailMode(snapshot) {
        mode = "mail";
        bootbox.confirm({
            title: 'Please enter your email address',
            message: '
Select one of the input fields to open the on screen keyboard. Click outside the on screen keyboard to hide it.

Name:<input type="text" class="keyboard form-control" id="emailName">

Email:<input type="text" class="keyboard form-control" id="emailAddress">

',
            callback: result => {
                if (result) {
                    const recipientName = $('#emailName').val().trim();
                    const recipientAddress = $('#emailAddress').val().trim();
                    snapshotMailer.send(snapshot.metadata, recipientAddress, recipientName === '' ? undefined : recipientName)
                        .then(info => notify.success('Email sent successfully', "", info))
                        .catch(error => notify.error('Email could not be sent', 'Please check the error message', error));
                }
                switchToSlider();
            }
        });
        $("#emailName").keyboard();
        $("#emailAddress").keyboard();
    }

    function switchToPrintMode(snapshot) {
        mode = "print";
        bootbox.confirm({
            title: 'Confirm printing',
            message: `<p class="dialog">Do you want to print: <em>"<b>${snapshot.metadata.authors.map(a => a.trim()).join(", ")}:</b> ${snapshot.metadata.title.trim()}"</em>?<p>`,
            callback: result => {
                if (result)
                    print(snapshot);
                switchToSlider();
            }
        });
    }

    function print(snapshot) {
        return SnapshotPrinter.print(snapshot.cachedPdf)
            .then(() => notify.success('File has been sent to the printer'))
            .catch(error => notify.error('Print job failed', 'Please check the error message', error));
    }

    async function updateSnapshotList() {
        const errorNotificationTitle = "Update of SNAPSHOT list failed";
        const successNotificationTitle = "Update of SNAPSHOT list successful";

        try {
            const newSnapshotList = await SnapshotListUpdater.update(miscConfig.snapshots.autoUpdate.url);
            if (_.isEqual(newSnapshotList.snapshots, miscConfig.snapshots.articles)) {
                notify.success(successNotificationTitle, "No change detected.", newSnapshotList);
            } else {
                settings.set("snapshots.articles", newSnapshotList.snapshots, {
                    prettify: true
                });
                notify.success(successNotificationTitle, "Reloading …", newSnapshotList)
                    .then(() => location.reload());
            }
        } catch (error) {
            switch (error.errorCode) {
                case SnapshotListUpdater.UpdateError.ERROR_DOWNLOAD_FAILED:
                    notify.error(errorNotificationTitle, "The list could not be downloaded.", error.cause);
                    break;
                case SnapshotListUpdater.UpdateError.ERROR_VERSION_MISMATCH:
                    notify.error(errorNotificationTitle, "Online data has wrong version tag.", error.cause);
                    break;
                case SnapshotListUpdater.UpdateError.ERROR_VALIDATION_FAILED:
                    notify.error(errorNotificationTitle, "The downloaded list is invalid.", error.cause);
                    break;
            }
        }
    }

    // GO!
    new Promise(resolve => $(document).ready(resolve))
        .then(initGlobalKeyHandlers)
        .then(miscConfig.snapshots.autoUpdate.enable ? () => {
            notify.info("Starting update of SNAPSHOT list");
            return updateSnapshotList().reflect();
        } : Promise.resolve())
        .then(async () => {
            const startTime = performance.now();
            const articles = preprocessSnapshotList(miscConfig.snapshots);
            snapshots = articles.map(article => new Snapshot(article, {
                cacheDir: utils.getPath('cache'),
                defaultWidth: 717,
                defaultHeight: 1014
            }));
            notify.updateInitProgressBar("#progressCache", {
                max: snapshots.length
            });
            notify.updateInitProgressBar("#progressDocs", {
                max: snapshots.length
            });

            const cachePDFPromises = [];
            const initDocumentPromises = [];
            const initNestedSlidesPromises = [];
            const initCompletePromises = [];
            let totalNumPages = 0;
            snapshots.forEach(snapshot => {
                let article = snapshot.metadata;

                let cachePromise = snapshot.cacheDocumentPromise.catch(e => {
                    notify.error("Initialization error", `Could not cache "${article.authors.join(", ")}: ${article.title}" (doi: ${article.doi})`, e);
                    throw e;
                });
                let initDocumentPromise = snapshot.initDocumentPromise.catch(e => {
                    notify.error("Initialization error", `Failed to load "${article.authors.join(", ")}: ${article.title}" (doi: ${article.doi})`, e);
                    throw e;
                });
                let cachePagePromises = snapshot.cachePagePromises.catch(e => {
                    notify.error("Initialization error",
                        `Failed to render pages of "${article.authors.join(", ")}: ${article.title}" (doi: ${article.doi})`, e);
                    throw e;
                });

                cachePromise.finally(() => notify.updateInitProgressBar("#progressCache", {
                    now: 1,
                    offset: true
                }));
                cachePagePromises.finally(() =>
                    notify.updateInitProgressBar("#progressDocs", {
                        now: 1,
                        offset: true
                    })
                );

                initDocumentPromise = initDocumentPromise.then(document => {
                    totalNumPages += document.numPages;
                    notify.updateInitProgressBar("#progressPages", {
                        max: totalNumPages
                    });
                    notify.updateInitProgressBar("#progressRendered", {
                        max: totalNumPages
                    });
                    return document;
                });

                let initCompletePromise = cachePagePromises
                    .catch(e => {
                        console.log("Problem processing snapshot", snapshot.metadata, e);
                    })
                    .then(actualCachePagePromises => Promise.all(actualCachePagePromises));

                snapshot.initPagePromises.then(promises => promises.forEach(promise => promise.finally(
                    () => notify.updateInitProgressBar("#progressPages", {
                        now: 1,
                        offset: true
                    }))));

                snapshot.cachePagePromises.then(promises => promises.forEach(promise => promise.finally(
                    () => notify.updateInitProgressBar("#progressRendered", {
                        now: 1,
                        offset: true
                    }))));

                cachePDFPromises.push(cachePromise);
                initDocumentPromises.push(initDocumentPromise);
                initNestedSlidesPromises.push(cachePagePromises);
                initCompletePromises.push(initCompletePromise);
            });

            // create the slider and add it to the DOM (final initialization is done upon insertion into the DOM)
            const {swiper, slideContents} = await SliderInitializer.createTopLevelSlider(snapshots, {
                onMail: miscConfig.mail.enable ? snapshot => switchToMailMode(snapshot) : undefined,
                onPrint: miscConfig.print.enable ? snapshot => switchToPrintMode(snapshot) : undefined,
            });
            document.querySelector('#full-hd-centered').appendChild(swiper.el);

            Promise.all(initNestedSlidesPromises).catch(() => notify.warning("Initialization incomplete", "Some SNAPSHOTS will be unavailable."));
            Promise.all(initCompletePromises)
                .then(notify.closeInitProgressBar)
                .then(() => {
                    initIdleHandlers(createIdleAction(swiper, slideContents));
                    initSliderKeyHandlers(swiper, slideContents);
                    switchToSlider();

                    console.log(`loading took ${(performance.now() - startTime) / 1000.0}s`)
                });
        });

})();
<|endoftext|>


<commit_before>'use strict';

var url = require('url');

module.exports = function(shariff) {

    var shareUrl = url.parse(shariff.getURL(), true);
    shareUrl.query.view = 'mail';
    delete shareUrl.search;

    return {
        popup: false,
        shareText: 'mail',
        name: 'mail',
        faName: 'fa-envelope',
        title: {
            'de': 'Per E-Mail versenden',
            'en': 'Send by email',
            'es': 'Enviar por email'
        },
        shareUrl: url.format(shareUrl)
    };
};
<commit_msg>Add fr,it,da,nl<commit_after>'use strict';

var url = require('url');

module.exports = function(shariff) {

    var shareUrl = url.parse(shariff.getURL(), true);
    shareUrl.query.view = 'mail';
    delete shareUrl.search;

    return {
        popup: false,
        shareText: 'mail',
        name: 'mail',
        faName: 'fa-envelope',
        title: {
            'de': 'Per E-Mail versenden',
            'en': 'Send by email',
            'es': 'Enviar por email',
            'fr': 'Envoyer par courriel',
            'it': 'Inviare via email',
            'da': 'Sende via e-mail',
            'nl': 'Sturen via e-mail '
        },
        shareUrl: url.format(shareUrl)
    };
};
<|endoftext|>


<commit_before>YUI.add("view-timeline", function(Y) {
    "use strict";

    var moment    = require("moment"),

        tristis    = Y.namespace("Tristis"),
        extensions = Y.namespace("Tristis.Extensions"),
        templates  = Y.namespace("Tristis.Templates"),

        options, Timeline;

    options = {
        "data-external" : ""
    };

    moment.lang("en", {
        relativeTime : {
            // our custom values
            s  : "s",
            m  : "1m",
            mm : "%dm",
            h  : "1h",
            hh : "%dh",

            // default moment.js values
            future : "in %s",
            past   : "%s ago",
            d      : "a day",
            dd     : "%d days",
            M      : "a month",
            MM     : "%d months",
            y      : "a year",
            yy     : "%d years"
        }
    });

    Timeline = Y.Base.create("timeline", Y.View, [
        extensions.TimelineMenus,
        extensions.TweetActions
    ], {
        template : templates.timeline,

        initializer : function() {
            var model = this.get("model");

            this._handles = [
                model.after("reset", this.render, this),
                model.get("tweets").after([ "reset", "add" ], this._renderUpdate, this)
            ];
        },

        destructor : function() {
            new Y.EventTarget(this._handles).detach();

            this._handles = null;
        },

        render : function() {
            var timeline = this.get("model").toJSON();

            this.get("container").setHTML(
                this.template(
                    Y.merge(
                        timeline, {
                            tweets : this._renderTweets(timeline.tweets),
                            _t     : templates
                        }
                    )
                )
            );

            this.rendered = true;

            return this;
        },

        _renderUpdate : function(e) {
            var models;

            if(!this.rendered) {
                return this.render();
            }

            models = e.models ? e.models : [ e.model ];

            this.get("container").one("ol").prepend(
                this._renderTweets(models)
            );
        },

        _renderTweets : function(tweets) {
            var now = moment();

            return tweets.reduce(function(prev, tweet) {
                var time = moment(tweet.created_at),
                    diff = now.diff(time, "hours");

                tweet.html = tristis.txt.autoLinkWithJSON(tweet.text, tweet.entities, options);

                // TODO: .fromNow is a little over-eager about showing "1 day", should figure out why
                tweet.date = diff > 24 ? time.format("DD MMM") : time.fromNow(true);

                return prev + templates.tweet(tweet);
            }, "");
        }
    });

    Y.namespace("Tristis.Views").Timeline = Timeline;

}, "@VERSION@", {
    requires : [
        // YUI
        "base-build",
        "view",

        // Extensions
        "extension-timeline-menus",
        "extension-tweet-actions",

        // Templates
        "template-timeline",
        "template-tweet",

        // CSS
        "css-timeline",
        "css-tweet",
        "css-icons"
    ]
});
<commit_msg>Fixed seconds display issue, listening to more events<commit_after>YUI.add("view-timeline", function(Y) {
    "use strict";

    var moment    = require("moment"),

        tristis    = Y.namespace("Tristis"),
        extensions = Y.namespace("Tristis.Extensions"),
        templates  = Y.namespace("Tristis.Templates"),

        options, Timeline;

    options = {
        "data-external" : ""
    };

    moment.lang("en", {
        relativeTime : {
            // our custom values
            s  : "%ds",
            m  : "1m",
            mm : "%dm",
            h  : "1h",
            hh : "%dh",

            // default moment.js values
            future : "in %s",
            past   : "%s ago",
            d      : "a day",
            dd     : "%d days",
            M      : "a month",
            MM     : "%d months",
            y      : "a year",
            yy     : "%d years"
        }
    });

    Timeline = Y.Base.create("timeline", Y.View, [
        extensions.TimelineMenus,
        extensions.TweetActions
    ], {
        template : templates.timeline,

        initializer : function() {
            var model = this.get("model");

            this._handles = [
                model.after("reset", this.render, this),
                model.get("tweets").after([ "reset", "add", "more" ], this._renderUpdate, this)
            ];
        },

        destructor : function() {
            new Y.EventTarget(this._handles).detach();

            this._handles = null;
        },

        render : function() {
            var timeline = this.get("model").toJSON();

            this.get("container").setHTML(
                this.template(
                    Y.merge(
                        timeline, {
                            tweets : this._renderTweets(timeline.tweets),
                            _t     : templates
                        }
                    )
                )
            );

            this.rendered = true;

            return this;
        },

        _renderUpdate : function(e) {
            var models;

            if(!this.rendered) {
                return this.render();
            }

            models = e.models || e.parsed || [ e.model ];

            this.get("container").one("ol").prepend(
                this._renderTweets(models)
            );
        },

        _renderTweets : function(tweets) {
            var now = moment();

            return tweets.reduce(function(prev, tweet) {
                var time = moment(tweet.created_at),
                    diff = now.diff(time, "hours");

                tweet.html = tristis.txt.autoLinkWithJSON(tweet.text, tweet.entities, options);

                // TODO: .fromNow is a little over-eager about showing "1 day", should figure out why
                tweet.date = diff > 24 ? time.format("DD MMM") : time.fromNow(true);

                return prev + templates.tweet(tweet);
            }, "");
        }
    });

    Y.namespace("Tristis.Views").Timeline = Timeline;

}, "@VERSION@", {
    requires : [
        // YUI
        "base-build",
        "view",

        // Extensions
        "extension-timeline-menus",
        "extension-tweet-actions",

        // Templates
        "template-timeline",
        "template-tweet",

        // CSS
        "css-timeline",
        "css-tweet",
        "css-icons"
    ]
});
<|endoftext|>


<commit_before>
import { EventDispatcher } from '../Three';

function Routes ( metadataSource ) {

	// determine segments between junctions and entrances/passage ends and create mapping array.

	this.metadataSource = metadataSource;
	this.surveyTree = null;
	this.vertexPairToSegment = []; // maps vertex index to segment membership
	this.segmentMap = new Map(); // maps segments of survey between ends of passages and junctions.
	this.segmentToInfo = {};

	this.routes = new Map();
	this.routeNames = [];

	this.currentRoute = new Set();
	this.currentRouteName = null;
	this.adjacentSegments = new Set();
	this.maxDistance = 0;

	this.stations = null;
	this.legs = null;

	Object.defineProperty( this, 'setRoute', {
		set: function ( x ) { this.loadRoute( x ); },
		get: function () { return this.currentRouteName; }
	} );

	const routes = metadataSource.getRoutes();
	const routeNames = this.routeNames;

	var routeName;

	for ( routeName in routes ) {

		const route = routes[ routeName ];

		routeNames.push( routeName );
		this.routes.set( routeName, route.segments );

	}

	routeNames.sort();

	this.dispatchEvent( { type: 'changed', name: 'download' } );

}

Object.assign( Routes.prototype, EventDispatcher.prototype );

Routes.prototype.mapSurvey = function ( stations, legsObject, surveyTree ) {

	// determine segments between junctions and entrances/passage ends and create mapping array.
	this.surveyTree = surveyTree;
	this.stations = stations;
	this.legsObject = legsObject;

	const legs = legsObject.geometry.vertices;
	const segmentMap = this.segmentMap;
	const vertexPairToSegment = this.vertexPairToSegment;
	const segmentToInfo = this.segmentToInfo;
	const l = legs.length;

	var station;
	var newSegment = true;
	var segment = 0;
	var segmentInfo;
	var i;

	for ( i = 0; i < l; i = i + 2 ) {

		const v1 = legs[ i ];
		const v2 = legs[ i + 1 ];

		vertexPairToSegment.push( segment );

		station = stations.getStation( v1 );

		if ( station !== undefined ) {

			station.legs.push( i );
			station.linkedSegments.push( segment );

		}

		if ( newSegment ) {

			if ( station === undefined ) continue; // possible use of separator in station name.

			segmentInfo = {
				segment: segment,
				startStation: station,
				endStation: null,
			};

			newSegment = false;

		}

		station = stations.getStation( v2 );
		if ( station !== undefined ) station.legs.push( i );

		if ( station && ( v2.connections > 2 || ( i + 2 < l && ! v2.equals( legs[ i + 2 ] ) ) ) ) {

			// we have found a junction or a passage end
			segmentInfo.endStation = station;

			segmentMap.set( segmentInfo.startStation.id + ':' + station.id, segmentInfo );
			segmentToInfo[ segment ] = segmentInfo;

			station.linkedSegments.push( segment );

			segment++;

			newSegment = true;

		}

	}

	if ( ! newSegment ) {

		segmentInfo.endStation = station;

		segmentMap.set( segmentInfo.startStation.id + ':' + station.id, segmentInfo );

		station.linkedSegments.push( segment );

	}

	return this;

};

Routes.prototype.addRoute = function ( routeName ) {

	if ( routeName === this.currentRouteName || routeName === undefined ) return;

	if ( this.routeNames.indexOf( routeName ) < 0 ) {

		// create entry for empty route if a new name

		this.routeNames.push( routeName );
		this.routes.set( routeName, [] );

	}

	this.loadRoute( routeName );

};

Routes.prototype.loadRoute = function ( routeName ) {

	const self = this;

	const surveyTree = this.surveyTree;
	const currentRoute = this.currentRoute;
	const segmentMap = this.segmentMap;
	const routeSegments = this.routes.get( routeName );

	var i;

	if ( ! routeSegments ) {

		alert( 'route ' + routeName + ' does not exist' );
		return false;

	}

	currentRoute.clear();

	for ( i = 0; i < routeSegments.length; i++ ) {

		const segment = routeSegments[ i ];

		const map = segmentMap.get( surveyTree.getIdByPath( segment.start ) + ':' + surveyTree.getIdByPath( segment.end ) );

		if ( map !== undefined ) currentRoute.add( map.segment );

	}

	this.currentRouteName = routeName;

	self.dispatchEvent( { type: 'changed', name: '' } );

	return true;

};

Routes.prototype.getCurrentRoute = function () {

	return this.currentRoute;

};

Routes.prototype.saveCurrent = function () {

	const routeName = this.currentRouteName;
	const segmentMap = this.segmentMap;
	const route = this.currentRoute;

	if ( ! routeName ) return;

	const routeSegments = [];

	segmentMap.forEach( _addRoute );

	// update in memory route

	this.routes.set( routeName, routeSegments );

	// update persistant browser storage

	this.metadataSource.saveRoute( routeName, { segments: routeSegments } );

	function _addRoute ( value /*, key */ ) {

		if ( route.has( value.segment ) ) {

			routeSegments.push( {
				start: value.startStation.getPath(),
				end: value.endStation.getPath()
			} );

		}

	}

};

Routes.prototype.getRouteNames = function () {

	return this.routeNames;

};

Routes.prototype.toggleSegment = function ( index ) {

	const self = this;
	const route = this.currentRoute;
	const segment = this.vertexPairToSegment[ index / 2 ];

	this.adjacentSegments.clear();

	if ( route.has( segment ) ) {

		route.delete( segment );

	} else {

		route.add( segment );

		// handle adjacent segments to the latest segment toggled 'on'

		const segmentInfo = this.segmentToInfo[ segment ];

		if ( segmentInfo !== undefined ) {

			segmentInfo.startStation.linkedSegments.forEach( _setAdjacentSegments );
			segmentInfo.endStation.linkedSegments.forEach( _setAdjacentSegments );

		}

	}

	return;

	function _setAdjacentSegments ( segment ) {

		if ( ! route.has( segment ) ) self.adjacentSegments.add( segment );

	}

};

Routes.prototype.inCurrentRoute = function ( index ) {

	return this.currentRoute.has( this.vertexPairToSegment[ index / 2 ] );

};

Routes.prototype.adjacentToRoute = function ( index ) {

	return this.adjacentSegments.has( this.vertexPairToSegment[ index / 2 ] );

};

Routes.prototype.shortestPathSearch = function ( station ) {

	// queue of stations searched.
	const queue = [ station ];

	const legsObject = this.legsObject;
	const legs = legsObject.geometry.vertices;
	const stations = this.stations;

	stations.resetDistances();

	var maxDistance = 0;

	station.distance = 0;

	while ( queue.length > 0 ) {

		const station = queue.shift();
		const currentDistance = station.distance;
		const stationLegs = station.legs;

		// console.log( 'station:', station.getPath(), currentDistance );

		maxDistance = Math.max( maxDistance, currentDistance );

		let i;

		// find stations connected to this station
		for ( i = 0; i < stationLegs.length; i++ ) {

			const leg = stationLegs[ i ];

			const v1 = legs[ leg ];
			const v2 = legs[ leg + 1 ];

			const nextVertex = ( v1 !== station.p ) ? v1 : v2;
			const nextStation = stations.getStation( nextVertex );
			const nextLength = legsObject.legLengths[ leg / 2 ];

			// label stations with distance of shortest path
			// add to search list

			if ( nextStation.distance > currentDistance + nextLength ) {

				nextStation.distance = currentDistance + nextLength;
				queue.push( nextStation );

				// console.log( 'new next', nextStation.distance, queue.length );

			}

		}

	}

	// console.log( 'max:', maxDistance );
	this.maxDistance = maxDistance;

};

export { Routes };

<commit_msg>add method to record legs on shortest path<commit_after>
import { EventDispatcher } from '../Three';

function Routes ( metadataSource ) {

	// determine segments between junctions and entrances/passage ends and create mapping array.

	this.metadataSource = metadataSource;
	this.surveyTree = null;
	this.vertexPairToSegment = []; // maps vertex index to segment membership
	this.segmentMap = new Map(); // maps segments of survey between ends of passages and junctions.
	this.segmentToInfo = {};

	this.routes = new Map();
	this.routeNames = [];

	this.currentRoute = new Set();
	this.currentRouteName = null;
	this.adjacentSegments = new Set();
	this.maxDistance = 0;
	this.zeroStation = null;

	this.stations = null;
	this.legs = null;

	Object.defineProperty( this, 'setRoute', {
		set: function ( x ) { this.loadRoute( x ); },
		get: function () { return this.currentRouteName; }
	} );

	const routes = metadataSource.getRoutes();
	const routeNames = this.routeNames;

	var routeName;

	for ( routeName in routes ) {

		const route = routes[ routeName ];

		routeNames.push( routeName );
		this.routes.set( routeName, route.segments );

	}

	routeNames.sort();

	this.dispatchEvent( { type: 'changed', name: 'download' } );

}

Object.assign( Routes.prototype, EventDispatcher.prototype );

Routes.prototype.mapSurvey = function ( stations, legsObject, surveyTree ) {

	// determine segments between junctions and entrances/passage ends and create mapping array.
	this.surveyTree = surveyTree;
	this.stations = stations;
	this.legsObject = legsObject;

	const legs = legsObject.geometry.vertices;
	const segmentMap = this.segmentMap;
	const vertexPairToSegment = this.vertexPairToSegment;
	const segmentToInfo = this.segmentToInfo;
	const l = legs.length;

	var station;
	var newSegment = true;
	var segment = 0;
	var segmentInfo;
	var i;

	for ( i = 0; i < l; i = i + 2 ) {

		const v1 = legs[ i ];
		const v2 = legs[ i + 1 ];

		vertexPairToSegment.push( segment );

		station = stations.getStation( v1 );

		if ( station !== undefined ) {

			station.legs.push( i );
			station.linkedSegments.push( segment );

		}

		if ( newSegment ) {

			if ( station === undefined ) continue; // possible use of separator in station name.

			segmentInfo = {
				segment: segment,
				startStation: station,
				endStation: null,
			};

			newSegment = false;

		}

		station = stations.getStation( v2 );
		if ( station !== undefined ) station.legs.push( i );

		if ( station && ( v2.connections > 2 || ( i + 2 < l && ! v2.equals( legs[ i + 2 ] ) ) ) ) {

			// we have found a junction or a passage end
			segmentInfo.endStation = station;

			segmentMap.set( segmentInfo.startStation.id + ':' + station.id, segmentInfo );
			segmentToInfo[ segment ] = segmentInfo;

			station.linkedSegments.push( segment );

			segment++;

			newSegment = true;

		}

	}

	if ( ! newSegment ) {

		segmentInfo.endStation = station;

		segmentMap.set( segmentInfo.startStation.id + ':' + station.id, segmentInfo );

		station.linkedSegments.push( segment );

	}

	return this;

};

Routes.prototype.addRoute = function ( routeName ) {

	if ( routeName === this.currentRouteName || routeName === undefined ) return;

	if ( this.routeNames.indexOf( routeName ) < 0 ) {

		// create entry for empty route if a new name

		this.routeNames.push( routeName );
		this.routes.set( routeName, [] );

	}

	this.loadRoute( routeName );

};

Routes.prototype.loadRoute = function ( routeName ) {

	const self = this;

	const surveyTree = this.surveyTree;
	const currentRoute = this.currentRoute;
	const segmentMap = this.segmentMap;
	const routeSegments = this.routes.get( routeName );

	var i;

	if ( ! routeSegments ) {

		alert( 'route ' + routeName + ' does not exist' );
		return false;

	}

	currentRoute.clear();

	for ( i = 0; i < routeSegments.length; i++ ) {

		const segment = routeSegments[ i ];

		const map = segmentMap.get( surveyTree.getIdByPath( segment.start ) + ':' + surveyTree.getIdByPath( segment.end ) );

		if ( map !== undefined ) currentRoute.add( map.segment );

	}

	this.currentRouteName = routeName;

	self.dispatchEvent( { type: 'changed', name: '' } );

	return true;

};

Routes.prototype.getCurrentRoute = function () {

	return this.currentRoute;

};

Routes.prototype.saveCurrent = function () {

	const routeName = this.currentRouteName;
	const segmentMap = this.segmentMap;
	const route = this.currentRoute;

	if ( ! routeName ) return;

	const routeSegments = [];

	segmentMap.forEach( _addRoute );

	// update in memory route

	this.routes.set( routeName, routeSegments );

	// update persistant browser storage

	this.metadataSource.saveRoute( routeName, { segments: routeSegments } );

	function _addRoute ( value /*, key */ ) {

		if ( route.has( value.segment ) ) {

			routeSegments.push( {
				start: value.startStation.getPath(),
				end: value.endStation.getPath()
			} );

		}

	}

};

Routes.prototype.getRouteNames = function () {

	return this.routeNames;

};

Routes.prototype.toggleSegment = function ( index ) {

	const self = this;
	const route = this.currentRoute;
	const segment = this.vertexPairToSegment[ index / 2 ];

	this.adjacentSegments.clear();

	if ( route.has( segment ) ) {

		route.delete( segment );

	} else {

		route.add( segment );

		// handle adjacent segments to the latest segment toggled 'on'

		const segmentInfo = this.segmentToInfo[ segment ];

		if ( segmentInfo !== undefined ) {

			segmentInfo.startStation.linkedSegments.forEach( _setAdjacentSegments );
			segmentInfo.endStation.linkedSegments.forEach( _setAdjacentSegments );

		}

	}

	return;

	function _setAdjacentSegments ( segment ) {

		if ( ! route.has( segment ) ) self.adjacentSegments.add( segment );

	}

};

Routes.prototype.inCurrentRoute = function ( index ) {

	return this.currentRoute.has( this.vertexPairToSegment[ index / 2 ] );

};

Routes.prototype.adjacentToRoute = function ( index ) {

	return this.adjacentSegments.has( this.vertexPairToSegment[ index / 2 ] );

};

Routes.prototype.shortestPathSearch = function ( station ) {

	// queue of stations searched.
	const queue = [ station ];

	const legsObject = this.legsObject;
	const legs = legsObject.geometry.vertices;
	const stations = this.stations;

	stations.resetDistances();

	var maxDistance = 0;

	station.distance = 0;

	while ( queue.length > 0 ) {

		const station = queue.shift();
		const currentDistance = station.distance;
		const stationLegs = station.legs;

		// console.log( 'station:', station.getPath(), currentDistance );

		maxDistance = Math.max( maxDistance, currentDistance );

		let i;

		// find stations connected to this station
		for ( i = 0; i < stationLegs.length; i++ ) {

			const leg = stationLegs[ i ];

			const v1 = legs[ leg ];
			const v2 = legs[ leg + 1 ];

			const nextVertex = ( v1 !== station.p ) ? v1 : v2;
			const nextStation = stations.getStation( nextVertex );
			const nextLength = legsObject.legLengths[ leg / 2 ];

			// label stations with distance of shortest path
			// add to search list

			if ( nextStation.distance > currentDistance + nextLength ) {

				nextStation.distance = currentDistance + nextLength;
				queue.push( nextStation );

				// console.log( 'new next', nextStation.distance, queue.length );

			}

		}

	}

	// console.log( 'max:', maxDistance );
	this.zeroStation = station;
	this.maxDistance = maxDistance;

};

Routes.prototype.getShortestPath = function ( startStation ) {

	const zeroStation = this.zeroStation;

	if ( this.zeroStation === null || startStation.distance === Infinity ) return;

	const stations = this.stations;
	const legsObject = this.legsObject;
	const legs = legsObject.geometry.vertices;
	const path = new Set();

	var nextStation = startStation;
	var testNext = true;

	// for each station find station with shortest distance to zeroStation

	while ( testNext ) {

		const stationLegs = nextStation.legs;
		const l = stationLegs.length;

		let i;

		for ( i = 0; i < l; i++ ) {

			const leg = stationLegs[ i ];

			const v1 = legs[ leg ];
			const v2 = legs[ leg + 1 ];

			const nextVertex = ( v1 !== nextStation.p ) ? v1 : v2;
			const testStation = stations.getStation( nextVertex );

			if ( testStation.distance < nextStation.distance ) {

				nextStation = testStation;
				path.add( leg );

				if ( nextStation === zeroStation ) testNext = false;

			}

		}

	}

	return path;

};

export { Routes };

<|endoftext|>


<commit_before>(function (FMS, Backbone, _, $) {
    _.extend( FMS, {
        LocatorView: FMS.FMSView.extend({
            skipLocationCheck: false,

            locate: function() {
                $('#locating').show();
                this.listenTo(FMS.locator, 'gps_located', this.gotLocation);
                this.listenTo(FMS.locator, 'gps_failed', this.failedLocation);
                this.listenTo(FMS.locator, 'gps_locating', this.locationUpdate);

                FMS.locator.geolocate(CONFIG.ACCURACY, this.skipLocationCheck);
                this.startLocateProgress();
            },

            startLocateProgress: function() {
                this.located = false;
                this.locateCount = 1;
                var that = this;
                window.setTimeout( function() {that.showLocateProgress();}, 1000);
            },

            locationUpdate: function( accuracy ) {
                if ( accuracy && accuracy < 500 ) {
                    $('#progress-bar').css( 'background-color', 'orange' );
                } else if ( accuracy && accuracy < 250 ) {
                    $('#progress-bar').css( 'background-color', 'yellow' );
                } else {
                    $('#progress-bar').css( 'background-color', 'grey' );
                }

                $('#accuracy').text(parseInt(accuracy, 10) + 'm');
            },

            showLocateProgress: function() {
                if ( !this.located && this.locateCount > 20 ) {
                    FMS.searchMessage = FMS.strings.geolocation_failed;
                    $('#locating').hide();
                    return;
                }
                var percent = ( ( 20 - this.locateCount ) / 20 ) * 100;
                $('#progress-bar').css( 'width', percent + '%' );
                this.locateCount++;
                var that = this;
                window.setTimeout( function() {that.showLocateProgress();}, 1000);
            },

            finishedLocating: function() {
                this.stopListening(FMS.locator, 'gps_locating');
                this.stopListening(FMS.locator, 'gps_located');
                this.stopListening(FMS.locator, 'gps_failed');
                $('#locating').hide();
            },

            failedLocation: function(details) {
                this.finishedLocating();
            },

            gotLocation: function(info) {
                this.finishedLocating();
            }
        })
    });
})(FMS, Backbone, _, $);
<commit_msg>Stop locating screen being able to scroll. Fixes #60<commit_after>(function (FMS, Backbone, _, $) {
    _.extend( FMS, {
        LocatorView: FMS.FMSView.extend({
            skipLocationCheck: false,

            locate: function() {
                $(document).delegate('.ui-content', 'touchmove', false);
                $('#locating').show();
                this.listenTo(FMS.locator, 'gps_located', this.gotLocation);
                this.listenTo(FMS.locator, 'gps_failed', this.failedLocation);
                this.listenTo(FMS.locator, 'gps_locating', this.locationUpdate);

                FMS.locator.geolocate(CONFIG.ACCURACY, this.skipLocationCheck);
                this.startLocateProgress();
            },

            startLocateProgress: function() {
                this.located = false;
                this.locateCount = 1;
                var that = this;
                window.setTimeout( function() {that.showLocateProgress();}, 1000);
            },

            locationUpdate: function( accuracy ) {
                if ( accuracy && accuracy < 500 ) {
                    $('#progress-bar').css( 'background-color', 'orange' );
                } else if ( accuracy && accuracy < 250 ) {
                    $('#progress-bar').css( 'background-color', 'yellow' );
                } else {
                    $('#progress-bar').css( 'background-color', 'grey' );
                }

                $('#accuracy').text(parseInt(accuracy, 10) + 'm');
            },

            showLocateProgress: function() {
                if ( !this.located && this.locateCount > 20 ) {
                    FMS.searchMessage = FMS.strings.geolocation_failed;
                    $(document).undelegate('.ui-content', 'touchmove', false);
                    $('#locating').hide();
                    return;
                }
                var percent = ( ( 20 - this.locateCount ) / 20 ) * 100;
                $('#progress-bar').css( 'width', percent + '%' );
                this.locateCount++;
                var that = this;
                window.setTimeout( function() {that.showLocateProgress();}, 1000);
            },

            finishedLocating: function() {
                this.stopListening(FMS.locator, 'gps_locating');
                this.stopListening(FMS.locator, 'gps_located');
                this.stopListening(FMS.locator, 'gps_failed');
                $(document).undelegate('.ui-content', 'touchmove', false);
                $('#locating').hide();
            },

            failedLocation: function(details) {
                this.finishedLocating();
            },

            gotLocation: function(info) {
                this.finishedLocating();
            }
        })
    });
})(FMS, Backbone, _, $);
<|endoftext|>


<commit_before>/*global _, window, document, jQuery, Kinetic*/
var MAPJS = MAPJS || {};
if (Kinetic.Stage.prototype.isRectVisible) {
	throw ('isRectVisible already exists, should not mix in our methods');
}
MAPJS.Rectangle = function (x, y, width, height) {
	'use strict';
	this.scale = function (scale) {
		return new MAPJS.Rectangle(x * scale, y * scale, width * scale, height * scale);
	};
	this.translate = function (dx, dy) {
		return new MAPJS.Rectangle(x + dx, y + dy, width, height);
	};
	this.inset = function (margin) {
		return new MAPJS.Rectangle(x + margin, y + margin, width - (margin * 2), height - (margin * 2));
	};
	this.x = x;
	this.y = y;
	this.height = height;
	this.width = width;
};
Kinetic.Stage.prototype.isRectVisible = function (rect, offset) {
	'use strict';
	offset = offset || {x: 0, y: 0, margin: 0};
	var scale = this.getScale().x || 1;
	rect = rect.scale(scale).translate(offset.x, offset.y).inset(offset.margin);
	return !(
		rect.x + this.attrs.x > this.getWidth() ||
		rect.x + rect.width + this.attrs.x < 0  ||
		rect.y + this.attrs.y > this.getHeight() ||
		rect.y + rect.height + this.attrs.y < 0
	);
};

MAPJS.KineticMediator = function (mapModel, stage, imageRendering) {
	'use strict';
	var layer = new Kinetic.Layer(),
		nodeByIdeaId = {},
		connectorByFromIdeaId_ToIdeaId = {},
		connectorKey = function (fromIdeaId, toIdeaId) {
			return fromIdeaId + '_' + toIdeaId;
		},
		atLeastOneVisible = function (list, deltaX, deltaY) {
			var margin = Math.min(stage.getHeight(), stage.getWidth()) * 0.1;
			return _.find(list, function (node) {
				return node.isVisible({x: deltaX, y: deltaY, margin: margin});
			});
		},
		moveStage = function (deltaX, deltaY) {
			var visibleAfterMove, visibleBeforeMove;
			if (!stage) {
				return;
			}
			visibleBeforeMove = atLeastOneVisible(nodeByIdeaId, 0, 0) || atLeastOneVisible(connectorByFromIdeaId_ToIdeaId, 0, 0);
			visibleAfterMove = atLeastOneVisible(nodeByIdeaId, deltaX, deltaY) || atLeastOneVisible(connectorByFromIdeaId_ToIdeaId, deltaX, deltaY);
			if (visibleAfterMove || (!visibleBeforeMove)) {
				if (deltaY !== 0) { stage.attrs.y += deltaY; }
				if (deltaX !== 0) { stage.attrs.x += deltaX; }
				stage.draw();
			}
		},
		resetStage = function () {
			stage.transitionTo({
				x: 0.5 * stage.getWidth(),
				y: 0.5 * stage.getHeight(),
				scale: {
					x: 1,
					y: 1
				},
				duration: 0.5,
				easing: 'ease-in-out'
			});
		};
	stage.add(layer);
	mapModel.addEventListener('nodeCreated', function (n) {
		var node = new Kinetic.Idea({
			level: n.level,
			x: n.x,
			y: n.y,
			text: n.title,
			mmStyle: n.style,
			opacity: 1
		});

		if (imageRendering) {
			node = Kinetic.IdeaProxy(node, stage, layer);
		}
		/* in kinetic 4.3 cannot use click because click if fired on dragend */
		node.on('click tap', mapModel.selectNode.bind(mapModel, n.id));
		node.on('dragstart', function () {
			node.moveToTop();
			node.getNodeAttrs().shadow.offset = {
				x: 8,
				y: 8
			};
		});
		node.on('dragmove', function () {
			mapModel.nodeDragMove(
				n.id,
				node.attrs.x,
				node.attrs.y
			);
		});
		node.on('dragend', function () {
			node.getNodeAttrs().shadow.offset = {
				x: 4,
				y: 4
			};
			mapModel.nodeDragEnd(
				n.id,
				node.attrs.x,
				node.attrs.y
			);
			if (n.level > 1) {
				stage.setDraggable(true);
			}
		});
		node.on(':textChanged', function (event) {
			mapModel.updateTitle(n.id, event.text);
			mapModel.setInputEnabled(true);
		});
		node.on(':editing', function () {
			mapModel.setInputEnabled(false);
		});
		node.on(':nodeEditRequested', mapModel.editNode.bind(mapModel, 'mouse', false));

		if (n.level > 1) {
			node.on('mouseover touchstart', stage.setDraggable.bind(stage, false));
			node.on('mouseout touchend', stage.setDraggable.bind(stage, true));

		}
		layer.add(node);
		node.transitionToAndDontStopCurrentTransitions({
			opacity: 1,
			duration: 0.4
		});

		mapModel.addEventListener('nodeEditRequested:' + n.id, node.editNode);
		nodeByIdeaId[n.id] = node;

	});
	mapModel.addEventListener('nodeSelectionChanged', function (ideaId, isSelected) {
		var node = nodeByIdeaId[ideaId],
			scale = stage.getScale().x || 1,
			offset = 100,
			move = { x: 0, y: 0 };
		node.setIsSelected(isSelected);
		if (!isSelected) {
			return;
		}
		if (node.getAbsolutePosition().x + node.getWidth() * scale + offset > stage.getWidth()) {
			move.x = stage.getWidth() - (node.getAbsolutePosition().x + node.getWidth() * scale + offset);
		} else if (node.getAbsolutePosition().x < offset) {
			move.x  = offset - node.getAbsolutePosition().x;
		}
		if (node.getAbsolutePosition().y + node.getHeight() * scale + offset > stage.getHeight()) {
			move.y = stage.getHeight() - (node.getAbsolutePosition().y + node.getHeight() * scale + offset);
		} else if (node.getAbsolutePosition().y < offset) {
			move.y = offset - node.getAbsolutePosition().y;
		}
		stage.transitionTo({
			x: stage.attrs.x + move.x,
			y: stage.attrs.y + move.y,
			duration: 0.4,
			easing: 'ease-in-out'
		});
	});
	mapModel.addEventListener('nodeStyleChanged', function (n) {
		var node = nodeByIdeaId[n.id];
		node.setMMStyle(n.style);
	});
	mapModel.addEventListener('nodeDroppableChanged', function (ideaId, isDroppable) {
		var node = nodeByIdeaId[ideaId];
		node.setIsDroppable(isDroppable);
	});
	mapModel.addEventListener('nodeRemoved', function (n) {
		var node = nodeByIdeaId[n.id];
		delete nodeByIdeaId[n.id];
		node.transitionTo({
			opacity: 0.25,
			duration: 0.4,
			callback: node.remove.bind(node)
		});
		node.off('click dblclick tap dragstart dragmove dragend mouseover mouseout :textChanged :nodeEditRequested');
		mapModel.removeEventListener('nodeEditRequested:' + n.id, node.editNode);
	});
	mapModel.addEventListener('nodeMoved', function (n, reason) {
		var node = nodeByIdeaId[n.id];
		node.transitionTo({
			x: n.x,
			y: n.y,
			duration: 0.4,
			easing: reason === 'failed' ? 'bounce-ease-out' : 'ease-in-out'
		});
	});
	mapModel.addEventListener('nodeTitleChanged', function (n) {
		var node = nodeByIdeaId[n.id];
		node.setText(n.title);
		layer.draw();
	});
	mapModel.addEventListener('connectorCreated', function (n) {
		var connector = new Kinetic.Connector({
			shapeFrom: nodeByIdeaId[n.from],
			shapeTo: nodeByIdeaId[n.to],
			stroke: '#888',
			strokeWidth: 1,
			opacity: 0
		});
		connector.opacity = 0;
		connectorByFromIdeaId_ToIdeaId[connectorKey(n.from, n.to)] = connector;
		layer.add(connector);
		connector.moveToBottom();
		connector.transitionTo({
			opacity: 1,
			duration: 0.4
		});
	});
	mapModel.addEventListener('connectorRemoved', function (n) {
		var key = connectorKey(n.from, n.to),
			connector = connectorByFromIdeaId_ToIdeaId[key];
		delete connectorByFromIdeaId_ToIdeaId[key];
		connector.transitionTo({
			opacity: 0,
			duration: 0.1,
			callback: connector.remove.bind(connector)
		});
	});
	mapModel.addEventListener('mapScaleChanged', function (scaleMultiplier, zoomPoint) {
		var currentScale = stage.getScale().x || 1,
			targetScale = Math.max(Math.min(currentScale * scaleMultiplier, 5), 0.2);
		if (currentScale === targetScale) {
			return;
		}
		zoomPoint = zoomPoint || {x:  0.5 * stage.getWidth(), y: 0.5 * stage.getHeight()};
		stage.transitionTo({
			scale: {
				x: targetScale,
				y: targetScale
			},
			x: zoomPoint.x + (stage.attrs.x - zoomPoint.x) * targetScale / currentScale,
			y: zoomPoint.y + (stage.attrs.y - zoomPoint.y) * targetScale / currentScale,
			duration: 0.1,
			easing: 'ease-in-out',
			callback: function () {
				if (imageRendering) {
					stage.fire(':scaleChangeComplete');
				}
			}
		});
	});
	mapModel.addEventListener('mapViewResetRequested', function () {
		resetStage();
	});
	mapModel.addEventListener('mapMoveRequested', function (deltaX, deltaY) {
		moveStage(deltaX, deltaY);
	});
	(function () {
		var x, y;
		stage.on('dragmove', function () {
			var deltaX = x - stage.attrs.x,
				deltaY = y - stage.attrs.y,
				visibleAfterMove = atLeastOneVisible(nodeByIdeaId, 0, 0) || atLeastOneVisible(connectorByFromIdeaId_ToIdeaId, 0, 0),
				shouldMoveBack = !visibleAfterMove && !(atLeastOneVisible(nodeByIdeaId, deltaX, deltaY) || atLeastOneVisible(connectorByFromIdeaId_ToIdeaId, deltaX, deltaY));
			if (shouldMoveBack) {
				moveStage(deltaX, deltaY);
			} else {
				x = stage.attrs.x;
				y = stage.attrs.y;
			}
		});
	}());
};
MAPJS.KineticMediator.dimensionProvider = _.memoize(function (title) {
	'use strict';
	var text = new Kinetic.Idea({
		text: title
	});
	return {
		width: text.getWidth(),
		height: text.getHeight()
	};
});
MAPJS.KineticMediator.layoutCalculator = function (idea) {
	'use strict';
	return MAPJS.calculateLayout(idea, MAPJS.KineticMediator.dimensionProvider);
};
<commit_msg>reset fires scale change complete<commit_after>/*global _, window, document, jQuery, Kinetic*/
var MAPJS = MAPJS || {};
if (Kinetic.Stage.prototype.isRectVisible) {
	throw ('isRectVisible already exists, should not mix in our methods');
}
MAPJS.Rectangle = function (x, y, width, height) {
	'use strict';
	this.scale = function (scale) {
		return new MAPJS.Rectangle(x * scale, y * scale, width * scale, height * scale);
	};
	this.translate = function (dx, dy) {
		return new MAPJS.Rectangle(x + dx, y + dy, width, height);
	};
	this.inset = function (margin) {
		return new MAPJS.Rectangle(x + margin, y + margin, width - (margin * 2), height - (margin * 2));
	};
	this.x = x;
	this.y = y;
	this.height = height;
	this.width = width;
};
Kinetic.Stage.prototype.isRectVisible = function (rect, offset) {
	'use strict';
	offset = offset || {x: 0, y: 0, margin: 0};
	var scale = this.getScale().x || 1;
	rect = rect.scale(scale).translate(offset.x, offset.y).inset(offset.margin);
	return !(
		rect.x + this.attrs.x > this.getWidth() ||
		rect.x + rect.width + this.attrs.x < 0  ||
		rect.y + this.attrs.y > this.getHeight() ||
		rect.y + rect.height + this.attrs.y < 0
	);
};

MAPJS.KineticMediator = function (mapModel, stage, imageRendering) {
	'use strict';
	var layer = new Kinetic.Layer(),
		nodeByIdeaId = {},
		connectorByFromIdeaId_ToIdeaId = {},
		connectorKey = function (fromIdeaId, toIdeaId) {
			return fromIdeaId + '_' + toIdeaId;
		},
		atLeastOneVisible = function (list, deltaX, deltaY) {
			var margin = Math.min(stage.getHeight(), stage.getWidth()) * 0.1;
			return _.find(list, function (node) {
				return node.isVisible({x: deltaX, y: deltaY, margin: margin});
			});
		},
		moveStage = function (deltaX, deltaY) {
			var visibleAfterMove, visibleBeforeMove;
			if (!stage) {
				return;
			}
			visibleBeforeMove = atLeastOneVisible(nodeByIdeaId, 0, 0) || atLeastOneVisible(connectorByFromIdeaId_ToIdeaId, 0, 0);
			visibleAfterMove = atLeastOneVisible(nodeByIdeaId, deltaX, deltaY) || atLeastOneVisible(connectorByFromIdeaId_ToIdeaId, deltaX, deltaY);
			if (visibleAfterMove || (!visibleBeforeMove)) {
				if (deltaY !== 0) { stage.attrs.y += deltaY; }
				if (deltaX !== 0) { stage.attrs.x += deltaX; }
				stage.draw();
			}
		},
		resetStage = function () {
			stage.transitionTo({
				x: 0.5 * stage.getWidth(),
				y: 0.5 * stage.getHeight(),
				scale: {
					x: 1,
					y: 1
				},
				duration: 0.5,
				easing: 'ease-in-out',
				callback: function () {
					if (imageRendering) {
						stage.fire(':scaleChangeComplete');
					}
				}
			});
		};
	stage.add(layer);
	mapModel.addEventListener('nodeCreated', function (n) {
		var node = new Kinetic.Idea({
			level: n.level,
			x: n.x,
			y: n.y,
			text: n.title,
			mmStyle: n.style,
			opacity: 1
		});

		if (imageRendering) {
			node = Kinetic.IdeaProxy(node, stage, layer);
		}
		/* in kinetic 4.3 cannot use click because click if fired on dragend */
		node.on('click tap', mapModel.selectNode.bind(mapModel, n.id));
		node.on('dragstart', function () {
			node.moveToTop();
			node.getNodeAttrs().shadow.offset = {
				x: 8,
				y: 8
			};
		});
		node.on('dragmove', function () {
			mapModel.nodeDragMove(
				n.id,
				node.attrs.x,
				node.attrs.y
			);
		});
		node.on('dragend', function () {
			node.getNodeAttrs().shadow.offset = {
				x: 4,
				y: 4
			};
			mapModel.nodeDragEnd(
				n.id,
				node.attrs.x,
				node.attrs.y
			);
			if (n.level > 1) {
				stage.setDraggable(true);
			}
		});
		node.on(':textChanged', function (event) {
			mapModel.updateTitle(n.id, event.text);
			mapModel.setInputEnabled(true);
		});
		node.on(':editing', function () {
			mapModel.setInputEnabled(false);
		});
		node.on(':nodeEditRequested', mapModel.editNode.bind(mapModel, 'mouse', false));

		if (n.level > 1) {
			node.on('mouseover touchstart', stage.setDraggable.bind(stage, false));
			node.on('mouseout touchend', stage.setDraggable.bind(stage, true));

		}
		layer.add(node);
		node.transitionToAndDontStopCurrentTransitions({
			opacity: 1,
			duration: 0.4
		});

		mapModel.addEventListener('nodeEditRequested:' + n.id, node.editNode);
		nodeByIdeaId[n.id] = node;

	});
	mapModel.addEventListener('nodeSelectionChanged', function (ideaId, isSelected) {
		var node = nodeByIdeaId[ideaId],
			scale = stage.getScale().x || 1,
			offset = 100,
			move = { x: 0, y: 0 };
		node.setIsSelected(isSelected);
		if (!isSelected) {
			return;
		}
		if (node.getAbsolutePosition().x + node.getWidth() * scale + offset > stage.getWidth()) {
			move.x = stage.getWidth() - (node.getAbsolutePosition().x + node.getWidth() * scale + offset);
		} else if (node.getAbsolutePosition().x < offset) {
			move.x  = offset - node.getAbsolutePosition().x;
		}
		if (node.getAbsolutePosition().y + node.getHeight() * scale + offset > stage.getHeight()) {
			move.y = stage.getHeight() - (node.getAbsolutePosition().y + node.getHeight() * scale + offset);
		} else if (node.getAbsolutePosition().y < offset) {
			move.y = offset - node.getAbsolutePosition().y;
		}
		stage.transitionTo({
			x: stage.attrs.x + move.x,
			y: stage.attrs.y + move.y,
			duration: 0.4,
			easing: 'ease-in-out'
		});
	});
	mapModel.addEventListener('nodeStyleChanged', function (n) {
		var node = nodeByIdeaId[n.id];
		node.setMMStyle(n.style);
	});
	mapModel.addEventListener('nodeDroppableChanged', function (ideaId, isDroppable) {
		var node = nodeByIdeaId[ideaId];
		node.setIsDroppable(isDroppable);
	});
	mapModel.addEventListener('nodeRemoved', function (n) {
		var node = nodeByIdeaId[n.id];
		delete nodeByIdeaId[n.id];
		node.transitionTo({
			opacity: 0.25,
			duration: 0.4,
			callback: node.remove.bind(node)
		});
		node.off('click dblclick tap dragstart dragmove dragend mouseover mouseout :textChanged :nodeEditRequested');
		mapModel.removeEventListener('nodeEditRequested:' + n.id, node.editNode);
	});
	mapModel.addEventListener('nodeMoved', function (n, reason) {
		var node = nodeByIdeaId[n.id];
		node.transitionTo({
			x: n.x,
			y: n.y,
			duration: 0.4,
			easing: reason === 'failed' ? 'bounce-ease-out' : 'ease-in-out'
		});
	});
	mapModel.addEventListener('nodeTitleChanged', function (n) {
		var node = nodeByIdeaId[n.id];
		node.setText(n.title);
		layer.draw();
	});
	mapModel.addEventListener('connectorCreated', function (n) {
		var connector = new Kinetic.Connector({
			shapeFrom: nodeByIdeaId[n.from],
			shapeTo: nodeByIdeaId[n.to],
			stroke: '#888',
			strokeWidth: 1,
			opacity: 0
		});
		connector.opacity = 0;
		connectorByFromIdeaId_ToIdeaId[connectorKey(n.from, n.to)] = connector;
		layer.add(connector);
		connector.moveToBottom();
		connector.transitionTo({
			opacity: 1,
			duration: 0.4
		});
	});
	mapModel.addEventListener('connectorRemoved', function (n) {
		var key = connectorKey(n.from, n.to),
			connector = connectorByFromIdeaId_ToIdeaId[key];
		delete connectorByFromIdeaId_ToIdeaId[key];
		connector.transitionTo({
			opacity: 0,
			duration: 0.1,
			callback: connector.remove.bind(connector)
		});
	});
	mapModel.addEventListener('mapScaleChanged', function (scaleMultiplier, zoomPoint) {
		var currentScale = stage.getScale().x || 1,
			targetScale = Math.max(Math.min(currentScale * scaleMultiplier, 5), 0.2);
		if (currentScale === targetScale) {
			return;
		}
		zoomPoint = zoomPoint || {x:  0.5 * stage.getWidth(), y: 0.5 * stage.getHeight()};
		stage.transitionTo({
			scale: {
				x: targetScale,
				y: targetScale
			},
			x: zoomPoint.x + (stage.attrs.x - zoomPoint.x) * targetScale / currentScale,
			y: zoomPoint.y + (stage.attrs.y - zoomPoint.y) * targetScale / currentScale,
			duration: 0.1,
			easing: 'ease-in-out',
			callback: function () {
				if (imageRendering) {
					stage.fire(':scaleChangeComplete');
				}
			}
		});
	});
	mapModel.addEventListener('mapViewResetRequested', function () {
		resetStage();
	});
	mapModel.addEventListener('mapMoveRequested', function (deltaX, deltaY) {
		moveStage(deltaX, deltaY);
	});
	(function () {
		var x, y;
		stage.on('dragmove', function () {
			var deltaX = x - stage.attrs.x,
				deltaY = y - stage.attrs.y,
				visibleAfterMove = atLeastOneVisible(nodeByIdeaId, 0, 0) || atLeastOneVisible(connectorByFromIdeaId_ToIdeaId, 0, 0),
				shouldMoveBack = !visibleAfterMove && !(atLeastOneVisible(nodeByIdeaId, deltaX, deltaY) || atLeastOneVisible(connectorByFromIdeaId_ToIdeaId, deltaX, deltaY));
			if (shouldMoveBack) {
				moveStage(deltaX, deltaY);
			} else {
				x = stage.attrs.x;
				y = stage.attrs.y;
			}
		});
	}());
};
MAPJS.KineticMediator.dimensionProvider = _.memoize(function (title) {
	'use strict';
	var text = new Kinetic.Idea({
		text: title
	});
	return {
		width: text.getWidth(),
		height: text.getHeight()
	};
});
MAPJS.KineticMediator.layoutCalculator = function (idea) {
	'use strict';
	return MAPJS.calculateLayout(idea, MAPJS.KineticMediator.dimensionProvider);
};
<|endoftext|>


<commit_before>L.UGeoJSONLayer = L.GeoJSON.extend({
  options: {
    debug: false,
    light: true,
    usebbox: false,
    endpoint: -1,
    parameters: {},
    headers: {},
    maxRequests: 5,
    pollTime:0,
    once: false,
    minzoom: 0,
    enctype: 'form-data', //urlencoded || form-data || json
    transformData: function (data) { return data; },
    afterFetch: function () {},
    after: function (data) {},
  },
  callback: function (data) {
    if(this.options.light) {
      this.clearLayers();//if needed, we clean the layers
    }

    //Then we add the new data
    this.addData(data);
    this.options.after(data);
  },
  initialize: function (uOptions, options) {
    L.GeoJSON.prototype.initialize.call(this, undefined, options);
    L.Util.setOptions(this, uOptions);

    this._layersOld = [];
    this._requests = [];
  },
  onMoveEnd: function () {
    if (this.options.debug) {
      console.debug('load Data');
    }

    while(this._requests.length > this.options.maxRequests) { //This allows to stop the oldest requests
      this._requests.shift().abort();
    }

    var postData = {};

    if (typeof this.options.parameters === 'function') {
      postData = this.options.parameters();
    } else {
      for (var k in this.options.parameters) {
        if (typeof this.options.parameters[k].scope !== 'undefined') {
	        postData[k]=this.options.parameters[k].scope[k];
        } else {
	        postData[k]=this.options.parameters[k];
        }
      }
    }

    postData.zoom = this._map.getZoom();

    if (postData.zoom < this.options.minzoom) {
      if (this.options.debug) {
        console.debug('ignoring zoomlevel '+postData.zoom+' (< '+this.options.minzoom+')');
      }
      this.clearLayers();
      return;
    }

    var bounds = this._map.getBounds();
    if (this.options.usebbox) {
      postData.bbox = bounds.toBBoxString();
    } else {
      postData.south = bounds.getSouth();
      postData.north = bounds.getNorth();
      postData.east = bounds.getEast();
      postData.west = bounds.getWest();
    }

    var self = this;
    var request = new XMLHttpRequest();
    request.open('POST', this.options.endpoint, true);
    for(var l in this.options.headers) {
      if (typeof this.options.headers[l].scope !== 'undefined') {
        request.setRequestHeader(l, this.options.headers[l].scope[l]);
      } else {
        request.setRequestHeader(l, this.options.headers[l]);
      }
    }

    request.onload = function() {
      for(var i in self._requests) {
        if(self._requests[i] === request) {
          self._requests.splice(i,1); //We remove the request from the list of currently running requests.
          break;
        }
      }

      if (this.status >= 200 && this.status < 400) {
        var data = JSON.parse(this.responseText);

        if (self.options.transformData) {
          data = self.options.transformData(data);
        }

        self.options.afterFetch();
        self.callback(data);
      }
    };

    this._requests.push(request);

    if (this.options.enctype=='urlencoded' || this.options.enctype=='json') {
      if (this.options.enctype=='urlencoded') {
        // urlencoded request
        var urlencoded = '';
        for (var p in postData) {
          if (postData.hasOwnProperty(p)) {
            if (urlencoded.length > 0) {
              urlencoded += '&';
            }
            urlencoded += encodeURIComponent(p) + '=' + encodeURIComponent(postData[p]);
          }
        }
        request.send(urlencoded);
      } else {
        // json request
        request.send(JSON.stringify(postData));
      }
    } else {
      var postFormData = new FormData();
      for (var q in postData) {
        if (postData.hasOwnProperty(q)) {
          postFormData.append(q, postData[q]);
        }
      }
      request.send(postFormData);
    }
  },
  onAdd: function (map) {
    this._map = map;

    if (typeof this.options.endpoint !== 'undefined' && this.options.endpoint !== -1) {
		  this.onMoveEnd();

      if (!this.options.once) {
        map.on('dragend', this.onMoveEnd, this);
        map.on('zoomend', this.onMoveEnd, this);
        map.on('refresh', this.onMoveEnd, this);

        if (this.options.pollTime > 0) {
          this.intervalID = window.setInterval(this.onMoveEnd.bind(this), this.options.pollTime);
        }
      }
    }

    if (this.options.debug) {
      console.debug('add layer');
    }
  },

  onRemove: function (map) {
    if (this.options.debug) {
      console.debug('remove layer');
    }
    L.LayerGroup.prototype.onRemove.call(this, map);

    if (!this.options.once && this.options.pollTime > 0) {
      window.clearInterval(this.intervalID);
    }

    while(this._requests.length > 0) {
      this._requests.shift().abort();
    }

    if(!this.options.once) {
      map.off({
        'dragend': this.onMoveEnd
      }, this);
      map.off({
        'zoomend': this.onMoveEnd
      }, this);
        map.off({
        'refresh': this.onMoveEnd
      }, this);
    }

    this._map = null;
  }
});

L.uGeoJSONLayer = function (uOptions, options) {
  return new L.UGeoJSONLayer(uOptions, options);
};
<commit_msg>Add ability to disable the data fetching manually<commit_after>L.UGeoJSONLayer = L.GeoJSON.extend({
  options: {
    debug: false,
    light: true,
    usebbox: false,
    endpoint: -1,
    parameters: {},
    headers: {},
    maxRequests: 5,
    pollTime:0,
    once: false,
    minzoom: 0,
    enctype: 'form-data', //urlencoded || form-data || json
    transformData: function (data) { return data; },
    afterFetch: function () {},
    after: function (data) {},
    disabled: false,
  },
  callback: function (data) {
    if(this.options.light) {
      this.clearLayers();//if needed, we clean the layers
    }

    //Then we add the new data
    this.addData(data);
    this.options.after(data);
  },
  initialize: function (uOptions, options) {
    L.GeoJSON.prototype.initialize.call(this, undefined, options);
    L.Util.setOptions(this, uOptions);

    this._layersOld = [];
    this._requests = [];
  },
  onMoveEnd: function () {
    if (this.disabled) {
      return;
    }
    if (this.options.debug) {
      console.debug('load Data');
    }

    while(this._requests.length > this.options.maxRequests) { //This allows to stop the oldest requests
      this._requests.shift().abort();
    }

    var postData = {};

    if (typeof this.options.parameters === 'function') {
      postData = this.options.parameters();
    } else {
      for (var k in this.options.parameters) {
        if (typeof this.options.parameters[k].scope !== 'undefined') {
	        postData[k]=this.options.parameters[k].scope[k];
        } else {
	        postData[k]=this.options.parameters[k];
        }
      }
    }

    postData.zoom = this._map.getZoom();

    if (postData.zoom < this.options.minzoom) {
      if (this.options.debug) {
        console.debug('ignoring zoomlevel '+postData.zoom+' (< '+this.options.minzoom+')');
      }
      this.clearLayers();
      return;
    }

    var bounds = this._map.getBounds();
    if (this.options.usebbox) {
      postData.bbox = bounds.toBBoxString();
    } else {
      postData.south = bounds.getSouth();
      postData.north = bounds.getNorth();
      postData.east = bounds.getEast();
      postData.west = bounds.getWest();
    }

    var self = this;
    var request = new XMLHttpRequest();
    request.open('POST', this.options.endpoint, true);
    for(var l in this.options.headers) {
      if (typeof this.options.headers[l].scope !== 'undefined') {
        request.setRequestHeader(l, this.options.headers[l].scope[l]);
      } else {
        request.setRequestHeader(l, this.options.headers[l]);
      }
    }

    request.onload = function() {
      for(var i in self._requests) {
        if(self._requests[i] === request) {
          self._requests.splice(i,1); //We remove the request from the list of currently running requests.
          break;
        }
      }

      if (this.status >= 200 && this.status < 400) {
        var data = JSON.parse(this.responseText);

        if (self.options.transformData) {
          data = self.options.transformData(data);
        }

        self.options.afterFetch();
        self.callback(data);
      }
    };

    this._requests.push(request);

    if (this.options.enctype=='urlencoded' || this.options.enctype=='json') {
      if (this.options.enctype=='urlencoded') {
        // urlencoded request
        var urlencoded = '';
        for (var p in postData) {
          if (postData.hasOwnProperty(p)) {
            if (urlencoded.length > 0) {
              urlencoded += '&';
            }
            urlencoded += encodeURIComponent(p) + '=' + encodeURIComponent(postData[p]);
          }
        }
        request.send(urlencoded);
      } else {
        // json request
        request.send(JSON.stringify(postData));
      }
    } else {
      var postFormData = new FormData();
      for (var q in postData) {
        if (postData.hasOwnProperty(q)) {
          postFormData.append(q, postData[q]);
        }
      }
      request.send(postFormData);
    }
  },
  onAdd: function (map) {
    this._map = map;

    if (typeof this.options.endpoint !== 'undefined' && this.options.endpoint !== -1) {
		  this.onMoveEnd();

      if (!this.options.once) {
        map.on('dragend', this.onMoveEnd, this);
        map.on('zoomend', this.onMoveEnd, this);
        map.on('refresh', this.onMoveEnd, this);

        if (this.options.pollTime > 0) {
          this.intervalID = window.setInterval(this.onMoveEnd.bind(this), this.options.pollTime);
        }
      }
    }

    if (this.options.debug) {
      console.debug('add layer');
    }
  },

  onRemove: function (map) {
    if (this.options.debug) {
      console.debug('remove layer');
    }
    L.LayerGroup.prototype.onRemove.call(this, map);

    if (!this.options.once && this.options.pollTime > 0) {
      window.clearInterval(this.intervalID);
    }

    while(this._requests.length > 0) {
      this._requests.shift().abort();
    }

    if(!this.options.once) {
      map.off({
        'dragend': this.onMoveEnd
      }, this);
      map.off({
        'zoomend': this.onMoveEnd
      }, this);
        map.off({
        'refresh': this.onMoveEnd
      }, this);
    }

    this._map = null;
  },

  toggleDisabled() {
    this.disabled = !this.disabled;
  }
});

L.uGeoJSONLayer = function (uOptions, options) {
  return new L.UGeoJSONLayer(uOptions, options);
};
<|endoftext|>