<commit_before>"use strict";

jsns.define("edity.widgets.treemenu.editor", [
    "htmlrest.controller",
    "htmlrest.toggles",
    "htmlrest.rest",
], function (exports, module, controller, toggles, rest) {

    var editTreeMenuItem = null;
    var deleteTreeMenuItem = null;
    var addTreeMenuItem = null;

    function EditTreeMenuItemController(bindings) {
        editTreeMenuItem = this;

        var dialog = bindings.getToggle("dialog");
        var model = bindings.getModel("properties");
        var currentMenuItem = null;
        var currentEditingCompleteCallback = null;
        var linkToggle = bindings.getToggle('link');
        var makingLink = false;

        function edit(menuItem, editingCompleteCallback) {
            dialog.on();
            model.setData(menuItem);
            currentMenuItem = menuItem;
            currentEditingCompleteCallback = editingCompleteCallback;
            makingLink = menuItem.hasOwnProperty('link');
            if (makingLink) {
                linkToggle.on();
            }
            else {
                linkToggle.off();
            }
        }
        this.edit = edit;

        function updateMenuItem(evt) {
            evt.preventDefault();
            evt.stopPropagation();
            dialog.off();

            var data = model.getData();
            currentMenuItem.name = data.name;
            if (makingLink) {
                currentMenuItem.link = data.link;
            }

            currentEditingCompleteCallback(currentMenuItem);
            currentMenuItem = null;
            currentEditingCompleteCallback = null;
        }
        this.updateMenuItem = updateMenuItem;
    }

    function DeleteTreeMenuItemController(bindings) {
        deleteTreeMenuItem = this;

        var dialog = bindings.getToggle('dialog');
        var model = bindings.getModel('info');
        var currentMenuItem = null;
        var currentCallback = null;

        function confirm(menuItem, deleteCallback) {
            currentMenuItem = menuItem;
            currentCallback = deleteCallback;
            model.setData(menuItem);
            dialog.on();
        }
        this.confirm = confirm;

        function deleteItem(evt) {
            evt.preventDefault();
            evt.stopPropagation();

            var parent = currentMenuItem.parent;
            var loc = parent.folders.indexOf(currentMenuItem);
            if (loc !== -1) {
                parent.folders.splice(loc, 1);
                currentCallback();
            }

            currentMenuItem = null;
            currentCallback = null;
            dialog.off();
        }
        this.deleteItem = deleteItem;
    }

    function AddTreeMenuItemController(bindings) {
        addTreeMenuItem = this;

        var currentParent = null;
        var currentCallback = null;

        var questionModel = bindings.getModel('question');
        var createFolderModel = bindings.getModel('createFolder');
        var createLinkModel = bindings.getModel('createLink');
        var linkAutoTypeModel = bindings.getModel('linkAutoType');

        var dialog = bindings.getToggle('dialog');

        var questionToggle = bindings.getToggle('question');
        var createFolderToggle = bindings.getToggle('createFolder');
        var createLinkToggle = bindings.getToggle('createLink');
        var toggleGroup = new toggles.Group(questionToggle, createFolderToggle, createLinkToggle);
        var autoTypeUrl = true;

        toggleGroup.show(questionToggle);

        function createNewItem(parent, createdCallback) {
            currentParent = parent;
            currentCallback = createdCallback;
            questionModel.setData(parent);
            toggleGroup.show(questionToggle);
            dialog.on();
        }
        this.createNewItem = createNewItem;

        function startFolderCreation(evt) {
            evt.preventDefault();
            evt.stopPropagation();

            toggleGroup.show(createFolderToggle);
        }
        this.startFolderCreation = startFolderCreation;

        function createFolder(evt) {
            evt.preventDefault();
            evt.stopPropagation();
            createFolderModel.clear();
            var folderData = createFolderModel.getData();
            var newItem = {
                name: folderData.name,
                folders: [],
                links: [],
                parent: currentParent
            };
            currentParent.folders.push(newItem);
            finishAdd(newItem);
        }
        this.createFolder = createFolder;

        function startLinkCreation(evt) {
            evt.preventDefault();
            evt.stopPropagation();
            createLinkModel.clear();
            linkAutoTypeModel.clear();
            autoTypeUrl = true;

            toggleGroup.show(createLinkToggle);
        }
        this.startLinkCreation = startLinkCreation;

        function createLink(evt) {
            evt.preventDefault();
            evt.stopPropagation();

            var linkData = createLinkModel.getData();
            var newItem = {
                name: linkData.name,
                link: linkData.link,
                parent: currentParent
            };
            currentParent.links.push(newItem);
            finishAdd(newItem);
        }
        this.createLink = createLink;

        function finishAdd(newItem) {
            dialog.off();
            currentCallback(newItem);
            currentCallback = null;
            currentParent = null;
        }

        function replaceUrl(x) {
            return '-';
        }

        function nameChanged(evt) {
            if (autoTypeUrl) {
                var data = createLinkModel.getData();
                linkAutoTypeModel.setData({
                    link: '/' + encodeURI(data.name.replace(/\s/g, replaceUrl))
                });
            }
        }
        this.nameChanged = nameChanged;

        function cancelAutoType() {
            autoTypeUrl = false;
        }
        this.cancelAutoType = cancelAutoType;
    }

    function menuJsonSerializeReplacer(key, value) {
        if (key === 'parent' || key === 'menuItemId') {
            return undefined;
        }
        return value;
    }

    function RootNodeControls(bindings, context) {
        var menuData = context.menuData;
        var updateCb = context.updateCb;
        var saveUrl = context.saveUrl;
        var uploadUrl = bindings.getModel('treeMenuEditRoot').getSrc();

        function save(evt) {
            evt.preventDefault();
            evt.stopPropagation();
            var blob = new Blob([JSON.stringify(menuData, menuJsonSerializeReplacer, 4)], { type: "application/json" });
            rest.upload(uploadUrl + saveUrl, blob);
        }
        this.save = save;

        function addItem(evt) {
            evt.preventDefault();
            evt.stopPropagation();
            addTreeMenuItem.createNewItem(menuData, function () {
                updateCb();
            });
        }
        this.addItem = addItem;
    }
    exports.RootNodeControls = RootNodeControls;

    function createControllers() {
        if (editTreeMenuItem === null) {
            controller.create("editTreeMenuItem", EditTreeMenuItemController);
            controller.create("deleteTreeMenuItem", DeleteTreeMenuItemController);
            controller.create("createTreeMenuItem", AddTreeMenuItemController);
        }
    }

    function moveUp(evt, menuData, itemData, updateCb) {
        evt.preventDefault();
        evt.stopPropagation();
        var parent = itemData.parent;
        var loc = parent.folders.indexOf(itemData);
        if (loc !== -1) {
            if (loc > 0) {
                var swap = parent.folders[loc - 1];
                parent.folders[loc - 1] = itemData;
                parent.folders[loc] = swap;
                updateCb();
            }
        }
        else {
            loc = parent.links.indexOf(itemData);
            if (loc > 0) {
                var swap = parent.links[loc - 1];
                parent.links[loc - 1] = itemData;
                parent.links[loc] = swap;
                updateCb();
            }
        }
    }

    function moveDown(evt, menuData, itemData, updateCb) {
        evt.preventDefault();
        evt.stopPropagation();
        var parent = itemData.parent;
        var loc = parent.folders.indexOf(itemData);
        if (loc !== -1) {
            if (loc + 1 < parent.folders.length) {
                var swap = parent.folders[loc + 1];
                parent.folders[loc + 1] = itemData;
                parent.folders[loc] = swap;
                updateCb();
            }
        }
        else {
            loc = parent.links.indexOf(itemData);
            if (loc !== -1 && loc + 1 < parent.links.length) {
                var swap = parent.links[loc + 1];
                parent.links[loc + 1] = itemData;
                parent.links[loc] = swap;
                updateCb();
            }
        }
    }

    function addItem(evt, menuData, itemData, updateCb) {
        evt.preventDefault();
        evt.stopPropagation();
        addTreeMenuItem.createNewItem(itemData, function () {
            updateCb();
        });
    }

    function editItem(evt, menuData, itemData, updateCb) {
        evt.preventDefault();
        evt.stopPropagation();
        editTreeMenuItem.edit(itemData, function () {
            updateCb();
        });
    }

    function deleteItem(evt, menuData, itemData, updateCb) {
        evt.preventDefault();
        evt.stopPropagation();
        deleteTreeMenuItem.confirm(itemData, function () {
            updateCb();
        });
    }

    function fireItemAdded(menuData, listener, itemData, updateCb) {
        createControllers(menuData);

        listener.moveUp = function (evt) {
            moveUp(evt, menuData, itemData, updateCb);
        }

        listener.moveDown = function (evt) {
            moveDown(evt, menuData, itemData, updateCb);
        }

        listener.addItem = function (evt) {
            addItem(evt, menuData, itemData, updateCb);
        }

        listener.editItem = function (evt) {
            editItem(evt, menuData, itemData, updateCb);
        }

        listener.deleteItem = function (evt) {
            deleteItem(evt, menuData, itemData, updateCb);
        }
    }

    exports.fireItemAdded = fireItemAdded;
});

jsns.run([
    "htmlrest.storage",
    "htmlrest.rest",
    "htmlrest.controller",
    "edity.widgets.treemenu.editor"
], function (exports, module, storage, rest, controller, treeEditor) {

    controller.create("treeMenu", TreeMenuController);

    /**
     * This function builds a controller that handles a tree menu on the page.
     * @param {type} bindings
     */
    function TreeMenuController(bindings) {
        var rootModel = bindings.getModel('children');
        var config = bindings.getConfig();
        var editMode = config["treemenu-editmode"] === 'true';
        var version = config["treemenu-version"];

        var ajaxurl = rootModel.getSrc();
        var getNextId = (function () {
            var i = -1;
            return function () {
                return ++i;
            }
        })();

        var menuStorageId = 'treemenu-cache-' + ajaxurl;
        var sessionData = storage.getSessionJson(menuStorageId, null);
        var menuCache = null;
        var menuData = null;
        var createdItems = {};
        window.onbeforeunload = function (e) {
            if (editMode) {
                removeParents(menuData);
            }
            storage.storeJsonInSession(menuStorageId, {
                cache: menuCache,
                data: menuData,
                version: version
            });
        };

        if (sessionData === null || sessionData.version !== version) {
            //No data, get it
            menuCache = {};
            rest.get(ajaxurl, function (data) {
                initialSetup(data);
            });
        }
        else {
            //Use what we had
            menuCache = sessionData.cache;
            initialSetup(sessionData.data);
        }

        function initialSetup(data) {
            menuData = data;
            if (menuData !== null) {
                if (data['menuItemId'] === undefined) {
                    createIds(data);
                }

                if (editMode) {
                    findParents(data, null);
                    controller.create("treeMenuEditRoot", treeEditor.RootNodeControls, { //This isn't really right, will create controllers for all tree menus on the page, need to single out somehow
                        menuData: menuData,
                        updateCb: rebuildMenu,
                        saveUrl: ajaxurl
                    });
                }

                var menuCacheInfo = getMenuCacheInfo(data.menuItemId);
                buildMenu(bindings, menuCacheInfo, menuData, false);
            }
        }

        function rebuildMenu() {
            createdItems = {};
            var childModel = bindings.getModel('children');
            childModel.setData([]);
            var menuCacheInfo = getMenuCacheInfo(menuData.menuItemId);
            buildMenu(bindings, menuCacheInfo, menuData, false);
        }

        function createIds(data) {
            data.menuItemId = getNextId();
            var folders = data.folders;
            for (var i = 0; i < folders.length; ++i) {
                //Recursion, I don't care, how nested is your menu that you run out of stack space here? Can a user really use that?
                createIds(folders[i]);
            }
        }

        function findParents(data, parent) {
            data.parent = parent;
            var folders = data.folders;
            for (var i = 0; i < folders.length; ++i) {
                //Recursion, I don't care, how nested is your menu that you run out of stack space here? Can a user really use that?
                findParents(folders[i], data);
            }
            var links = data.links;
            for (var i = 0; i < links.length; ++i) {
                links[i].parent = data;
            }
        }

        function removeParents(data) {
            delete data.parent;
            var folders = data.folders;
            for (var i = 0; i < folders.length; ++i) {
                //Recursion, I don't care, how nested is your menu that you run out of stack space here? Can a user really use that?
                removeParents(folders[i]);
            }
            var links = data.links;
            for (var i = 0; i < links.length; ++i) {
                delete links[i].parent;
            }
        }

        function getMenuCacheInfo(parentCategoryId) {
            if (!menuCache.hasOwnProperty(parentCategoryId)) {
                menuCache[parentCategoryId] = {
                    expanded: false,
                    id: parentCategoryId
                };
            }
            return menuCache[parentCategoryId];
        }

        function buildMenu(parentBindings, menuCacheInfo, folder, autoHide) {
            if (autoHide === undefined) {
                autoHide = true;
            }

            if (!createdItems[menuCacheInfo.id]) {
                var parentModel = parentBindings.getModel('children');
                var list = null;
                parentModel.setData({}, function (created) {
                    list = created;
                });
                createdItems[menuCacheInfo.id] = true;

                var foldersModel = list.getModel('folders');
                var linksModel = list.getModel('links');

                foldersModel.setData(folder.folders, function (folderComponent, data) {
                    var id = data.menuItemId;
                    var menuCacheInfo = getMenuCacheInfo(id);

                    var listener = {
                        toggleMenuItem: function (evt) {
                            evt.preventDefault();

                            buildMenu(folderComponent, menuCacheInfo, data);
                            toggleMenu(menuCacheInfo, folderComponent.getToggle('children'));
                        }
                    };
                    if (editMode) {
                        treeEditor.fireItemAdded(menuData, listener, data, rebuildMenu);
                    }
                    folderComponent.setListener(listener);

                    if (menuCacheInfo.expanded) {
                        buildMenu(folderComponent, menuCacheInfo, data, autoHide);
                    }
                });

                if (editMode) {
                    linksModel.setData(folder.links, function (component, data) {
                        var listener = {};
                        treeEditor.fireItemAdded(menuData, listener, data, rebuildMenu);
                        component.setListener(listener);
                    });
                }
                else {
                    linksModel.setData(folder.links);
                }
            }
        }

        function toggleMenu(menuCacheInfo, toggle, transitionTime) {
            if (transitionTime === undefined) {
                transitionTime = 200;
            }

            if (menuCacheInfo.expanded) {
                menuCacheInfo.expanded = false;
                toggle.off();
            }
            else {
                menuCacheInfo.expanded = true;
                toggle.on();
            }
        }
    }
});<commit_msg>Cleaned up auto typed urls more.<commit_after>"use strict";

jsns.define("edity.widgets.treemenu.editor", [
    "htmlrest.controller",
    "htmlrest.toggles",
    "htmlrest.rest",
], function (exports, module, controller, toggles, rest) {

    var editTreeMenuItem = null;
    var deleteTreeMenuItem = null;
    var addTreeMenuItem = null;

    function EditTreeMenuItemController(bindings) {
        editTreeMenuItem = this;

        var dialog = bindings.getToggle("dialog");
        var model = bindings.getModel("properties");
        var currentMenuItem = null;
        var currentEditingCompleteCallback = null;
        var linkToggle = bindings.getToggle('link');
        var makingLink = false;

        function edit(menuItem, editingCompleteCallback) {
            dialog.on();
            model.setData(menuItem);
            currentMenuItem = menuItem;
            currentEditingCompleteCallback = editingCompleteCallback;
            makingLink = menuItem.hasOwnProperty('link');
            if (makingLink) {
                linkToggle.on();
            }
            else {
                linkToggle.off();
            }
        }
        this.edit = edit;

        function updateMenuItem(evt) {
            evt.preventDefault();
            evt.stopPropagation();
            dialog.off();

            var data = model.getData();
            currentMenuItem.name = data.name;
            if (makingLink) {
                currentMenuItem.link = data.link;
            }

            currentEditingCompleteCallback(currentMenuItem);
            currentMenuItem = null;
            currentEditingCompleteCallback = null;
        }
        this.updateMenuItem = updateMenuItem;
    }

    function DeleteTreeMenuItemController(bindings) {
        deleteTreeMenuItem = this;

        var dialog = bindings.getToggle('dialog');
        var model = bindings.getModel('info');
        var currentMenuItem = null;
        var currentCallback = null;

        function confirm(menuItem, deleteCallback) {
            currentMenuItem = menuItem;
            currentCallback = deleteCallback;
            model.setData(menuItem);
            dialog.on();
        }
        this.confirm = confirm;

        function deleteItem(evt) {
            evt.preventDefault();
            evt.stopPropagation();

            var parent = currentMenuItem.parent;
            var loc = parent.folders.indexOf(currentMenuItem);
            if (loc !== -1) {
                parent.folders.splice(loc, 1);
                currentCallback();
            }

            currentMenuItem = null;
            currentCallback = null;
            dialog.off();
        }
        this.deleteItem = deleteItem;
    }

    function AddTreeMenuItemController(bindings) {
        addTreeMenuItem = this;

        var currentParent = null;
        var currentCallback = null;

        var questionModel = bindings.getModel('question');
        var createFolderModel = bindings.getModel('createFolder');
        var createLinkModel = bindings.getModel('createLink');
        var linkAutoTypeModel = bindings.getModel('linkAutoType');

        var dialog = bindings.getToggle('dialog');

        var questionToggle = bindings.getToggle('question');
        var createFolderToggle = bindings.getToggle('createFolder');
        var createLinkToggle = bindings.getToggle('createLink');
        var toggleGroup = new toggles.Group(questionToggle, createFolderToggle, createLinkToggle);
        var autoTypeUrl = true;

        toggleGroup.show(questionToggle);

        function createNewItem(parent, createdCallback) {
            currentParent = parent;
            currentCallback = createdCallback;
            questionModel.setData(parent);
            toggleGroup.show(questionToggle);
            dialog.on();
        }
        this.createNewItem = createNewItem;

        function startFolderCreation(evt) {
            evt.preventDefault();
            evt.stopPropagation();

            toggleGroup.show(createFolderToggle);
        }
        this.startFolderCreation = startFolderCreation;

        function createFolder(evt) {
            evt.preventDefault();
            evt.stopPropagation();
            createFolderModel.clear();
            var folderData = createFolderModel.getData();
            var newItem = {
                name: folderData.name,
                folders: [],
                links: [],
                parent: currentParent
            };
            currentParent.folders.push(newItem);
            finishAdd(newItem);
        }
        this.createFolder = createFolder;

        function startLinkCreation(evt) {
            evt.preventDefault();
            evt.stopPropagation();
            createLinkModel.clear();
            linkAutoTypeModel.clear();
            autoTypeUrl = true;

            toggleGroup.show(createLinkToggle);
        }
        this.startLinkCreation = startLinkCreation;

        function createLink(evt) {
            evt.preventDefault();
            evt.stopPropagation();

            var linkData = createLinkModel.getData();
            var newItem = {
                name: linkData.name,
                link: linkData.link,
                parent: currentParent
            };
            currentParent.links.push(newItem);
            finishAdd(newItem);
        }
        this.createLink = createLink;

        function finishAdd(newItem) {
            dialog.off();
            currentCallback(newItem);
            currentCallback = null;
            currentParent = null;
        }

        function replaceUrl(x) {
            switch (x) {
                case ' ':
                    return '-';
                default:
                    return '';
            }
        }

        function nameChanged(evt) {
            if (autoTypeUrl) {
                var data = createLinkModel.getData();
                var urlName = encodeURI(data.name.replace(/\s|[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, replaceUrl));
                linkAutoTypeModel.setData({
                    link: '/' + urlName
                });
            }
        }
        this.nameChanged = nameChanged;

        function cancelAutoType() {
            autoTypeUrl = false;
        }
        this.cancelAutoType = cancelAutoType;
    }

    function menuJsonSerializeReplacer(key, value) {
        if (key === 'parent' || key === 'menuItemId') {
            return undefined;
        }
        return value;
    }

    function RootNodeControls(bindings, context) {
        var menuData = context.menuData;
        var updateCb = context.updateCb;
        var saveUrl = context.saveUrl;
        var uploadUrl = bindings.getModel('treeMenuEditRoot').getSrc();

        function save(evt) {
            evt.preventDefault();
            evt.stopPropagation();
            var blob = new Blob([JSON.stringify(menuData, menuJsonSerializeReplacer, 4)], { type: "application/json" });
            rest.upload(uploadUrl + saveUrl, blob);
        }
        this.save = save;

        function addItem(evt) {
            evt.preventDefault();
            evt.stopPropagation();
            addTreeMenuItem.createNewItem(menuData, function () {
                updateCb();
            });
        }
        this.addItem = addItem;
    }
    exports.RootNodeControls = RootNodeControls;

    function createControllers() {
        if (editTreeMenuItem === null) {
            controller.create("editTreeMenuItem", EditTreeMenuItemController);
            controller.create("deleteTreeMenuItem", DeleteTreeMenuItemController);
            controller.create("createTreeMenuItem", AddTreeMenuItemController);
        }
    }

    function moveUp(evt, menuData, itemData, updateCb) {
        evt.preventDefault();
        evt.stopPropagation();
        var parent = itemData.parent;
        var loc = parent.folders.indexOf(itemData);
        if (loc !== -1) {
            if (loc > 0) {
                var swap = parent.folders[loc - 1];
                parent.folders[loc - 1] = itemData;
                parent.folders[loc] = swap;
                updateCb();
            }
        }
        else {
            loc = parent.links.indexOf(itemData);
            if (loc > 0) {
                var swap = parent.links[loc - 1];
                parent.links[loc - 1] = itemData;
                parent.links[loc] = swap;
                updateCb();
            }
        }
    }

    function moveDown(evt, menuData, itemData, updateCb) {
        evt.preventDefault();
        evt.stopPropagation();
        var parent = itemData.parent;
        var loc = parent.folders.indexOf(itemData);
        if (loc !== -1) {
            if (loc + 1 < parent.folders.length) {
                var swap = parent.folders[loc + 1];
                parent.folders[loc + 1] = itemData;
                parent.folders[loc] = swap;
                updateCb();
            }
        }
        else {
            loc = parent.links.indexOf(itemData);
            if (loc !== -1 && loc + 1 < parent.links.length) {
                var swap = parent.links[loc + 1];
                parent.links[loc + 1] = itemData;
                parent.links[loc] = swap;
                updateCb();
            }
        }
    }

    function addItem(evt, menuData, itemData, updateCb) {
        evt.preventDefault();
        evt.stopPropagation();
        addTreeMenuItem.createNewItem(itemData, function () {
            updateCb();
        });
    }

    function editItem(evt, menuData, itemData, updateCb) {
        evt.preventDefault();
        evt.stopPropagation();
        editTreeMenuItem.edit(itemData, function () {
            updateCb();
        });
    }

    function deleteItem(evt, menuData, itemData, updateCb) {
        evt.preventDefault();
        evt.stopPropagation();
        deleteTreeMenuItem.confirm(itemData, function () {
            updateCb();
        });
    }

    function fireItemAdded(menuData, listener, itemData, updateCb) {
        createControllers(menuData);

        listener.moveUp = function (evt) {
            moveUp(evt, menuData, itemData, updateCb);
        }

        listener.moveDown = function (evt) {
            moveDown(evt, menuData, itemData, updateCb);
        }

        listener.addItem = function (evt) {
            addItem(evt, menuData, itemData, updateCb);
        }

        listener.editItem = function (evt) {
            editItem(evt, menuData, itemData, updateCb);
        }

        listener.deleteItem = function (evt) {
            deleteItem(evt, menuData, itemData, updateCb);
        }
    }

    exports.fireItemAdded = fireItemAdded;
});

jsns.run([
    "htmlrest.storage",
    "htmlrest.rest",
    "htmlrest.controller",
    "edity.widgets.treemenu.editor"
], function (exports, module, storage, rest, controller, treeEditor) {

    controller.create("treeMenu", TreeMenuController);

    /**
     * This function builds a controller that handles a tree menu on the page.
     * @param {type} bindings
     */
    function TreeMenuController(bindings) {
        var rootModel = bindings.getModel('children');
        var config = bindings.getConfig();
        var editMode = config["treemenu-editmode"] === 'true';
        var version = config["treemenu-version"];

        var ajaxurl = rootModel.getSrc();
        var getNextId = (function () {
            var i = -1;
            return function () {
                return ++i;
            }
        })();

        var menuStorageId = 'treemenu-cache-' + ajaxurl;
        var sessionData = storage.getSessionJson(menuStorageId, null);
        var menuCache = null;
        var menuData = null;
        var createdItems = {};
        window.onbeforeunload = function (e) {
            if (editMode) {
                removeParents(menuData);
            }
            storage.storeJsonInSession(menuStorageId, {
                cache: menuCache,
                data: menuData,
                version: version
            });
        };

        if (sessionData === null || sessionData.version !== version) {
            //No data, get it
            menuCache = {};
            rest.get(ajaxurl, function (data) {
                initialSetup(data);
            });
        }
        else {
            //Use what we had
            menuCache = sessionData.cache;
            initialSetup(sessionData.data);
        }

        function initialSetup(data) {
            menuData = data;
            if (menuData !== null) {
                if (data['menuItemId'] === undefined) {
                    createIds(data);
                }

                if (editMode) {
                    findParents(data, null);
                    controller.create("treeMenuEditRoot", treeEditor.RootNodeControls, { //This isn't really right, will create controllers for all tree menus on the page, need to single out somehow
                        menuData: menuData,
                        updateCb: rebuildMenu,
                        saveUrl: ajaxurl
                    });
                }

                var menuCacheInfo = getMenuCacheInfo(data.menuItemId);
                buildMenu(bindings, menuCacheInfo, menuData, false);
            }
        }

        function rebuildMenu() {
            createdItems = {};
            var childModel = bindings.getModel('children');
            childModel.setData([]);
            var menuCacheInfo = getMenuCacheInfo(menuData.menuItemId);
            buildMenu(bindings, menuCacheInfo, menuData, false);
        }

        function createIds(data) {
            data.menuItemId = getNextId();
            var folders = data.folders;
            for (var i = 0; i < folders.length; ++i) {
                //Recursion, I don't care, how nested is your menu that you run out of stack space here? Can a user really use that?
                createIds(folders[i]);
            }
        }

        function findParents(data, parent) {
            data.parent = parent;
            var folders = data.folders;
            for (var i = 0; i < folders.length; ++i) {
                //Recursion, I don't care, how nested is your menu that you run out of stack space here? Can a user really use that?
                findParents(folders[i], data);
            }
            var links = data.links;
            for (var i = 0; i < links.length; ++i) {
                links[i].parent = data;
            }
        }

        function removeParents(data) {
            delete data.parent;
            var folders = data.folders;
            for (var i = 0; i < folders.length; ++i) {
                //Recursion, I don't care, how nested is your menu that you run out of stack space here? Can a user really use that?
                removeParents(folders[i]);
            }
            var links = data.links;
            for (var i = 0; i < links.length; ++i) {
                delete links[i].parent;
            }
        }

        function getMenuCacheInfo(parentCategoryId) {
            if (!menuCache.hasOwnProperty(parentCategoryId)) {
                menuCache[parentCategoryId] = {
                    expanded: false,
                    id: parentCategoryId
                };
            }
            return menuCache[parentCategoryId];
        }

        function buildMenu(parentBindings, menuCacheInfo, folder, autoHide) {
            if (autoHide === undefined) {
                autoHide = true;
            }

            if (!createdItems[menuCacheInfo.id]) {
                var parentModel = parentBindings.getModel('children');
                var list = null;
                parentModel.setData({}, function (created) {
                    list = created;
                });
                createdItems[menuCacheInfo.id] = true;

                var foldersModel = list.getModel('folders');
                var linksModel = list.getModel('links');

                foldersModel.setData(folder.folders, function (folderComponent, data) {
                    var id = data.menuItemId;
                    var menuCacheInfo = getMenuCacheInfo(id);

                    var listener = {
                        toggleMenuItem: function (evt) {
                            evt.preventDefault();

                            buildMenu(folderComponent, menuCacheInfo, data);
                            toggleMenu(menuCacheInfo, folderComponent.getToggle('children'));
                        }
                    };
                    if (editMode) {
                        treeEditor.fireItemAdded(menuData, listener, data, rebuildMenu);
                    }
                    folderComponent.setListener(listener);

                    if (menuCacheInfo.expanded) {
                        buildMenu(folderComponent, menuCacheInfo, data, autoHide);
                    }
                });

                if (editMode) {
                    linksModel.setData(folder.links, function (component, data) {
                        var listener = {};
                        treeEditor.fireItemAdded(menuData, listener, data, rebuildMenu);
                        component.setListener(listener);
                    });
                }
                else {
                    linksModel.setData(folder.links);
                }
            }
        }

        function toggleMenu(menuCacheInfo, toggle, transitionTime) {
            if (transitionTime === undefined) {
                transitionTime = 200;
            }

            if (menuCacheInfo.expanded) {
                menuCacheInfo.expanded = false;
                toggle.off();
            }
            else {
                menuCacheInfo.expanded = true;
                toggle.on();
            }
        }
    }
});<|endoftext|>


<commit_before>/**
  Copyright 2018 LNFWebsite
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at
      http://www.apache.org/licenses/LICENSE-2.0
  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
**/

// * This function loads the video name
// * Updated video data capture method because of deprecation of player.getVideoData() in API (11/15/2017)

function getVideoName(id, callback) {
  var url = "https://www.youtube.com/watch?v=" + id;
  var get = {
    url: url
  };

  $.ajax({
    dataType: "json",
    url: "https://noembed.com/embed",
    data: get,
    success: function(result) {
      callback(result.title);
    }
  });
}

// * This function utilizes function above to add videos with applicable data
// * It used to handle data gathering on it's own, but stands as a wrapper to the async ajax above

function getVideoData(id) {
  var videoId = id;
  var videoTime = 0;

  getVideoName(id, function(name) {
    var videoName = name;
    videoName = encodeURIComponent(videoName).replace(/%20/g, " ");

    if (!inBoxSearch) {
      $("#inputBox").val("").attr("placeholder", placeholder);
      addVideo(videoName, videoTime, videoId);

      //if playlist, loop addAutoplayVideo for YouTube playlist import
      //autoplayListOverride is for utilizing YouTube Mix stations without loading all videos
      if (autoplayList && !autoplayListOverride) {
        console.log(autoplayVideoIteration);
        addAutoplayVideo();
      }
    }
    else {
      addSearchResult(videoName, id);
      searchResultsIteration++;
      if (searchResultsIteration < quickSearchVideos.length - 1) {
        quickSearch("");
      }
      else {
        inBoxSearch = false;
      }
    }
  });
}

// * This function sets the video time on video play
// * It is handled this way since YouTube does not set player.getDuration() until play. Screw you YouTube.

function setVideoTime() {
  if (videos[videoIteration][1] === 0) {
    var name = videos[videoIteration][0];
    var time = player.getDuration();
    time = Math.round(time);
    var printTime = msConversion(time * 1000);

    videos[videoIteration][1] = time;
    removeVideoFromList(videoIteration, false);
    addVideoToList(name, printTime, videoIteration, false);
    restoreHighlight(videoIteration);
    setPlaylist();
  }
}
<commit_msg>skip videos with undefined title<commit_after>/**
  Copyright 2018 LNFWebsite
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at
      http://www.apache.org/licenses/LICENSE-2.0
  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
**/

// * This function loads the video name
// * Updated video data capture method because of deprecation of player.getVideoData() in API (11/15/2017)

function getVideoName(id, callback) {
  var url = "https://www.youtube.com/watch?v=" + id;
  var get = {
    url: url
  };

  $.ajax({
    dataType: "json",
    url: "https://noembed.com/embed",
    data: get,
    success: function(result) {
      callback(result.title);
    }
  });
}

// * This function utilizes function above to add videos with applicable data
// * It used to handle data gathering on it's own, but stands as a wrapper to the async ajax above

function getVideoData(id) {
  var videoId = id;
  var videoTime = 0;

  getVideoName(id, function(name) {
    var videoName = name;
    videoName = encodeURIComponent(videoName).replace(/%20/g, " ");

    if (!inBoxSearch) {
      //skip videos with 'undefined' title
      if (videoName !== "undefined") {
        $("#inputBox").val("").attr("placeholder", placeholder);
        addVideo(videoName, videoTime, videoId);

        //if playlist, loop addAutoplayVideo for YouTube playlist import
        //autoplayListOverride is for utilizing YouTube Mix stations without loading all videos
        if (autoplayList && !autoplayListOverride) {
          console.log(autoplayVideoIteration);
          addAutoplayVideo();
        }
      }
      else {
        searchResultsIteration++;
        getVideoData(quickSearchVideos[searchResultsIteration]);
      }
    }
    else {
      addSearchResult(videoName, id);
      searchResultsIteration++;
      if (searchResultsIteration < quickSearchVideos.length - 1) {
        quickSearch("");
      }
      else {
        inBoxSearch = false;
      }
    }
  });
}

// * This function sets the video time on video play
// * It is handled this way since YouTube does not set player.getDuration() until play. Screw you YouTube.

function setVideoTime() {
  if (videos[videoIteration][1] === 0) {
    var name = videos[videoIteration][0];
    var time = player.getDuration();
    time = Math.round(time);
    var printTime = msConversion(time * 1000);

    videos[videoIteration][1] = time;
    removeVideoFromList(videoIteration, false);
    addVideoToList(name, printTime, videoIteration, false);
    restoreHighlight(videoIteration);
    setPlaylist();
  }
}
<|endoftext|>


<commit_before>// TODO: have you ever heard of classes? or promises?
// TODO: better way to call serverless binary?

'use strict';

const exec = require('child_process').exec;
const fs = require('fs');
const path = require('path');

const urlRegex = require('url-regex');
const YAML = require('yamljs');

const serverlessPath = path.join(require.resolve('serverless'), '../../bin/serverless');

let service = '';

module.exports = {
  prepareConcurrency: (config, callback) => {
    let functions = {};

    for (var i = 0; i < config.test.concurrency; i++) {
      functions['test' + i] = generateFunction(config, i);
    }

    let ymlPath = path.join(__dirname, config.provider.name, 'serverless.yml');

    YAML.load(ymlPath, yml => {
      yml.functions = functions;
      yml = prepareYAML(config, yml);

      let ymlString = YAML.stringify(yml, null, 4);

      fs.writeFile(ymlPath, ymlString, err => {
        if (err) throw err;
        callback();
      });
    });
  },
  deploy: (config, callback) => {
    console.log('Beginning deployment to ' + config.provider.name);

    let deploymentPath = path.join(__dirname, config.provider.name);
    let child = exec('cd ' + deploymentPath + '&& node ' + serverlessPath + ' remove & node ' + serverlessPath + ' deploy');

    let stdout = '';
    child.stdout.on('data', data => {
      stdout += data;
      process.stdout.write(data);
    });

    child.stderr.on('data', data => process.stderr.write(data));

    child.on('close', code => {
      if (code != 0) {
        throw new Error('Provider deployment exited with failure code: ' + code);
      }

      let uris = extractUris(config, stdout.match(urlRegex()));

      console.log('Finished deployment to ' + config.provider.name);

      callback(uris);
    });
  },
  cleanupDeployment: (config) => {
    console.log('Beginning ' + config.provider.name + ' deployment cleanup');

    let deploymentPath = path.join(__dirname, config.provider.name);
    let child = exec('cd ' + deploymentPath + '&& node ' + serverlessPath + ' remove');

    child.stdout.on('data', data => process.stdout.write(data));

    child.stderr.on('data', data => process.stderr.write(data));

    child.on('close', code => {
      if (code != 0) {
        throw new Error('Provider deployment cleanup exited with failure code: ' + code);
      }

      console.log('Finished ' + config.provider.name + ' deployment cleanup');
    });
  }
};

function prepareYAML(config, yml) {
  switch(config.provider.name) {
    case 'alphabet':
      config.provider.project = config.provider.project || generateRandomLetters(12);
      yml.provider.project = config.provider.project;
      config.provider.credentials = config.provider.credentials || '~/.gcloud/keyfile.json';
      yml.provider.credentials = config.provider.credentials;
      break;
    case 'microsoft':
      config.provider.service = config.provider.service || generateRandomLetters(12);
      yml.service = config.provider.service;
      break;
  }
  return yml;
}

function generateFunction(config, index) {
  switch(config.provider.name) {
    case 'alphabet':
      return {
        handler: 'test',
        availableMemoryMb: 512,
        events: [{
          http: true
        }]
      };
    case 'amazon':
      return {
        handler: 'handler.test',
        events: [{
          http: {
            path: '/',
            method: 'post',
            private: false
          }
        }]
      };
    case 'ibm':
      return {
        handler: 'handler.test',
        memory: 512,
        events: [{
          http: 'POST test' + index
        }]
      };
    case 'microsoft':
      return {
        handler: 'handler.test',
        events: [{
          http: true,
          'x-azure-settings': {
            authLevel: 'anonymous'
          }
        }]
      };
  }
}

function extractUris(config, uris) {
  let uri = '';
  switch (config.provider.name) {
    case 'ibm':
      uri = uris[0];
      for (let i = 0; i < config.test.concurrency; i++) {
        uris[i] = uri + '/test' + i;
      }
      return uris;
    case 'microsoft':
      uris = [];
      uri = 'http://' + config.provider.service + '.azurewebsites.net/api';
      for (let i = 0; i < config.test.concurrency; i++) {
        uris[i] = uri + '/test' + i;
      }
      return uris;
    default:
      return uris;
  }
}

function generateRandomLetters(length)
{
    let possibilities = "abcdefghijklmnopqrstuvwxyz";
    let text = "";
    for(let i = 0; i < length; i++) {
        text += possibilities.charAt(Math.floor(Math.random() * possibilities.length));
    }
    return text;
}
<commit_msg>update Azure support, IBM support, fix Linux operability issue<commit_after>// TODO: have you ever heard of classes? or promises?
// TODO: better way to call serverless binary?

'use strict';

const exec = require('child_process').exec;
const fs = require('fs');
const path = require('path');

const urlRegex = require('url-regex');
const YAML = require('yamljs');

const serverlessPath = path.join(require.resolve('serverless'), '../../bin/serverless');

let service = '';

module.exports = {
  prepareConcurrency: (config, callback) => {
    let functions = {};

    for (var i = 0; i < config.test.concurrency; i++) {
      functions['test' + i] = generateFunction(config, i);
    }

    let ymlPath = path.join(__dirname, config.provider.name, 'serverless.yml');

    YAML.load(ymlPath, yml => {
      yml.functions = functions;
      yml = prepareYAML(config, yml);

      let ymlString = YAML.stringify(yml, null, 4);

      fs.writeFile(ymlPath, ymlString, err => {
        if (err) throw err;
        callback();
      });
    });
  },
  deploy: (config, callback) => {
    console.log('Beginning deployment to ' + config.provider.name);

    let deploymentPath = path.join(__dirname, config.provider.name);
    let remove = exec('cd ' + deploymentPath + '&& node ' + serverlessPath + ' remove');

    remove.stdout.on('data', data => process.stdout.write(data));

    remove.on('close', code => {
      let deploy = exec('cd ' + deploymentPath + '&& node ' + serverlessPath + ' deploy');

      let stdout = '';
      deploy.stdout.on('data', data => {
        stdout += data;
        process.stdout.write(data);
      });

      deploy.stderr.on('data', data => process.stderr.write(data));

      deploy.on('close', code => {
        if (code != 0) {
          throw new Error('Provider deployment exited with failure code: ' + code);
        }

        let uris = extractUris(config, stdout.match(urlRegex()));

        console.log('Finished deployment to ' + config.provider.name);

        callback(uris);
      });
    });
  },
  cleanupDeployment: (config) => {
    console.log('Beginning ' + config.provider.name + ' deployment cleanup');

    let deploymentPath = path.join(__dirname, config.provider.name);
    let child = exec('cd ' + deploymentPath + '&& node ' + serverlessPath + ' remove');

    child.stdout.on('data', data => process.stdout.write(data));

    child.stderr.on('data', data => process.stderr.write(data));

    child.on('close', code => {
      if (code != 0) {
        throw new Error('Provider deployment cleanup exited with failure code: ' + code);
      }

      console.log('Finished ' + config.provider.name + ' deployment cleanup');
    });
  }
};

function prepareYAML(config, yml) {
  switch(config.provider.name) {
    case 'alphabet':
      config.provider.project = config.provider.project || generateRandomLetters(12);
      yml.provider.project = config.provider.project;
      config.provider.credentials = config.provider.credentials || '~/.gcloud/keyfile.json';
      yml.provider.credentials = config.provider.credentials;
      break;
    case 'microsoft':
      config.provider.service = config.provider.service || generateRandomLetters(12);
      yml.service = config.provider.service;
      break;
  }
  return yml;
}

function generateFunction(config, index) {
  switch(config.provider.name) {
    case 'alphabet':
      return {
        handler: 'test',
        availableMemoryMb: 512,
        events: [{
          http: true
        }]
      };
    case 'amazon':
      return {
        handler: 'handler.test',
        events: [{
          http: {
            path: '/',
            method: 'post',
            private: false
          }
        }]
      };
    case 'ibm':
      return {
        handler: 'handler.test',
        memory: 512,
        events: [{
          http: 'POST test' + index
        }]
      };
    case 'microsoft':
      return {
        handler: 'handler.test',
        events: [{
          http: true,
          'x-azure-settings': {
            authLevel: 'anonymous'
          }
        }]
      };
  }
}

function extractUris(config, uris) {
  switch (config.provider.name) {
    case 'microsoft':
      uris = [];
      let uri = 'http://' + config.provider.service + '.azurewebsites.net/api';
      for (let i = 0; i < config.test.concurrency; i++) {
        uris[i] = uri + '/test' + i;
      }
      return uris;
    default:
      return uris;
  }
}

function generateRandomLetters(length)
{
    let possibilities = "abcdefghijklmnopqrstuvwxyz";
    let text = "";
    for(let i = 0; i < length; i++) {
        text += possibilities.charAt(Math.floor(Math.random() * possibilities.length));
    }
    return text;
}
<|endoftext|>


<commit_before>const {app, BrowserWindow, ipcMain} = require('electron');
const path = require('path');
const jayson = require('jayson');
// tree-kill has better cross-platform handling of
// killing a process.  child-process.kill was unreliable
const kill = require('tree-kill');
const child_process = require('child_process');
const assert = require('assert');

let client = jayson.client.http('http://localhost:5279/lbryapi');
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win;
// Also keep the daemon subprocess alive
let daemonSubprocess;

// This is set to true right before we try to shut the daemon subprocess --
// if it dies when we didn't ask it to shut down, we want to alert the user.
let daemonSubprocessKillRequested = false;

// When a quit is attempted, we cancel the quit, do some preparations, then
// this is set to true and app.quit() is called again to quit for real.
let readyToQuit = false;

/*
 * Replacement for Electron's shell.openItem. The Electron version doesn't
 * reliably work from the main process, and we need to be able to run it
 * when no windows are open.
 */
function openItem(fullPath) {
    const subprocOptions = {
      detached: true,
      stdio: 'ignore',
    };

    let child;
    if (process.platform == 'darwin') {
      child = child_process.spawn('open', [fullPath], subprocOptions);
    } else if (process.platform == 'linux') {
      child = child_process.spawn('xdg-open', [fullPath], subprocOptions);
    } else if (process.platform == 'win32') {
      child = child_process.execSync('start', [fullPath], subprocOptions);
    }

    // Causes child process reference to be garbage collected, allowing main process to exit
    child.unref();
}

function createWindow () {
  win = new BrowserWindow({backgroundColor: '#155b4a'})
  win.maximize()
  //win.webContents.openDevTools()
  win.loadURL(`file://${__dirname}/dist/index.html`)
  win.on('closed', () => {
    win = null
  })
};

function handleDaemonSubprocessExited() {
  console.log('The daemon has exited.');
  daemonSubprocess = null;
  if (!daemonSubprocessKillRequested) {
    // We didn't stop the daemon subprocess on purpose, so display a
    // warning and schedule a quit.
    //
    // TODO: maybe it would be better to restart the daemon?
    if (win) {
      console.log('Did not request daemon stop, so quitting in 5 seconds.');
      win.loadURL(`file://${__dirname}/dist/warning.html`);
      setTimeout(quitNow, 5000);
    } else {
      console.log('Did not request daemon stop, so quitting.');
      quitNow();
    }
  }
}

function launchDaemon() {
  assert(!daemonSubprocess, 'Tried to launch daemon twice');

  if (process.env.LBRY_DAEMON) {
    executable = process.env.LBRY_DAEMON;
  } else {
    executable = path.join(__dirname, 'dist', 'lbrynet-daemon');
  }
  console.log('Launching daemon:', executable)
  daemonSubprocess = child_process.spawn(executable)
  // Need to handle the data event instead of attaching to
  // process.stdout because the latter doesn't work. I believe on
  // windows it buffers stdout and we don't get any meaningful output
  daemonSubprocess.stdout.on('data', (buf) => {console.log(String(buf).trim());});
  daemonSubprocess.stderr.on('data', (buf) => {console.log(String(buf).trim());});
  daemonSubprocess.on('exit', handleDaemonSubprocessExited);
  console.log('lbrynet daemon has launched')
}

/*
 * Quits without any preparation. When a quit is requested (either through the
 * interface or through app.quit()), we abort the quit, try to shut down the daemon,
 * and then call this to quit for real.
 */
function quitNow() {
  readyToQuit = true;
  app.quit();
}

app.on('ready', function(){
  launchDaemonIfNotRunning();
  createWindow();
});

function launchDaemonIfNotRunning() {
  // Check if the daemon is already running. If we get
  // an error its because its not running
  console.log('Checking for lbrynet daemon');
  client.request(
    'status', [],
    function (err, res) {
      if (err) {
        console.log('lbrynet daemon needs to be launched')
        launchDaemon();
      } else {
        console.log('lbrynet daemon is already running')
      }
    }
  );
}

/*
 * Last resort for killing unresponsive daemon instances.
 * Looks for any processes called "lbrynet-daemon" and
 * tries to force kill them.
 */
function forceKillAllDaemonsAndQuit() {
  console.log('Attempting to force kill any running lbrynet-daemon instances...');

  const fgrepOut = child_process.spawnSync('pgrep', ['-x', 'lbrynet-daemon'], {encoding: 'utf8'}).stdout;
  const daemonPids = fgrepOut.match(/\d+/g);
  if (!daemonPids) {
    console.log('No lbrynet-daemon found running.');
    quitNow();
  } else {
    console.log(`Found ${daemonPids.length} running daemon instances. Attempting to force kill...`);

    for (const pid of daemonPids) {
      const daemonKillAttemptsComplete = 0;
      kill(pid, 'SIGKILL', (err) => {
        daemonKillAttemptsComplete++;
        if (err) {
          console.log(`Failed to force kill running daemon with pid ${pid}. Error message: ${err.message}`);
        }
        if (daemonKillAttemptsComplete >= daemonPids.length) {
          quitNow();
        }
      });
    }
  }
}

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('before-quit', (event) => {
  if (!readyToQuit) {
    // We need to shutdown the daemon before we're ready to actually quit. This
    // event will be triggered re-entrantly once preparation is done.
    event.preventDefault();
    shutdownDaemonAndQuit();
  } else {
    console.log('Quitting.')
  }
});

app.on('activate', () => {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (win === null) {
    createWindow()
  }
});

// When a quit is attempted, this is called. It attempts to shutdown the daemon,
// then calls quitNow() to quit for real.
function shutdownDaemonAndQuit(evenIfNotStartedByApp = false) {
  if (daemonSubprocess) {
    console.log('Killing lbrynet-daemon process');
    kill(daemonSubprocess.pid, undefined, (err) => {
      console.log('Killed lbrynet-daemon process');
      requestedDaemonSubprocessKilled = true;
      quitNow();
    });
  } else if (evenIfNotStartedByApp) {
    console.log('Stopping lbrynet-daemon, even though app did not start it');
    client.request('daemon_stop', [], (err, res) => {
      if (err) {
        // We could get an error because the daemon is already stopped (good)
        // or because it's running but not responding properly (bad).
        // So try to force kill any daemons that are still running.

        console.log(`received error when stopping lbrynet-daemon. Error message: ${err.message}`);
        forceKillAllDaemonsAndQuit();
      } else {
        console.log('Successfully stopped daemon via RPC call.')
        quitNow();
      }
    });
  } else {
    console.log('Not killing lbrynet-daemon because app did not start it');
    quitNow();
  }

  // Is it safe to start the installer before the daemon finishes running?
  // If not, we should wait until the daemon is closed before we start the install.
}

function upgrade(event, installerPath) {
  app.on('quit', () => {
    console.log('Launching upgrade installer at', installerPath);
    // This gets triggered called after *all* other quit-related events, so
    // we'll only get here if we're fully prepared and quitting for real.
    openItem(installerPath);
  });

  if (win) {
    win.loadURL(`file://${__dirname}/dist/upgrade.html`);
  }

  app.quit();
  // wait for daemon to shut down before upgrading
  // what to do if no shutdown in a long time?
  console.log('Update downloaded to', installerPath);
  console.log('The app will close, and you will be prompted to install the latest version of LBRY.');
  console.log('After the install is complete, please reopen the app.');
}

ipcMain.on('upgrade', upgrade);
<commit_msg>Add code to get process IDs for daemon on Windows<commit_after>const {app, BrowserWindow, ipcMain} = require('electron');
const path = require('path');
const jayson = require('jayson');
// tree-kill has better cross-platform handling of
// killing a process.  child-process.kill was unreliable
const kill = require('tree-kill');
const child_process = require('child_process');
const assert = require('assert');

let client = jayson.client.http('http://localhost:5279/lbryapi');
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win;
// Also keep the daemon subprocess alive
let daemonSubprocess;

// This is set to true right before we try to shut the daemon subprocess --
// if it dies when we didn't ask it to shut down, we want to alert the user.
let daemonSubprocessKillRequested = false;

// When a quit is attempted, we cancel the quit, do some preparations, then
// this is set to true and app.quit() is called again to quit for real.
let readyToQuit = false;

/*
 * Replacement for Electron's shell.openItem. The Electron version doesn't
 * reliably work from the main process, and we need to be able to run it
 * when no windows are open.
 */
function openItem(fullPath) {
    const subprocOptions = {
      detached: true,
      stdio: 'ignore',
    };

    let child;
    if (process.platform == 'darwin') {
      child = child_process.spawn('open', [fullPath], subprocOptions);
    } else if (process.platform == 'linux') {
      child = child_process.spawn('xdg-open', [fullPath], subprocOptions);
    } else if (process.platform == 'win32') {
      child = child_process.execSync('start', [fullPath], subprocOptions);
    }

    // Causes child process reference to be garbage collected, allowing main process to exit
    child.unref();
}

function getPidsForProcessName(name) {
  if (process.platform == 'windows') {
    const tasklistOut = child_process.execSync('tasklist',
      ['/fi', `Imagename eq ${name}.exe`, '/nh'],
      {encoding: 'utf8'}
    ).stdout;
    return tasklistOut.match(/[^\n]+/g).map((line) => line.split(/\s+/)[1]); // Second column of every non-empty line
  } else {
    const pgrepOut = child_process.spawnSync('pgrep', ['-x', name], {encoding: 'utf8'}).stdout;
    return pgrepOut.match(/\d+/g);
  }
}

function createWindow () {
  win = new BrowserWindow({backgroundColor: '#155b4a'})
  win.maximize()
  //win.webContents.openDevTools()
  win.loadURL(`file://${__dirname}/dist/index.html`)
  win.on('closed', () => {
    win = null
  })
};

function handleDaemonSubprocessExited() {
  console.log('The daemon has exited.');
  daemonSubprocess = null;
  if (!daemonSubprocessKillRequested) {
    // We didn't stop the daemon subprocess on purpose, so display a
    // warning and schedule a quit.
    //
    // TODO: maybe it would be better to restart the daemon?
    if (win) {
      console.log('Did not request daemon stop, so quitting in 5 seconds.');
      win.loadURL(`file://${__dirname}/dist/warning.html`);
      setTimeout(quitNow, 5000);
    } else {
      console.log('Did not request daemon stop, so quitting.');
      quitNow();
    }
  }
}

function launchDaemon() {
  assert(!daemonSubprocess, 'Tried to launch daemon twice');

  if (process.env.LBRY_DAEMON) {
    executable = process.env.LBRY_DAEMON;
  } else {
    executable = path.join(__dirname, 'dist', 'lbrynet-daemon');
  }
  console.log('Launching daemon:', executable)
  daemonSubprocess = child_process.spawn(executable)
  // Need to handle the data event instead of attaching to
  // process.stdout because the latter doesn't work. I believe on
  // windows it buffers stdout and we don't get any meaningful output
  daemonSubprocess.stdout.on('data', (buf) => {console.log(String(buf).trim());});
  daemonSubprocess.stderr.on('data', (buf) => {console.log(String(buf).trim());});
  daemonSubprocess.on('exit', handleDaemonSubprocessExited);
  console.log('lbrynet daemon has launched')
}

/*
 * Quits without any preparation. When a quit is requested (either through the
 * interface or through app.quit()), we abort the quit, try to shut down the daemon,
 * and then call this to quit for real.
 */
function quitNow() {
  readyToQuit = true;
  app.quit();
}

app.on('ready', function(){
  launchDaemonIfNotRunning();
  createWindow();
});

function launchDaemonIfNotRunning() {
  // Check if the daemon is already running. If we get
  // an error its because its not running
  console.log('Checking for lbrynet daemon');
  client.request(
    'status', [],
    function (err, res) {
      if (err) {
        console.log('lbrynet daemon needs to be launched')
        launchDaemon();
      } else {
        console.log('lbrynet daemon is already running')
      }
    }
  );
}

/*
 * Last resort for killing unresponsive daemon instances.
 * Looks for any processes called "lbrynet-daemon" and
 * tries to force kill them.
 */
function forceKillAllDaemonsAndQuit() {
  console.log('Attempting to force kill any running lbrynet-daemon instances...');

  const daemonPids = getPidsForProcessName('lbrynet-daemon');
  if (!daemonPids) {
    console.log('No lbrynet-daemon found running.');
    quitNow();
  } else {
    console.log(`Found ${daemonPids.length} running daemon instances. Attempting to force kill...`);

    for (const pid of daemonPids) {
      const daemonKillAttemptsComplete = 0;
      kill(pid, 'SIGKILL', (err) => {
        daemonKillAttemptsComplete++;
        if (err) {
          console.log(`Failed to force kill running daemon with pid ${pid}. Error message: ${err.message}`);
        }
        if (daemonKillAttemptsComplete >= daemonPids.length) {
          quitNow();
        }
      });
    }
  }
}

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('before-quit', (event) => {
  if (!readyToQuit) {
    // We need to shutdown the daemon before we're ready to actually quit. This
    // event will be triggered re-entrantly once preparation is done.
    event.preventDefault();
    shutdownDaemonAndQuit();
  } else {
    console.log('Quitting.')
  }
});

app.on('activate', () => {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (win === null) {
    createWindow()
  }
});

// When a quit is attempted, this is called. It attempts to shutdown the daemon,
// then calls quitNow() to quit for real.
function shutdownDaemonAndQuit(evenIfNotStartedByApp = false) {
  if (daemonSubprocess) {
    console.log('Killing lbrynet-daemon process');
    kill(daemonSubprocess.pid, undefined, (err) => {
      console.log('Killed lbrynet-daemon process');
      requestedDaemonSubprocessKilled = true;
      quitNow();
    });
  } else if (evenIfNotStartedByApp) {
    console.log('Stopping lbrynet-daemon, even though app did not start it');
    client.request('daemon_stop', [], (err, res) => {
      if (err) {
        // We could get an error because the daemon is already stopped (good)
        // or because it's running but not responding properly (bad).
        // So try to force kill any daemons that are still running.

        console.log(`received error when stopping lbrynet-daemon. Error message: ${err.message}`);
        forceKillAllDaemonsAndQuit();
      } else {
        console.log('Successfully stopped daemon via RPC call.')
        quitNow();
      }
    });
  } else {
    console.log('Not killing lbrynet-daemon because app did not start it');
    quitNow();
  }

  // Is it safe to start the installer before the daemon finishes running?
  // If not, we should wait until the daemon is closed before we start the install.
}

function upgrade(event, installerPath) {
  app.on('quit', () => {
    console.log('Launching upgrade installer at', installerPath);
    // This gets triggered called after *all* other quit-related events, so
    // we'll only get here if we're fully prepared and quitting for real.
    openItem(installerPath);
  });

  if (win) {
    win.loadURL(`file://${__dirname}/dist/upgrade.html`);
  }

  app.quit();
  // wait for daemon to shut down before upgrading
  // what to do if no shutdown in a long time?
  console.log('Update downloaded to', installerPath);
  console.log('The app will close, and you will be prompted to install the latest version of LBRY.');
  console.log('After the install is complete, please reopen the app.');
}

ipcMain.on('upgrade', upgrade);
<|endoftext|>


<commit_before>var remoteUrl = "https://api.carpoolvote.com/live";

// Create a data object containing all URL query string data:
var data = tinyQuery.getAll();

if (data.uuid_driver) {
  data.uuid = data.uuid || data.uuid_driver;
  data.type = data.type || 'driver';
}
if (data.uuid_rider) {
  data.uuid = data.uuid || data.uuid_rider;
  data.type = data.type || 'rider';
}

// Cache jQuery selectors:
var $login = $('#login'),
  $manage = $('#manage'),
  $info = $manage.find('#info');

if (data.uuid) {
  $login.find('#enter-uuid').remove();
}
if (data.type) {
  $login.find('#enter-type').remove();
}

$login.validator().on('submit', function(e) {
  if (e.isDefaultPrevented()) {
    return;
  }
  data.uuid = data.uuid || $(this).find('#inputUUID').val();
  data.type = data.type || $(this).find('#enter-type').find('input:checked').val();
  data.phone = $(this).find('#inputPhoneNumber').val();

  $(this).slideUp(300).attr('aria-hidden','true');
  $manage.slideDown(300).attr('aria-hidden','false');
  updateUI();
  e.preventDefault();
});

function updateUI(uuid, type, phone) {
  $manage.find('#btnCancelDriveOffer').toggle(data.type === 'driver');
  $manage.find('#btnCancelDriverMatch').toggle(data.type === 'driver' && data.score !== 'undefined');
  $manage.find('#btnPauseDriverMatch').toggle(data.type === 'driver' && data.score !== 'undefined');

  $manage.find('#btnCancelRideRequest').toggle(data.type === 'rider');
  $manage.find('#btnCancelRiderMatch').toggle(data.type === 'rider' && data.score !== 'undefined');

  $manage.find('#btnAcceptDriverMatch').toggle(data.type === 'driver' && data.score !== 'undefined');
}

$manage
  .on('click', '#btnCancelRideRequest', cancelRideRequest)
  .on('click', '#btnCancelRiderMatch', cancelRiderMatch)
  .on('click', '#btnCancelDriveOffer', cancelDriveOffer)
  .on('click', '#btnCancelDriverMatch', cancelDriverMatch)
  .on('click', '#btnPauseDriverMatch', pauseDriverMatch)
  .on('click', '#btnAcceptDriverMatch', acceptDriverMatch)
  .on('click', '#logout', logout);

function cancelRideRequest() {
  sendAjaxRequest(
    {
      uuid: data.uuid,
      RiderPhone: data.phone
    },
    '/cancel-ride-request'
  );
}

function cancelRiderMatch() {
  sendAjaxRequest(
    {
      UUID_driver: data.uuid_driver,
      UUID_rider: data.uuid,
      Score: data.score,
      RiderPhone: data.phone
    },
    '/cancel-rider-match'
  );
}

function cancelDriveOffer() {
  sendAjaxRequest(
    {
      uuid: data.uuid,
      DriverPhone: data.phone
    },
    '/cancel-drive-offer'
  );
}

function cancelDriverMatch() {
  sendAjaxRequest(
    {
      UUID_driver: data.uuid_driver,
      UUID_rider: data.uuid,
      Score: data.score,
      RiderPhone: data.phone
    },
    '/cancel-driver-match'
  );
}

function acceptDriverMatch() {
  sendAjaxRequest(
    {
      UUID_driver: data.uuid,
      UUID_rider: data.uuid_rider,
      Score: data.score,
      DriverPhone: data.phone
    },
    '/accept-driver-match'
  );
}

function pauseDriverMatch() {
  sendAjaxRequest(
    {
      uuid: data.uuid,
      DriverPhone: data.phone
    },
    '/pause-driver-match'
  );
}

function logout() {
  window.location.search = tinyQuery.remove(['type','uuid','uuid_driver','uuid_rider']);
}

function sendAjaxRequest(ajaxData, ajaxPath) {
  var url = tinyQuery.set(ajaxData, remoteUrl + ajaxPath);

  $info.html('⏳ Loading…');

  $.getJSON(url, function(dbInfo) {
    var keys = Object.keys(dbInfo);
    if (keys) {
      info = dbInfo[keys[0]].toString();
      $info.text('ℹ ' + info);
    }
  })
  .fail(function(err) {
    $info.text('⚠ ' + err.statusText);
  });
}
<commit_msg>Add confirm prompts for self-service cancel buttons<commit_after>var remoteUrl = "https://api.carpoolvote.com/live";

// Create a data object containing all URL query string data:
var data = tinyQuery.getAll();

if (data.uuid_driver) {
  data.uuid = data.uuid || data.uuid_driver;
  data.type = data.type || 'driver';
}
if (data.uuid_rider) {
  data.uuid = data.uuid || data.uuid_rider;
  data.type = data.type || 'rider';
}

// Cache jQuery selectors:
var $login = $('#login'),
  $manage = $('#manage'),
  $info = $manage.find('#info');

if (data.uuid) {
  $login.find('#enter-uuid').remove();
}
if (data.type) {
  $login.find('#enter-type').remove();
}

$login.validator().on('submit', function(e) {
  if (e.isDefaultPrevented()) {
    return;
  }
  data.uuid = data.uuid || $(this).find('#inputUUID').val();
  data.type = data.type || $(this).find('#enter-type').find('input:checked').val();
  data.phone = $(this).find('#inputPhoneNumber').val();

  $(this).slideUp(300).attr('aria-hidden','true');
  $manage.slideDown(300).attr('aria-hidden','false');
  updateUI();
  e.preventDefault();
});

function updateUI(uuid, type, phone) {
  $manage.find('#btnCancelDriveOffer').toggle(data.type === 'driver');
  $manage.find('#btnCancelDriverMatch').toggle(data.type === 'driver' && data.score !== 'undefined');
  $manage.find('#btnPauseDriverMatch').toggle(data.type === 'driver' && data.score !== 'undefined');

  $manage.find('#btnCancelRideRequest').toggle(data.type === 'rider');
  $manage.find('#btnCancelRiderMatch').toggle(data.type === 'rider' && data.score !== 'undefined');

  $manage.find('#btnAcceptDriverMatch').toggle(data.type === 'driver' && data.score !== 'undefined');
}

$manage
  .on('click', '#btnCancelRideRequest', cancelRideRequest)
  .on('click', '#btnCancelRiderMatch', cancelRiderMatch)
  .on('click', '#btnCancelDriveOffer', cancelDriveOffer)
  .on('click', '#btnCancelDriverMatch', cancelDriverMatch)
  .on('click', '#btnPauseDriverMatch', pauseDriverMatch)
  .on('click', '#btnAcceptDriverMatch', acceptDriverMatch)
  .on('click', '#logout', logout);

function cancelRideRequest() {
  if (!window.confirm('This will cancel your ride request. Are you sure you want to proceed?')) {
    return;
  }
  sendAjaxRequest(
    {
      uuid: data.uuid,
      RiderPhone: data.phone
    },
    '/cancel-ride-request'
  );
}

function cancelRiderMatch() {
  if (!window.confirm('This will cancel your ride match. Are you sure you want to proceed?')) {
    return;
  }
  sendAjaxRequest(
    {
      UUID_driver: data.uuid_driver,
      UUID_rider: data.uuid,
      Score: data.score,
      RiderPhone: data.phone
    },
    '/cancel-rider-match'
  );
}

function cancelDriveOffer() {
  if (!window.confirm('This will cancel your ride offer. Are you sure you want to proceed?')) {
    return;
  }
  sendAjaxRequest(
    {
      uuid: data.uuid,
      DriverPhone: data.phone
    },
    '/cancel-drive-offer'
  );
}

function cancelDriverMatch() {
  if (!window.confirm('This will cancel your ride match. Are you sure you want to proceed?')) {
    return;
  }
  sendAjaxRequest(
    {
      UUID_driver: data.uuid_driver,
      UUID_rider: data.uuid,
      Score: data.score,
      RiderPhone: data.phone
    },
    '/cancel-driver-match'
  );
}

function acceptDriverMatch() {
  sendAjaxRequest(
    {
      UUID_driver: data.uuid,
      UUID_rider: data.uuid_rider,
      Score: data.score,
      DriverPhone: data.phone
    },
    '/accept-driver-match'
  );
}

function pauseDriverMatch() {
  sendAjaxRequest(
    {
      uuid: data.uuid,
      DriverPhone: data.phone
    },
    '/pause-driver-match'
  );
}

function logout() {
  window.location.search = tinyQuery.remove(['type','uuid','uuid_driver','uuid_rider']);
}

function sendAjaxRequest(ajaxData, ajaxPath) {
  var url = tinyQuery.set(ajaxData, remoteUrl + ajaxPath);

  $info.html('⏳ Loading…');

  $.getJSON(url, function(dbInfo) {
    var keys = Object.keys(dbInfo);
    if (keys) {
      info = dbInfo[keys[0]].toString();
      $info.text('ℹ ' + info);
    }
  })
  .fail(function(err) {
    $info.text('⚠ ' + err.statusText);
  });
}
<|endoftext|>


<commit_before>/* AlloyFinger v0.1.10
 * By dntzhang
 * Github: https://github.com/AlloyTeam/AlloyFinger
 */
; (function () {
    function getLen(v) {
        return Math.sqrt(v.x * v.x + v.y * v.y);
    }

    function dot(v1, v2) {
        return v1.x * v2.x + v1.y * v2.y;
    }

    function getAngle(v1, v2) {
        var mr = getLen(v1) * getLen(v2);
        if (mr === 0) return 0;
        var r = dot(v1, v2) / mr;
        if (r > 1) r = 1;
        return Math.acos(r);
    }

    function cross(v1, v2) {
        return v1.x * v2.y - v2.x * v1.y;
    }

    function getRotateAngle(v1, v2) {
        var angle = getAngle(v1, v2);
        if (cross(v1, v2) > 0) {
            angle *= -1;
        }

        return angle * 180 / Math.PI;
    }

    var HandlerAdmin = function(el) {
        this.handlers = [];
        this.el = el;
    };

    HandlerAdmin.prototype.add = function(handler) {
        this.handlers.push(handler);
    }

    HandlerAdmin.prototype.del = function(handler) {
        if(!handler) this.handlers = [];

        for(var i=this.handlers.length; i>=0; i--) {
            if(this.handlers[i] === handler) {
                this.handlers.splice(i, 1);
            }
        }
    }

    HandlerAdmin.prototype.dispatch = function() {
        for(var i=0,len=this.handlers.length; i<len; i++) {
            var handler = this.handlers[i];
            if(typeof handler === 'function') handler.apply(this.el, arguments);
        }
    }

    function wrapFunc(el, handler) {
        var handlerAdmin = new HandlerAdmin(el);
        handlerAdmin.add(handler);

        return handlerAdmin;
    }

    var AlloyFinger = function (el, option) {

        this.element = typeof el == 'string' ? document.querySelector(el) : el;

        this.start = this.start.bind(this);
        this.move = this.move.bind(this);
        this.end = this.end.bind(this);
        this.cancel = this.cancel.bind(this);
        this.element.addEventListener("touchstart", this.start, false);
        this.element.addEventListener("touchmove", this.move, false);
        this.element.addEventListener("touchend", this.end, false);
        this.element.addEventListener("touchcancel", this.cancel, false);

        this.preV = { x: null, y: null };
        this.pinchStartLen = null;
        this.zoom = 1;
        this.isDoubleTap = false;

        var noop = function () { };

        this.rotate = wrapFunc(this.element, option.rotate || noop);
        this.touchStart = wrapFunc(this.element, option.touchStart || noop);
        this.multipointStart = wrapFunc(this.element, option.multipointStart || noop);
        this.multipointEnd = wrapFunc(this.element, option.multipointEnd || noop);
        this.pinch = wrapFunc(this.element, option.pinch || noop);
        this.swipe = wrapFunc(this.element, option.swipe || noop);
        this.tap = wrapFunc(this.element, option.tap || noop);
        this.doubleTap = wrapFunc(this.element, option.doubleTap || noop);
        this.longTap = wrapFunc(this.element, option.longTap || noop);
        this.singleTap = wrapFunc(this.element, option.singleTap || noop);
        this.pressMove = wrapFunc(this.element, option.pressMove || noop);
        this.twoFingerPressMove = wrapFunc(this.element, option.twoFingerPressMove || noop);
        this.touchMove = wrapFunc(this.element, option.touchMove || noop);
        this.touchEnd = wrapFunc(this.element, option.touchEnd || noop);
        this.touchCancel = wrapFunc(this.element, option.touchCancel || noop);

        this._cancelAllHandler = this.cancelAll.bind(this);
        window.removeEventListener('scroll', this._cancelAllHandler);

        window.addEventListener('scroll', this._cancelAllHandler);

        this.delta = null;
        this.last = null;
        this.now = null;
        this.tapTimeout = null;
        this.singleTapTimeout = null;
        this.longTapTimeout = null;
        this.swipeTimeout = null;
        this.x1 = this.x2 = this.y1 = this.y2 = null;
        this.preTapPosition = { x: null, y: null };
    };

    AlloyFinger.prototype = {
        start: function (evt) {
            if (!evt.touches) return;
            this.now = Date.now();
            this.x1 = evt.touches[0].pageX;
            this.y1 = evt.touches[0].pageY;
            this.delta = this.now - (this.last || this.now);
            this.touchStart.dispatch(evt, this.element);
            if (this.preTapPosition.x !== null) {
                this.isDoubleTap = (this.delta > 0 && this.delta <= 250 && Math.abs(this.preTapPosition.x - this.x1) < 30 && Math.abs(this.preTapPosition.y - this.y1) < 30);
            }
            this.preTapPosition.x = this.x1;
            this.preTapPosition.y = this.y1;
            this.last = this.now;
            var preV = this.preV,
                len = evt.touches.length;
            if (len > 1) {
                this._cancelLongTap();
                this._cancelSingleTap();
                var v = { x: evt.touches[1].pageX - this.x1, y: evt.touches[1].pageY - this.y1 };
                preV.x = v.x;
                preV.y = v.y;
                this.pinchStartLen = getLen(preV);
                this.multipointStart.dispatch(evt, this.element);
            }
            this._preventTap = false;
            this.longTapTimeout = setTimeout(function () {
                this.longTap.dispatch(evt, this.element);
                this._preventTap = true;
            }.bind(this), 750);
        },
        move: function (evt) {
            if (!evt.touches) return;
            var preV = this.preV,
                len = evt.touches.length,
                currentX = evt.touches[0].pageX,
                currentY = evt.touches[0].pageY;
            this.isDoubleTap = false;
            if (len > 1) {
                var sCurrentX = evt.touches[1].pageX,
                    sCurrentY = evt.touches[1].pageY
                var v = { x: evt.touches[1].pageX - currentX, y: evt.touches[1].pageY - currentY };

                if (preV.x !== null) {
                    if (this.pinchStartLen > 0) {
                        evt.zoom = getLen(v) / this.pinchStartLen;
                        this.pinch.dispatch(evt, this.element);
                    }

                    evt.angle = getRotateAngle(v, preV);
                    this.rotate.dispatch(evt, this.element);
                }
                preV.x = v.x;
                preV.y = v.y;

                if (this.x2 !== null && this.sx2 !== null) {
                    evt.deltaX = (currentX - this.x2 + sCurrentX - this.sx2) / 2;
                    evt.deltaY = (currentY - this.y2 + sCurrentY - this.sy2) / 2;
                } else {
                    evt.deltaX = 0;
                    evt.deltaY = 0;
                }
                this.twoFingerPressMove.dispatch(evt, this.element);

                this.sx2 = sCurrentX;
                this.sy2 = sCurrentY;
            } else {
                if (this.x2 !== null) {
                    evt.deltaX = currentX - this.x2;
                    evt.deltaY = currentY - this.y2;

                } else {
                    evt.deltaX = 0;
                    evt.deltaY = 0;
                }

                this.pressMove.dispatch(evt, this.element);
            }

            this.touchMove.dispatch(evt, this.element);

            this._cancelLongTap();
            this.x2 = currentX;
            this.y2 = currentY;

            if (len > 1) {
                evt.preventDefault();
            }
        },
        end: function (evt) {
            if (!evt.changedTouches) return;
            this._cancelLongTap();
            var self = this;
            if (evt.touches.length < 2) {
                this.multipointEnd.dispatch(evt, this.element);
                this.sx2 = this.sy2 = null;
            }

            //swipe
            if ((this.x2 && Math.abs(this.x1 - this.x2) > 30) ||
                (this.y2 && Math.abs(this.y1 - this.y2) > 30)) {
                evt.direction = this._swipeDirection(this.x1, this.x2, this.y1, this.y2);
                this.swipeTimeout = setTimeout(function () {
                    self.swipe.dispatch(evt, self.element);

                }, 0)
            } else {
                this.tapTimeout = setTimeout(function () {
                    if(!self._preventTap){
                        self.tap.dispatch(evt, self.element);
                    }
                    // trigger double tap immediately
                    if (self.isDoubleTap) {
                        self.doubleTap.dispatch(evt, self.element);
                        clearTimeout(self.singleTapTimeout);
                        self.isDoubleTap = false;
                    }
                }, 0)

                if (!self.isDoubleTap) {
                    self.singleTapTimeout = setTimeout(function () {
                        self.singleTap.dispatch(evt, self.element);
                    }, 250);
                }
            }

            this.touchEnd.dispatch(evt, this.element);

            this.preV.x = 0;
            this.preV.y = 0;
            this.zoom = 1;
            this.pinchStartLen = null;
            this.x1 = this.x2 = this.y1 = this.y2 = null;
        },
        cancelAll: function () {
            this._preventTap = true
            clearTimeout(this.singleTapTimeout);
            clearTimeout(this.tapTimeout);
            clearTimeout(this.longTapTimeout);
            clearTimeout(this.swipeTimeout);
        },
        cancel: function (evt) {
            this.cancelAll()
            this.touchCancel.dispatch(evt, this.element);
        },
        _cancelLongTap: function () {
            clearTimeout(this.longTapTimeout);
        },
        _cancelSingleTap: function () {
            clearTimeout(this.singleTapTimeout);
        },
        _swipeDirection: function (x1, x2, y1, y2) {
            return Math.abs(x1 - x2) >= Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : (y1 - y2 > 0 ? 'Up' : 'Down')
        },

        on: function(evt, handler) {
            if(this[evt]) {
                this[evt].add(handler);
            }
        },

        off: function(evt, handler) {
            if(this[evt]) {
                this[evt].del(handler);
            }
        },

        destroy: function() {
            if(this.singleTapTimeout) clearTimeout(this.singleTapTimeout);
            if(this.tapTimeout) clearTimeout(this.tapTimeout);
            if(this.longTapTimeout) clearTimeout(this.longTapTimeout);
            if(this.swipeTimeout) clearTimeout(this.swipeTimeout);

            this.element.removeEventListener("touchstart", this.start);
            this.element.removeEventListener("touchmove", this.move);
            this.element.removeEventListener("touchend", this.end);
            this.element.removeEventListener("touchcancel", this.cancel);

            this.rotate.del();
            this.touchStart.del();
            this.multipointStart.del();
            this.multipointEnd.del();
            this.pinch.del();
            this.swipe.del();
            this.tap.del();
            this.doubleTap.del();
            this.longTap.del();
            this.singleTap.del();
            this.pressMove.del();
            this.twoFingerPressMove.del()
            this.touchMove.del();
            this.touchEnd.del();
            this.touchCancel.del();

            this.preV = this.pinchStartLen = this.zoom = this.isDoubleTap = this.delta = this.last = this.now = this.tapTimeout = this.singleTapTimeout = this.longTapTimeout = this.swipeTimeout = this.x1 = this.x2 = this.y1 = this.y2 = this.preTapPosition = this.rotate = this.touchStart = this.multipointStart = this.multipointEnd = this.pinch = this.swipe = this.tap = this.doubleTap = this.longTap = this.singleTap = this.pressMove = this.touchMove = this.touchEnd = this.touchCancel = this.twoFingerPressMove = null;

            return null;
        }
    };

    if (typeof module !== 'undefined' && typeof exports === 'object') {
        module.exports = AlloyFinger;
    } else {
        window.AlloyFinger = AlloyFinger;
    }
})();
<commit_msg>fix singleTap problem<commit_after>/* AlloyFinger v0.1.10
 * By dntzhang
 * Github: https://github.com/AlloyTeam/AlloyFinger
 */
; (function () {
    function getLen(v) {
        return Math.sqrt(v.x * v.x + v.y * v.y);
    }

    function dot(v1, v2) {
        return v1.x * v2.x + v1.y * v2.y;
    }

    function getAngle(v1, v2) {
        var mr = getLen(v1) * getLen(v2);
        if (mr === 0) return 0;
        var r = dot(v1, v2) / mr;
        if (r > 1) r = 1;
        return Math.acos(r);
    }

    function cross(v1, v2) {
        return v1.x * v2.y - v2.x * v1.y;
    }

    function getRotateAngle(v1, v2) {
        var angle = getAngle(v1, v2);
        if (cross(v1, v2) > 0) {
            angle *= -1;
        }

        return angle * 180 / Math.PI;
    }

    var HandlerAdmin = function(el) {
        this.handlers = [];
        this.el = el;
    };

    HandlerAdmin.prototype.add = function(handler) {
        this.handlers.push(handler);
    }

    HandlerAdmin.prototype.del = function(handler) {
        if(!handler) this.handlers = [];

        for(var i=this.handlers.length; i>=0; i--) {
            if(this.handlers[i] === handler) {
                this.handlers.splice(i, 1);
            }
        }
    }

    HandlerAdmin.prototype.dispatch = function() {
        for(var i=0,len=this.handlers.length; i<len; i++) {
            var handler = this.handlers[i];
            if(typeof handler === 'function') handler.apply(this.el, arguments);
        }
    }

    function wrapFunc(el, handler) {
        var handlerAdmin = new HandlerAdmin(el);
        handlerAdmin.add(handler);

        return handlerAdmin;
    }

    var AlloyFinger = function (el, option) {

        this.element = typeof el == 'string' ? document.querySelector(el) : el;

        this.start = this.start.bind(this);
        this.move = this.move.bind(this);
        this.end = this.end.bind(this);
        this.cancel = this.cancel.bind(this);
        this.element.addEventListener("touchstart", this.start, false);
        this.element.addEventListener("touchmove", this.move, false);
        this.element.addEventListener("touchend", this.end, false);
        this.element.addEventListener("touchcancel", this.cancel, false);

        this.preV = { x: null, y: null };
        this.pinchStartLen = null;
        this.zoom = 1;
        this.isDoubleTap = false;

        var noop = function () { };

        this.rotate = wrapFunc(this.element, option.rotate || noop);
        this.touchStart = wrapFunc(this.element, option.touchStart || noop);
        this.multipointStart = wrapFunc(this.element, option.multipointStart || noop);
        this.multipointEnd = wrapFunc(this.element, option.multipointEnd || noop);
        this.pinch = wrapFunc(this.element, option.pinch || noop);
        this.swipe = wrapFunc(this.element, option.swipe || noop);
        this.tap = wrapFunc(this.element, option.tap || noop);
        this.doubleTap = wrapFunc(this.element, option.doubleTap || noop);
        this.longTap = wrapFunc(this.element, option.longTap || noop);
        this.singleTap = wrapFunc(this.element, option.singleTap || noop);
        this.pressMove = wrapFunc(this.element, option.pressMove || noop);
        this.twoFingerPressMove = wrapFunc(this.element, option.twoFingerPressMove || noop);
        this.touchMove = wrapFunc(this.element, option.touchMove || noop);
        this.touchEnd = wrapFunc(this.element, option.touchEnd || noop);
        this.touchCancel = wrapFunc(this.element, option.touchCancel || noop);

        this._cancelAllHandler = this.cancelAll.bind(this);
        window.removeEventListener('scroll', this._cancelAllHandler);

        window.addEventListener('scroll', this._cancelAllHandler);

        this.delta = null;
        this.last = null;
        this.now = null;
        this.tapTimeout = null;
        this.singleTapTimeout = null;
        this.longTapTimeout = null;
        this.swipeTimeout = null;
        this.x1 = this.x2 = this.y1 = this.y2 = null;
        this.preTapPosition = { x: null, y: null };
    };

    AlloyFinger.prototype = {
        start: function (evt) {
            if (!evt.touches) return;
            this.now = Date.now();
            this.x1 = evt.touches[0].pageX;
            this.y1 = evt.touches[0].pageY;
            this.delta = this.now - (this.last || this.now);
            this.touchStart.dispatch(evt, this.element);
            if (this.preTapPosition.x !== null) {
                this.isDoubleTap = (this.delta > 0 && this.delta <= 250 && Math.abs(this.preTapPosition.x - this.x1) < 30 && Math.abs(this.preTapPosition.y - this.y1) < 30);
                if (this.isDoubleTap) clearTimeout(self.singleTapTimeout);
            }
            this.preTapPosition.x = this.x1;
            this.preTapPosition.y = this.y1;
            this.last = this.now;
            var preV = this.preV,
                len = evt.touches.length;
            if (len > 1) {
                this._cancelLongTap();
                this._cancelSingleTap();
                var v = { x: evt.touches[1].pageX - this.x1, y: evt.touches[1].pageY - this.y1 };
                preV.x = v.x;
                preV.y = v.y;
                this.pinchStartLen = getLen(preV);
                this.multipointStart.dispatch(evt, this.element);
            }
            this._preventTap = false;
            this.longTapTimeout = setTimeout(function () {
                this.longTap.dispatch(evt, this.element);
                this._preventTap = true;
            }.bind(this), 750);
        },
        move: function (evt) {
            if (!evt.touches) return;
            var preV = this.preV,
                len = evt.touches.length,
                currentX = evt.touches[0].pageX,
                currentY = evt.touches[0].pageY;
            this.isDoubleTap = false;
            if (len > 1) {
                var sCurrentX = evt.touches[1].pageX,
                    sCurrentY = evt.touches[1].pageY
                var v = { x: evt.touches[1].pageX - currentX, y: evt.touches[1].pageY - currentY };

                if (preV.x !== null) {
                    if (this.pinchStartLen > 0) {
                        evt.zoom = getLen(v) / this.pinchStartLen;
                        this.pinch.dispatch(evt, this.element);
                    }

                    evt.angle = getRotateAngle(v, preV);
                    this.rotate.dispatch(evt, this.element);
                }
                preV.x = v.x;
                preV.y = v.y;

                if (this.x2 !== null && this.sx2 !== null) {
                    evt.deltaX = (currentX - this.x2 + sCurrentX - this.sx2) / 2;
                    evt.deltaY = (currentY - this.y2 + sCurrentY - this.sy2) / 2;
                } else {
                    evt.deltaX = 0;
                    evt.deltaY = 0;
                }
                this.twoFingerPressMove.dispatch(evt, this.element);

                this.sx2 = sCurrentX;
                this.sy2 = sCurrentY;
            } else {
                if (this.x2 !== null) {
                    evt.deltaX = currentX - this.x2;
                    evt.deltaY = currentY - this.y2;

                } else {
                    evt.deltaX = 0;
                    evt.deltaY = 0;
                }

                this.pressMove.dispatch(evt, this.element);
            }

            this.touchMove.dispatch(evt, this.element);

            this._cancelLongTap();
            this.x2 = currentX;
            this.y2 = currentY;

            if (len > 1) {
                evt.preventDefault();
            }
        },
        end: function (evt) {
            if (!evt.changedTouches) return;
            this._cancelLongTap();
            var self = this;
            if (evt.touches.length < 2) {
                this.multipointEnd.dispatch(evt, this.element);
                this.sx2 = this.sy2 = null;
            }

            //swipe
            if ((this.x2 && Math.abs(this.x1 - this.x2) > 30) ||
                (this.y2 && Math.abs(this.y1 - this.y2) > 30)) {
                evt.direction = this._swipeDirection(this.x1, this.x2, this.y1, this.y2);
                this.swipeTimeout = setTimeout(function () {
                    self.swipe.dispatch(evt, self.element);

                }, 0)
            } else {
                this.tapTimeout = setTimeout(function () {
                    if(!self._preventTap){
                        self.tap.dispatch(evt, self.element);
                    }
                    // trigger double tap immediately
                    if (self.isDoubleTap) {
                        self.doubleTap.dispatch(evt, self.element);
                        clearTimeout(self.singleTapTimeout);
                        self.isDoubleTap = false;
                    }
                }, 0)

                if (!self.isDoubleTap) {
                    self.singleTapTimeout = setTimeout(function () {
                        self.singleTap.dispatch(evt, self.element);
                    }, 250);
                }
            }

            this.touchEnd.dispatch(evt, this.element);

            this.preV.x = 0;
            this.preV.y = 0;
            this.zoom = 1;
            this.pinchStartLen = null;
            this.x1 = this.x2 = this.y1 = this.y2 = null;
        },
        cancelAll: function () {
            this._preventTap = true
            clearTimeout(this.singleTapTimeout);
            clearTimeout(this.tapTimeout);
            clearTimeout(this.longTapTimeout);
            clearTimeout(this.swipeTimeout);
        },
        cancel: function (evt) {
            this.cancelAll()
            this.touchCancel.dispatch(evt, this.element);
        },
        _cancelLongTap: function () {
            clearTimeout(this.longTapTimeout);
        },
        _cancelSingleTap: function () {
            clearTimeout(this.singleTapTimeout);
        },
        _swipeDirection: function (x1, x2, y1, y2) {
            return Math.abs(x1 - x2) >= Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : (y1 - y2 > 0 ? 'Up' : 'Down')
        },

        on: function(evt, handler) {
            if(this[evt]) {
                this[evt].add(handler);
            }
        },

        off: function(evt, handler) {
            if(this[evt]) {
                this[evt].del(handler);
            }
        },

        destroy: function() {
            if(this.singleTapTimeout) clearTimeout(this.singleTapTimeout);
            if(this.tapTimeout) clearTimeout(this.tapTimeout);
            if(this.longTapTimeout) clearTimeout(this.longTapTimeout);
            if(this.swipeTimeout) clearTimeout(this.swipeTimeout);

            this.element.removeEventListener("touchstart", this.start);
            this.element.removeEventListener("touchmove", this.move);
            this.element.removeEventListener("touchend", this.end);
            this.element.removeEventListener("touchcancel", this.cancel);

            this.rotate.del();
            this.touchStart.del();
            this.multipointStart.del();
            this.multipointEnd.del();
            this.pinch.del();
            this.swipe.del();
            this.tap.del();
            this.doubleTap.del();
            this.longTap.del();
            this.singleTap.del();
            this.pressMove.del();
            this.twoFingerPressMove.del()
            this.touchMove.del();
            this.touchEnd.del();
            this.touchCancel.del();

            this.preV = this.pinchStartLen = this.zoom = this.isDoubleTap = this.delta = this.last = this.now = this.tapTimeout = this.singleTapTimeout = this.longTapTimeout = this.swipeTimeout = this.x1 = this.x2 = this.y1 = this.y2 = this.preTapPosition = this.rotate = this.touchStart = this.multipointStart = this.multipointEnd = this.pinch = this.swipe = this.tap = this.doubleTap = this.longTap = this.singleTap = this.pressMove = this.touchMove = this.touchEnd = this.touchCancel = this.twoFingerPressMove = null;

            return null;
        }
    };

    if (typeof module !== 'undefined' && typeof exports === 'object') {
        module.exports = AlloyFinger;
    } else {
        window.AlloyFinger = AlloyFinger;
    }
})();
<|endoftext|>