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

 "use strict";

Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("chrome://tbsync/content/tbsync.jsm");

var tbSyncAccountSettings = {

    account: null,
    servertype: null,
    provider: null,
    settings: null,
    updateTimer: Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer),

    stripHost: function (document, account) {
        let host = document.getElementById('tbsync.accountsettings.pref.host').value;
        if (host.indexOf("https://") == 0) {
            host = host.replace("https://","");
            document.getElementById('tbsync.accountsettings.pref.https').checked = true;
            tbSync.db.setAccountSetting(account, "https", "1");
        } else if (host.indexOf("http://") == 0) {
            host = host.replace("http://","");
            document.getElementById('tbsync.accountsettings.pref.https').checked = false;
            tbSync.db.setAccountSetting(account, "https", "0");
        }

        while (host.endsWith("/")) { host = host.slice(0,-1); }
        document.getElementById('tbsync.accountsettings.pref.host').value = host
        tbSync.db.setAccountSetting(account, "host", host);
    },

    updateFolderListObserver: {
        observe: function (aSubject, aTopic, aData) {
            //only run if is request for this account and main frame is visible
            let account = aData;
            if (account == tbSyncAccountSettings.account && !document.getElementById('tbsync.accountsettings.frame').hidden) {
                //make sure, folderlist is visible, otherwise our updates will be discarded (may cause errors)
                tbSyncAccountSettings.updateGui();
                tbSyncAccountSettings.updateFolderList();
            }
        }
    },

    updateGuiObserver: {
        observe: function (aSubject, aTopic, aData) {
            //only run if is request for this account and main frame is visible
            let account = aData;
            if (account == tbSyncAccountSettings.account && !document.getElementById('tbsync.accountsettings.frame').hidden) {
                tbSyncAccountSettings.updateGui();
            }
        }
    },

    updateSyncstateObserver: {
        observe: function (aSubject, aTopic, aData) {
            //only run if is request for this account and main frame is visible
            let account = aData;
            if (account == tbSyncAccountSettings.account && !document.getElementById('tbsync.accountsettings.frame').hidden) {

                let syncstate = tbSync.getSyncData(account,"syncstate");
                if (syncstate == "accountdone") {
                        let status = tbSync.db.getAccountSetting(account, "status");
                        switch (status) {
                            case "401":
                                //only popup one password prompt window
                                if (tbSync.passWindowObj === null) {
                                    tbSync.passWindowObj = window.openDialog("chrome://tbsync/content/manager/password.xul", "passwordprompt", "centerscreen,chrome,resizable=no", tbSync.db.getAccount(account), function() {tbSync.syncAccount("sync", account);});
                                }
                                break;
                        }
                    tbSyncAccountSettings.updateGui();
                } else {
                    tbSyncAccountSettings.updateSyncstate();
                }
            }
        }
    },

    onload: function () {
        //load observers
        Services.obs.addObserver(tbSyncAccountSettings.updateFolderListObserver, "tbsync.updateFolderList", false);
        Services.obs.addObserver(tbSyncAccountSettings.updateGuiObserver, "tbsync.updateAccountSettingsGui", false);
        Services.obs.addObserver(tbSyncAccountSettings.updateSyncstateObserver, "tbsync.updateSyncstate", false);
        //get the selected account from the loaded URI
        tbSyncAccountSettings.account = window.location.toString().split("id=")[1];

        //get information for that acount
        tbSyncAccountSettings.provider = tbSync.db.getAccountSetting(tbSyncAccountSettings.account, "provider");
        tbSyncAccountSettings.settings = Object.keys(tbSync[tbSyncAccountSettings.provider].getDefaultAccountEntries()).sort();

        //load overlays from the provider (if any)
        tbSync.overlayManager.injectAllOverlays(window, "chrome://tbsync/content/manager/editAccount.xul?provider=" + tbSyncAccountSettings.provider);

        tbSync.prepareSyncDataObj(tbSyncAccountSettings.account);
        tbSyncAccountSettings.loadSettings();

        //done, folderlist must be updated while visible
        document.getElementById('tbsync.accountsettings.frame').hidden = false;
        tbSyncAccountSettings.updateFolderList();
    },

    onunload: function () {
        tbSyncAccountSettings.updateTimer.cancel();
        if (!document.getElementById('tbsync.accountsettings.frame').hidden) {
            Services.obs.removeObserver(tbSyncAccountSettings.updateFolderListObserver, "tbsync.updateFolderList");
            Services.obs.removeObserver(tbSyncAccountSettings.updateGuiObserver, "tbsync.updateAccountSettingsGui");
            Services.obs.removeObserver(tbSyncAccountSettings.updateSyncstateObserver, "tbsync.updateSyncstate");
        }
    },

   folderListVisible: function () {
        let box = document.getElementById('tbsync.accountsettings.folderlist').getBoundingClientRect();
        let visible = box.width && box.height;
        return visible;
    },

    /**
     * Run through all defined TbSync settings and if there is a corresponding
     * field in the settings dialog, fill it with the stored value.
     */
    loadSettings: function () {
        tbSyncAccountSettings.servertype = tbSync.db.getAccountSetting(tbSyncAccountSettings.account, "servertype");

        for (let i=0; i < tbSyncAccountSettings.settings.length; i++) {
            let pref = document.getElementById("tbsync.accountsettings.pref." + tbSyncAccountSettings.settings[i]);
            let label = document.getElementById("tbsync.accountsettings.label." + tbSyncAccountSettings.settings[i]);

            if (pref) {
                //is this a checkbox?
                if (pref.tagName == "checkbox") {
                    //BOOL
                    if (tbSync.db.getAccountSetting(tbSyncAccountSettings.account, tbSyncAccountSettings.settings[i])  == "1") pref.setAttribute("checked", true);
                    else pref.setAttribute("checked", false);
                } else {
                    //Not BOOL
                    pref.setAttribute("value", tbSync.db.getAccountSetting(tbSyncAccountSettings.account, tbSyncAccountSettings.settings[i]));
                }

                pref.onblur = function() {tbSyncAccountSettings.instantSaveSetting(this)};
            }
        }

        // special treatment for configuration label, which is a permanent setting and will not change by switching modes (only by unlocking, which will handle that)
        let configlabel = document.getElementById("tbsync.accountsettings.label.config");
        if (configlabel) {
            configlabel.setAttribute("value", tbSync.getLocalizedMessage("config." + tbSyncAccountSettings.servertype, tbSyncAccountSettings.provider));
        }

        tbSyncAccountSettings.updateGui();
    },

    updateGui: function () {
        let status = tbSync.db.getAccountSetting(tbSyncAccountSettings.account, "status");

        let isConnected = tbSync.isConnected(tbSyncAccountSettings.account);
        let isEnabled = tbSync.isEnabled(tbSyncAccountSettings.account);
        let isSyncing = tbSync.isSyncing(tbSyncAccountSettings.account);

        { //disable settings if connected or syncing
            let items = document.getElementsByClassName("lockIfConnected");
            for (let i=0; i < items.length; i++) {
                if (isConnected || isSyncing) items[i].setAttribute("disabled", true);
                else items[i].removeAttribute("disabled");
                items[i].style["color"] = isConnected || isSyncing ? "darkgrey" : "black";
            }
        }

        document.getElementById('tbsync.accountsettings.connectbtn.container').hidden = !(isEnabled && !isConnected && !isSyncing);
        //currently we use a fixed button which is hidden during sync
        //document.getElementById('tbsync.accountsettings.connectbtn').label = tbSync.getLocalizedMessage("manager." + (isSyncing ? "connecting" : "tryagain"));

        { //show elements if connected
            let items = document.getElementsByClassName("showIfConnected");
            for (let i=0; i < items.length; i++) {
                items[i].hidden = !isConnected;
            }
        }

        { //show elements if enabled
            let items = document.getElementsByClassName("showIfEnabled");
            for (let i=0; i < items.length; i++) {
                items[i].hidden = !isEnabled;
            }
        }

        document.getElementById('tbsync.accountsettings.enabled').checked = isEnabled;
        document.getElementById('tbsync.accountsettings.folderlist').disabled = isSyncing;
        document.getElementById('tbsync.accountsettings.syncbtn').disabled = isSyncing;
        document.getElementById('tbsync.accountsettings.connectbtn').disabled = isSyncing;

        tbSyncAccountSettings.updateSyncstate();
    },

    updateSyncstate: function () {
        tbSyncAccountSettings.updateTimer.cancel();
        document.getElementById('syncstate_link').textContent = "";
        document.getElementById('syncstate_link').setAttribute("dest", "");

        // if this account is beeing synced, display syncstate, otherwise print status
        let status = tbSync.db.getAccountSetting(tbSyncAccountSettings.account, "status");
        let isSyncing = tbSync.isSyncing(tbSyncAccountSettings.account);
        let isConnected = tbSync.isConnected(tbSyncAccountSettings.account);
        let isEnabled = tbSync.isEnabled(tbSyncAccountSettings.account);
        let syncdata = tbSync.getSyncData(tbSyncAccountSettings.account);

        if (isSyncing) {
            let accounts = tbSync.db.getAccounts().data;
            let target = "";

            if (accounts.hasOwnProperty(syncdata.account) && syncdata.folderID !== "" && syncdata.syncstate != "done") { //if "Done" do not print folder info syncstate
                target = " [" + tbSync.db.getFolderSetting(syncdata.account, syncdata.folderID, "name") + "]";
            }

            let parts = syncdata.syncstate.split("||");
            let syncstate = parts[0];
            let synctime = (parts.length>1 ? parts[1] : Date.now());

            let diff = Date.now() - synctime;
            let msg = tbSync.getLocalizedMessage("syncstate." + syncstate, tbSyncAccountSettings.provider);
            if (diff > 2000) msg = msg + " (" + Math.round((tbSync.prefSettings.getIntPref("timeout") - diff)/1000) + "s)";

            document.getElementById('syncstate').textContent = msg + target;

            if (syncstate.split(".")[0] == "send") {
                //re-schedule update, if this is a waiting syncstate
                tbSyncAccountSettings.updateTimer.init(tbSyncAccountSettings.updateSyncstate, 1000, 0);
            }
        } else {
            let localized = tbSync.getLocalizedMessage("status." + status, tbSyncAccountSettings.provider);
            if (!isEnabled) localized = tbSync.getLocalizedMessage("status." + "disabled", tbSyncAccountSettings.provider);

            //check, if this localized string contains a link
            let parts = localized.split("||");
            document.getElementById('syncstate').textContent = parts[0];
            if (parts.length==3) {
                    document.getElementById('syncstate_link').setAttribute("dest", parts[1]);
                    document.getElementById('syncstate_link').textContent = parts[2];
            }
        }

        //update syncstates of folders in folderlist, if visible
        if (tbSyncAccountSettings.folderListVisible()) {
            let folderList = document.getElementById("tbsync.accountsettings.folderlist");
            for (let i=0; i < folderList.getRowCount(); i++) {
                let item = folderList.getItemAtIndex(i);
                let folder = tbSync.db.getFolder(tbSyncAccountSettings.account, item.value);
                if (folder) {

                    let rowData = tbSync[tbSyncAccountSettings.provider].folderList.getRowData(folder, syncdata);
                    tbSync[tbSyncAccountSettings.provider].folderList.updateRow(document, item, rowData);

                }
            }
        }
    },

    updateFolderList: function () {
        //do not upate, if not visible (may cause errors)
        if (!tbSyncAccountSettings.folderListVisible())
            return;

        //clear folderlist
        let folderList = document.getElementById("tbsync.accountsettings.folderlist");
        for (let i=folderList.getRowCount()-1; i>=0; i--) {
            folderList.removeItemAt(i);
        }

        //rebuild folderlist
        let folderData = tbSync[tbSyncAccountSettings.provider].folderList.getSortedData(tbSyncAccountSettings.account);
        for (let i=0; i < folderData.length; i++) {
            //add new entry
            let newListItem = document.createElement("richlistitem");
            newListItem.setAttribute("value", folderData[i].folderID);

            tbSync[tbSyncAccountSettings.provider].folderList.addRow(document, newListItem, folderData[i]);

            //ensureElementIsVisible also forces internal update of rowCount, which sometimes is not updated automatically upon appendChild
            folderList.ensureElementIsVisible(folderList.appendChild(newListItem));
        }
    },

    instantSaveSetting: function (field) {
        let setting = field.id.replace("tbsync.accountsettings.pref.","");
        let value = "";

        if (field.tagName == "checkbox") {
            if (field.checked) value = "1";
            else value = "0";
        } else {
            value = field.value;
        }
        tbSync.db.setAccountSetting(tbSyncAccountSettings.account, setting, value);

        if (setting == "accountname") {
            Services.obs.notifyObservers(null, "tbsync.updateAccountName", tbSyncAccountSettings.account + ":" + field.value);
        }
        tbSync.db.saveAccounts(); //write modified accounts to disk
    },

    toggleEnableState: function () {
        //ignore request, if button is disabled or a sync is ongoing
        if (tbSync.isSyncing(tbSyncAccountSettings.account)) return;
        Services.obs.notifyObservers(null, "tbsync.toggleEnableState", tbSyncAccountSettings.account);
    },

    toggleFolder: function () {
        let folderList = document.getElementById("tbsync.accountsettings.folderlist");
        if (folderList.selectedItem !== null && !folderList.disabled) {
            let fID =  folderList.selectedItem.value;
            let folder = tbSync.db.getFolder(tbSyncAccountSettings.account, fID, true);

            if (!tbSync.isEnabled(folder.account))
                return;

            if (folder.selected == "1") {
                if (window.confirm(tbSync.getLocalizedMessage("prompt.Unsubscribe"))) {
                    //deselect folder
                    folder.selected = "0";
                    //remove folder, which will trigger the listener in tbsync which will clean up everything
                    tbSync.removeTarget(folder.target, tbSync[tbSyncAccountSettings.provider].getThunderbirdFolderType(folder.type));
                }
            } else {
                //select and update status
                tbSync.db.setFolderSetting(tbSyncAccountSettings.account, fID, "selected", "1");
                tbSync.db.setFolderSetting(tbSyncAccountSettings.account, fID, "status", "aborted");
                tbSync.db.setAccountSetting(folder.account, "status", "notsyncronized");
            }
            Services.obs.notifyObservers(null, "tbsync.updateSyncstate", tbSyncAccountSettings.account);
        }
    },

    onFolderListContextMenuShowing: function () {
        let folderList = document.getElementById("tbsync.accountsettings.folderlist");
        let hideContextMenuToggleSubscription = true;
        let aFolderIsSelected = (!folderList.disabled && folderList.selectedItem !== null && folderList.selectedItem.value !== undefined);

        if (aFolderIsSelected) {
            let fID =  folderList.selectedItem.value;
            let folder = tbSync.db.getFolder(tbSyncAccountSettings.account, fID, true);

            //if any folder is selected,  show ContextMenuToggleSubscription
            hideContextMenuToggleSubscription = false;
            if (folder.selected == "1") {
                document.getElementById("tbsync.accountsettings.FolderListContextMenuToggleSubscription").label = tbSync.getLocalizedMessage("subscribe.off::" + folder.name, tbSyncAccountSettings.provider);
            } else {
                document.getElementById("tbsync.accountsettings.FolderListContextMenuToggleSubscription").label = tbSync.getLocalizedMessage("subscribe.on::" + folder.name, tbSyncAccountSettings.provider);
            }

            tbSync[tbSyncAccountSettings.provider].folderList.onContextMenuShowing(document, folder);
        } else {
            tbSync[tbSyncAccountSettings.provider].folderList.onContextMenuShowing(document, null);
        }

        document.getElementById("tbsync.accountsettings.FolderListContextMenuToggleSubscription").hidden = hideContextMenuToggleSubscription;
    }

};
<commit_msg>remove target and set red color upon error<commit_after>/*
 * This file is part of TbSync.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

 "use strict";

Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("chrome://tbsync/content/tbsync.jsm");

var tbSyncAccountSettings = {

    account: null,
    servertype: null,
    provider: null,
    settings: null,
    updateTimer: Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer),

    stripHost: function (document, account) {
        let host = document.getElementById('tbsync.accountsettings.pref.host').value;
        if (host.indexOf("https://") == 0) {
            host = host.replace("https://","");
            document.getElementById('tbsync.accountsettings.pref.https').checked = true;
            tbSync.db.setAccountSetting(account, "https", "1");
        } else if (host.indexOf("http://") == 0) {
            host = host.replace("http://","");
            document.getElementById('tbsync.accountsettings.pref.https').checked = false;
            tbSync.db.setAccountSetting(account, "https", "0");
        }

        while (host.endsWith("/")) { host = host.slice(0,-1); }
        document.getElementById('tbsync.accountsettings.pref.host').value = host
        tbSync.db.setAccountSetting(account, "host", host);
    },

    updateFolderListObserver: {
        observe: function (aSubject, aTopic, aData) {
            //only run if is request for this account and main frame is visible
            let account = aData;
            if (account == tbSyncAccountSettings.account && !document.getElementById('tbsync.accountsettings.frame').hidden) {
                //make sure, folderlist is visible, otherwise our updates will be discarded (may cause errors)
                tbSyncAccountSettings.updateGui();
                tbSyncAccountSettings.updateFolderList();
            }
        }
    },

    updateGuiObserver: {
        observe: function (aSubject, aTopic, aData) {
            //only run if is request for this account and main frame is visible
            let account = aData;
            if (account == tbSyncAccountSettings.account && !document.getElementById('tbsync.accountsettings.frame').hidden) {
                tbSyncAccountSettings.updateGui();
            }
        }
    },

    updateSyncstateObserver: {
        observe: function (aSubject, aTopic, aData) {
            //only run if is request for this account and main frame is visible
            let account = aData;
            if (account == tbSyncAccountSettings.account && !document.getElementById('tbsync.accountsettings.frame').hidden) {

                let syncstate = tbSync.getSyncData(account,"syncstate");
                if (syncstate == "accountdone") {
                        let status = tbSync.db.getAccountSetting(account, "status");
                        switch (status) {
                            case "401":
                                //only popup one password prompt window
                                if (tbSync.passWindowObj === null) {
                                    tbSync.passWindowObj = window.openDialog("chrome://tbsync/content/manager/password.xul", "passwordprompt", "centerscreen,chrome,resizable=no", tbSync.db.getAccount(account), function() {tbSync.syncAccount("sync", account);});
                                }
                                break;
                        }
                    tbSyncAccountSettings.updateGui();
                } else {
                    tbSyncAccountSettings.updateSyncstate();
                }
            }
        }
    },

    onload: function () {
        //load observers
        Services.obs.addObserver(tbSyncAccountSettings.updateFolderListObserver, "tbsync.updateFolderList", false);
        Services.obs.addObserver(tbSyncAccountSettings.updateGuiObserver, "tbsync.updateAccountSettingsGui", false);
        Services.obs.addObserver(tbSyncAccountSettings.updateSyncstateObserver, "tbsync.updateSyncstate", false);
        //get the selected account from the loaded URI
        tbSyncAccountSettings.account = window.location.toString().split("id=")[1];

        //get information for that acount
        tbSyncAccountSettings.provider = tbSync.db.getAccountSetting(tbSyncAccountSettings.account, "provider");
        tbSyncAccountSettings.settings = Object.keys(tbSync[tbSyncAccountSettings.provider].getDefaultAccountEntries()).sort();

        //load overlays from the provider (if any)
        tbSync.overlayManager.injectAllOverlays(window, "chrome://tbsync/content/manager/editAccount.xul?provider=" + tbSyncAccountSettings.provider);

        tbSync.prepareSyncDataObj(tbSyncAccountSettings.account);
        tbSyncAccountSettings.loadSettings();

        //done, folderlist must be updated while visible
        document.getElementById('tbsync.accountsettings.frame').hidden = false;
        tbSyncAccountSettings.updateFolderList();
    },

    onunload: function () {
        tbSyncAccountSettings.updateTimer.cancel();
        if (!document.getElementById('tbsync.accountsettings.frame').hidden) {
            Services.obs.removeObserver(tbSyncAccountSettings.updateFolderListObserver, "tbsync.updateFolderList");
            Services.obs.removeObserver(tbSyncAccountSettings.updateGuiObserver, "tbsync.updateAccountSettingsGui");
            Services.obs.removeObserver(tbSyncAccountSettings.updateSyncstateObserver, "tbsync.updateSyncstate");
        }
    },

   folderListVisible: function () {
        let box = document.getElementById('tbsync.accountsettings.folderlist').getBoundingClientRect();
        let visible = box.width && box.height;
        return visible;
    },

    /**
     * Run through all defined TbSync settings and if there is a corresponding
     * field in the settings dialog, fill it with the stored value.
     */
    loadSettings: function () {
        tbSyncAccountSettings.servertype = tbSync.db.getAccountSetting(tbSyncAccountSettings.account, "servertype");

        for (let i=0; i < tbSyncAccountSettings.settings.length; i++) {
            let pref = document.getElementById("tbsync.accountsettings.pref." + tbSyncAccountSettings.settings[i]);
            let label = document.getElementById("tbsync.accountsettings.label." + tbSyncAccountSettings.settings[i]);

            if (pref) {
                //is this a checkbox?
                if (pref.tagName == "checkbox") {
                    //BOOL
                    if (tbSync.db.getAccountSetting(tbSyncAccountSettings.account, tbSyncAccountSettings.settings[i])  == "1") pref.setAttribute("checked", true);
                    else pref.setAttribute("checked", false);
                } else {
                    //Not BOOL
                    pref.setAttribute("value", tbSync.db.getAccountSetting(tbSyncAccountSettings.account, tbSyncAccountSettings.settings[i]));
                }

                pref.onblur = function() {tbSyncAccountSettings.instantSaveSetting(this)};
            }
        }

        // special treatment for configuration label, which is a permanent setting and will not change by switching modes (only by unlocking, which will handle that)
        let configlabel = document.getElementById("tbsync.accountsettings.label.config");
        if (configlabel) {
            configlabel.setAttribute("value", tbSync.getLocalizedMessage("config." + tbSyncAccountSettings.servertype, tbSyncAccountSettings.provider));
        }

        tbSyncAccountSettings.updateGui();
    },

    updateGui: function () {
        let status = tbSync.db.getAccountSetting(tbSyncAccountSettings.account, "status");

        let isConnected = tbSync.isConnected(tbSyncAccountSettings.account);
        let isEnabled = tbSync.isEnabled(tbSyncAccountSettings.account);
        let isSyncing = tbSync.isSyncing(tbSyncAccountSettings.account);

        { //disable settings if connected or syncing
            let items = document.getElementsByClassName("lockIfConnected");
            for (let i=0; i < items.length; i++) {
                if (isConnected || isSyncing) items[i].setAttribute("disabled", true);
                else items[i].removeAttribute("disabled");
                items[i].style["color"] = isConnected || isSyncing ? "darkgrey" : "black";
            }
        }

        document.getElementById('tbsync.accountsettings.connectbtn.container').hidden = !(isEnabled && !isConnected && !isSyncing);
        //currently we use a fixed button which is hidden during sync
        //document.getElementById('tbsync.accountsettings.connectbtn').label = tbSync.getLocalizedMessage("manager." + (isSyncing ? "connecting" : "tryagain"));

        { //show elements if connected
            let items = document.getElementsByClassName("showIfConnected");
            for (let i=0; i < items.length; i++) {
                items[i].hidden = !isConnected;
            }
        }

        { //show elements if enabled
            let items = document.getElementsByClassName("showIfEnabled");
            for (let i=0; i < items.length; i++) {
                items[i].hidden = !isEnabled;
            }
        }

        document.getElementById('tbsync.accountsettings.enabled').checked = isEnabled;
        document.getElementById('tbsync.accountsettings.folderlist').disabled = isSyncing;
        document.getElementById('tbsync.accountsettings.syncbtn').disabled = isSyncing;
        document.getElementById('tbsync.accountsettings.connectbtn').disabled = isSyncing;

        tbSyncAccountSettings.updateSyncstate();

        //change color of syncstate according to status
        switch (status) {
            case "OK":
            case "disabled":
            case "notsyncronized":
            case "nolightning":
            case "syncing":
                document.getElementById('syncstate').removeAttribute("style");
            break;

            default:
                document.getElementById('syncstate').setAttribute("style","color: red");
        }

    },

    updateSyncstate: function () {
        tbSyncAccountSettings.updateTimer.cancel();
        document.getElementById('syncstate_link').textContent = "";
        document.getElementById('syncstate_link').setAttribute("dest", "");

        // if this account is beeing synced, display syncstate, otherwise print status
        let status = tbSync.db.getAccountSetting(tbSyncAccountSettings.account, "status");
        let isSyncing = tbSync.isSyncing(tbSyncAccountSettings.account);
        let isConnected = tbSync.isConnected(tbSyncAccountSettings.account);
        let isEnabled = tbSync.isEnabled(tbSyncAccountSettings.account);
        let syncdata = tbSync.getSyncData(tbSyncAccountSettings.account);

        if (isSyncing) {
            let accounts = tbSync.db.getAccounts().data;
//            let target = "";

//            if (accounts.hasOwnProperty(syncdata.account) && syncdata.folderID !== "" && syncdata.syncstate != "done") { //if "Done" do not print folder info syncstate
//                target = " [" + tbSync.db.getFolderSetting(syncdata.account, syncdata.folderID, "name") + "]";
//            }

            let parts = syncdata.syncstate.split("||");
            let syncstate = parts[0];
            let synctime = (parts.length>1 ? parts[1] : Date.now());

            let diff = Date.now() - synctime;
            let msg = tbSync.getLocalizedMessage("syncstate." + syncstate, tbSyncAccountSettings.provider);
            if (diff > 2000) msg = msg + " (" + Math.round((tbSync.prefSettings.getIntPref("timeout") - diff)/1000) + "s)";

            document.getElementById('syncstate').textContent = msg;// + target;

            if (syncstate.split(".")[0] == "send") {
                //re-schedule update, if this is a waiting syncstate
                tbSyncAccountSettings.updateTimer.init(tbSyncAccountSettings.updateSyncstate, 1000, 0);
            }
        } else {
            let localized = tbSync.getLocalizedMessage("status." + status, tbSyncAccountSettings.provider);
            if (!isEnabled) localized = tbSync.getLocalizedMessage("status." + "disabled", tbSyncAccountSettings.provider);

            //check, if this localized string contains a link
            let parts = localized.split("||");
            document.getElementById('syncstate').textContent = parts[0];

            if (parts.length==3) {
                    document.getElementById('syncstate_link').setAttribute("dest", parts[1]);
                    document.getElementById('syncstate_link').textContent = parts[2];
            }
        }

        //update syncstates of folders in folderlist, if visible
        if (tbSyncAccountSettings.folderListVisible()) {
            let folderList = document.getElementById("tbsync.accountsettings.folderlist");
            for (let i=0; i < folderList.getRowCount(); i++) {
                let item = folderList.getItemAtIndex(i);
                let folder = tbSync.db.getFolder(tbSyncAccountSettings.account, item.value);
                if (folder) {

                    let rowData = tbSync[tbSyncAccountSettings.provider].folderList.getRowData(folder, syncdata);
                    tbSync[tbSyncAccountSettings.provider].folderList.updateRow(document, item, rowData);

                }
            }
        }
    },

    updateFolderList: function () {
        //do not upate, if not visible (may cause errors)
        if (!tbSyncAccountSettings.folderListVisible())
            return;

        //clear folderlist
        let folderList = document.getElementById("tbsync.accountsettings.folderlist");
        for (let i=folderList.getRowCount()-1; i>=0; i--) {
            folderList.removeItemAt(i);
        }

        //rebuild folderlist
        let folderData = tbSync[tbSyncAccountSettings.provider].folderList.getSortedData(tbSyncAccountSettings.account);
        for (let i=0; i < folderData.length; i++) {
            //add new entry
            let newListItem = document.createElement("richlistitem");
            newListItem.setAttribute("value", folderData[i].folderID);

            tbSync[tbSyncAccountSettings.provider].folderList.addRow(document, newListItem, folderData[i]);

            //ensureElementIsVisible also forces internal update of rowCount, which sometimes is not updated automatically upon appendChild
            folderList.ensureElementIsVisible(folderList.appendChild(newListItem));
        }
    },

    instantSaveSetting: function (field) {
        let setting = field.id.replace("tbsync.accountsettings.pref.","");
        let value = "";

        if (field.tagName == "checkbox") {
            if (field.checked) value = "1";
            else value = "0";
        } else {
            value = field.value;
        }
        tbSync.db.setAccountSetting(tbSyncAccountSettings.account, setting, value);

        if (setting == "accountname") {
            Services.obs.notifyObservers(null, "tbsync.updateAccountName", tbSyncAccountSettings.account + ":" + field.value);
        }
        tbSync.db.saveAccounts(); //write modified accounts to disk
    },

    toggleEnableState: function () {
        //ignore request, if button is disabled or a sync is ongoing
        if (tbSync.isSyncing(tbSyncAccountSettings.account)) return;
        Services.obs.notifyObservers(null, "tbsync.toggleEnableState", tbSyncAccountSettings.account);
    },

    toggleFolder: function () {
        let folderList = document.getElementById("tbsync.accountsettings.folderlist");
        if (folderList.selectedItem !== null && !folderList.disabled) {
            let fID =  folderList.selectedItem.value;
            let folder = tbSync.db.getFolder(tbSyncAccountSettings.account, fID, true);

            if (!tbSync.isEnabled(folder.account))
                return;

            if (folder.selected == "1") {
                if (window.confirm(tbSync.getLocalizedMessage("prompt.Unsubscribe"))) {
                    //deselect folder
                    folder.selected = "0";
                    //remove folder, which will trigger the listener in tbsync which will clean up everything
                    tbSync.removeTarget(folder.target, tbSync[tbSyncAccountSettings.provider].getThunderbirdFolderType(folder.type));
                }
            } else {
                //select and update status
                tbSync.db.setFolderSetting(tbSyncAccountSettings.account, fID, "selected", "1");
                tbSync.db.setFolderSetting(tbSyncAccountSettings.account, fID, "status", "aborted");
                tbSync.db.setAccountSetting(folder.account, "status", "notsyncronized");
            }
            Services.obs.notifyObservers(null, "tbsync.updateSyncstate", tbSyncAccountSettings.account);
        }
    },

    onFolderListContextMenuShowing: function () {
        let folderList = document.getElementById("tbsync.accountsettings.folderlist");
        let hideContextMenuToggleSubscription = true;
        let aFolderIsSelected = (!folderList.disabled && folderList.selectedItem !== null && folderList.selectedItem.value !== undefined);

        if (aFolderIsSelected) {
            let fID =  folderList.selectedItem.value;
            let folder = tbSync.db.getFolder(tbSyncAccountSettings.account, fID, true);

            //if any folder is selected,  show ContextMenuToggleSubscription
            hideContextMenuToggleSubscription = false;
            if (folder.selected == "1") {
                document.getElementById("tbsync.accountsettings.FolderListContextMenuToggleSubscription").label = tbSync.getLocalizedMessage("subscribe.off::" + folder.name, tbSyncAccountSettings.provider);
            } else {
                document.getElementById("tbsync.accountsettings.FolderListContextMenuToggleSubscription").label = tbSync.getLocalizedMessage("subscribe.on::" + folder.name, tbSyncAccountSettings.provider);
            }

            tbSync[tbSyncAccountSettings.provider].folderList.onContextMenuShowing(document, folder);
        } else {
            tbSync[tbSyncAccountSettings.provider].folderList.onContextMenuShowing(document, null);
        }

        document.getElementById("tbsync.accountsettings.FolderListContextMenuToggleSubscription").hidden = hideContextMenuToggleSubscription;
    }

};
<|endoftext|>


<commit_before>/*
 * Copyright 2015-2016 OpenCB
 *
 * 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.
 */

import {LitElement, html} from "lit";
import UtilsNew from "../../../core/utilsNew.js";
import "../../commons/opencga-active-filters.js";
import "../../commons/forms/select-field-filter.js";
import "../../loading-spinner.js";
import "../../commons/tool-header.js";
import "./rga-gene-view.js";
import "./rga-filter.js";
import "./rga-individual-view.js";
import "./rga-variant-view.js";

export default class RgaBrowser extends LitElement {

    constructor() {
        super();

        // Set status and init private properties
        this._init();
    }

    createRenderRoot() {
        return this;
    }

    static get properties() {
        return {
            opencgaSession: {
                type: Object
            },
            cellbaseClient: {
                type: Object
            },
            query: {
                type: Object
            },
            config: {
                type: Object
            }
        };
    }

    _init() {
        this._prefix = "rga" + UtilsNew.randomString(6);
        this.query = {};
        this.preparedQuery = {};
        this.executedQuery = {};
        this.activeFilterAlias = {
        };
        this.activeTab = {"gene-tab": true};
        this.detail = {};
        this.resource = "RGA";

        // LoF as defaultQuery
        this.defaultQuery = {
            /* consequenceType: "transcript_ablation,splice_acceptor_variant,splice_donor_variant,stop_gained,frameshift_variant,stop_lost,start_lost,transcript_amplification,inframe_insertion,inframe_deletion",
            geneName: ["GRIK5", "ACTN3", "COMT", "TTN", "ABCA12", "ALMS1", "ALOX12B", "ATP8A2", "BLM",
                "CCNO", "CEP290", "CNGB3", "CUL7", "DNAAF1", "DOCK6", "EIF2B5", "ERCC6", "FLG", "HADA",
                "INPP5K", "MANIB1", "MERTK", "MUTYH", "NDUFAF5", "NDUFS7", "OTOG", "PAH", "PDZD7", "PHYH",
                "PKHD1", "PMM2", "RARS2", "SACS", "SGCA", "SIGMAR1", "SPG7", "TTN", "TYR", "USH2A", "WFS1"].join(","),
            // individualId: "118000206" // 118000208 is not a proband, 118000206 is a proband
            individualId: "210010274", // no case
            // knockoutType: "COMP_HET"*/
        };
    }

    connectedCallback() {
        super.connectedCallback();
        this._config = {...this.getDefaultConfig(), ...this.config};
    }

    firstUpdated(_changedProperties) {
        $(".bootstrap-select", this).selectpicker();
        UtilsNew.initTooltip(this);
    }

    update(changedProperties) {
        if (changedProperties.has("opencgaSession")) {
            this.opencgaSessionObserver();
        }
        if (changedProperties.has("query")) {
            this.queryObserver();
        }
        super.update(changedProperties);
    }

    opencgaSessionObserver() {
        if (this?.opencgaSession?.study?.fqn) {
            this.query = {study: this.opencgaSession.study.fqn, ...this.defaultQuery};
            this.preparedQuery = {study: this.opencgaSession.study.fqn, ...this.defaultQuery};
            // this.requestUpdate().then(() => $(".bootstrap-select", this).selectpicker());
        }
    }

    queryObserver() {
        if (this?.opencgaSession?.study?.fqn) {
            // NOTE UtilsNew.objectCompare avoid repeating remote requests.
            if (this.forceQuery || !UtilsNew.objectCompare(this.query, this._query)) {
                this._query = this.query;
                if (this.query) {
                    this.preparedQuery = {study: this.opencgaSession.study.fqn, ...this.query};
                    this.executedQuery = {study: this.opencgaSession.study.fqn, ...this.query};
                } else {
                    this.preparedQuery = {study: this.opencgaSession.study.fqn, ...this.defaultQuery};
                    this.executedQuery = {study: this.opencgaSession.study.fqn, ...this.defaultQuery};
                }
                // onServerFilterChange() in opencga-active-filters fires an activeFilterChange event when the Filter dropdown is used
                this.dispatchEvent(new CustomEvent("queryChange", {
                    detail: this.preparedQuery
                }
                ));
                this.detail = {};
            } else {
                // console.error("same queries")
            }
            this.forceQuery = false;
            // this.requestUpdate();
        }
    }

    notifySearch(query) {
        this.dispatchEvent(new CustomEvent("querySearch", {
            detail: {
                query: query
            },
            bubbles: true,
            composed: true
        }));
    }

    async onRun() {
        // NOTE notifySearch() triggers this chain: notifySearch -> onQueryFilterSearch() on iva-app.js -> this.queries updated -> queryObserver() here in rga-browser
        // queryObserver() here stops the repetition of the remote request by checking if it has changed
        this.query = {...this.preparedQuery};
        // forceQuery is meant to force a new search on click on Search, even if the query hasn't changed
        this.forceQuery = true;
        // updates this.queries in iva-app
        this.notifySearch(this.preparedQuery);
    }

    onClickPill(e) {
        this._changeView(e.currentTarget.dataset.tabId);
    }

    _changeView(tabId) {
        $(".content-pills", this).removeClass("active");
        $(".content-tab", this).removeClass("active");
        for (const tab in this.activeTab) {
            if (Object.prototype.hasOwnProperty.call(this.activeTab, tab)) {
                this.activeTab[tab] = false;
            }
        }
        $(`button.content-pills[data-id=${tabId}]`, this).addClass("active");
        $("#" + tabId, this).addClass("active");
        this.activeTab[tabId] = true;
        this.requestUpdate();
    }

    onFilterChange(e) {
        this.query = e.detail;
    }

    onQueryFilterChange(e) {
        this.preparedQuery = e.detail.query;
        this.requestUpdate();
    }

    onActiveFilterChange(e) {
        this.preparedQuery = {study: this.opencgaSession.study.fqn, ...e.detail};
        this.query = {study: this.opencgaSession.study.fqn, ...e.detail};
    }

    onActiveFilterClear() {
        console.log("onActiveFilterClear");
        this.query = {study: this.opencgaSession.study.fqn};
        this.preparedQuery = {...this.query};
    }

    getDefaultConfig() {
        // return BrowserConf.config;
        return {
            title: "Recessive Variant Browser",
            icon: "fas fa-dna",
            active: false,
            searchButtonText: "Search",
            consequenceTypes: [
                "frameshift_variant",
                "incomplete_terminal_codon_variant",
                "inframe_deletion",
                "inframe_insertion",
                "missense_variant",
                "start_lost",
                "stop_gained",
                "stop_lost",
                "splice_acceptor_variant",
                "splice_donor_variant",
                "feature_truncation",
                "transcript_ablation"
            ],
            views: [
                {
                    id: "gene-tab",
                    name: "Genes",
                    icon: "fa fa-table",
                    active: true
                },
                {
                    id: "individual-tab",
                    name: "Individuals",
                    icon: "fas fa-table"
                },
                {
                    id: "variant-tab",
                    name: "Variants",
                    icon: "fas fa-table"
                }
            ],
            filter: {
                title: "Filter",
                activeFilters: {
                    alias: {
                        // Example:
                        // "region": "Region",
                        // "gene": "Gene",
                        "ct": "Consequence Types"
                    },
                    complexFields: [],
                    hiddenFields: []
                },
                sections: [ // sections and subsections, structure and order is respected
                    {
                        title: "Gene",
                        collapsed: false,
                        fields: [
                            {
                                id: "geneName",
                                name: "Gene",
                                description: "Gene selection"
                            },
                            {
                                id: "knockoutType",
                                name: "Knockout Type",
                                allowedValues: [{id: "COMP_HET", name: "Compound Heterozygous"}, {id: "HOM_ALT", name: "Homozygous"}]
                            }
                        ]
                    },
                    {
                        title: "Individual",
                        collapsed: false,
                        fields: [
                            {
                                id: "individualId",
                                name: "Individual",
                                description: "Individual selection"
                            }
                        ]
                    },
                    {
                        title: "Confidence",
                        fields: [
                            {
                                id: "numParents",
                                name: "Include families with",
                                description: "Confidence selection",
                                type: "checkbox",
                                allowedValues: [{id: 0, name: "No parents"}, {id: 1, name: "One parent"}, {id: 2, name: "Two parents"}]
                            }
                            /* {
                                id: "probandOnly",
                                name: "Affected individuals (proband) only",
                                type: "boolean",
                                defaultValue: "no",
                                tooltip: "other info here"
                                // allowedValues: ["father", "mother"]
                            }*/
                        ]
                    },
                    {
                        title: "Variants",
                        fields: [
                            /* {
                                id: "cohort",
                                name: "Cohort",
                                description: "Cohort selection"
                            },*/
                            {
                                id: "variants",
                                name: "Variant Id",
                                placeholder: "13:32906644:-:T,13:32912753:AC:-"
                            },
                            /* {
                                id: "region",
                                name: "Genomic Location",
                                tooltip: tooltips.region
                            },*/
                            {
                                id: "populationFrequency",
                                name: "Select Population Frequency",
                                onlyPopFreqAll: true
                            },
                            {
                                id: "type",
                                name: "Variant types",
                                types: ["SNV", "INDEL", "INSERTION", "DELETION"],
                                tooltip: tooltips.type
                                // layout: "horizontal"
                            },
                            {
                                id: "consequenceType",
                                name: "Consequence type",
                                tooltip: tooltips.consequenceTypeSelect,
                                value: CONSEQUENCE_TYPES.lof
                            },
                            {
                                id: "clinicalSignificance",
                                name: "Clinical Significance",
                            }
                        ]
                    }
                ],
                examples: [
                    {
                        id: "BRCA2 missense variants",
                        active: false,
                        query: {
                            geneName: "BRCA2",
                            ct: "missense_variant"
                        }
                    }
                ],
                result: {
                    grid: {}
                },
                detail: {
                    title: "Selected Variant",
                    views: [
                        {
                            id: "annotationSummary",
                            title: "Summary",
                            active: true
                        }
                    ]
                }
            }
        };
    }

    render() {

        if (!this?.opencgaSession?.study?.fqn) {
            return html`

No public projects available to browse. Please login to continue.

`;
        }

        if (this.opencgaSession.study?.attributes?.RGA !== "INDEXED") {
            return html`

Study ${this?.opencgaSession?.study.name} is not enabled to Recessive Variant Analysis.

`;
        }

        return html`

            <tool-header title="${this._config.title}" icon="${this._config.icon}"></tool-header>

                        <button type="button" class="btn btn-primary ripple" @click="${this.onRun}">
                             ${this._config.searchButtonText || "Run"}
                        </button>

                            Filters

                            <rga-filter
                                    .opencgaSession="${this.opencgaSession}"
                                    .cellbaseClient="${this.cellbaseClient}"
                                    .config="${this._config.filter}"
                                    .query="${this.query}"
                                    .searchButton="${false}"
                                    @queryChange="${this.onQueryFilterChange}">
                            </rga-filter>

                            ${this._config.views && this._config.views.length ? this._config.views.map(tab => html`
                                <button type="button" class="btn btn-success ripple content-pills ${this.activeTab[tab.id] ? "active" : ""}" ?disabled=${tab.disabled} @click="${this.onClickPill}" data-tab-id="${tab.id}">
                                     ${tab.name}
                                </button>
                            `) : html`No view has been configured`}

                        <opencga-active-filters .resource="${this.resource}"
                                                .opencgaSession="${this.opencgaSession}"
                                                .defaultStudy="${this.opencgaSession?.study?.fqn}"
                                                .query="${this.preparedQuery}"
                                                .refresh="${this.executedQuery}"
                                                .alias="${this.activeFilterAlias}"
                                                .config="${this._config.activeFilters}"
                                                .filters="${this._config.filter.examples}"
                                                @activeFilterChange="${this.onActiveFilterChange}"
                                                @activeFilterClear="${this.onActiveFilterClear}">
                        </opencga-active-filters>

                                <rga-gene-view .query=${this.executedQuery} .opencgaSession="${this.opencgaSession}" .active="${this.activeTab["gene-tab"]}"></rga-gene-view>

                                <rga-individual-view .query=${this.executedQuery} .config=${this._config} .opencgaSession="${this.opencgaSession}" .active="${this.activeTab["individual-tab"]}"></rga-individual-view>

                                <rga-variant-view .query=${this.executedQuery}
                                                  .config=${this._config}
                                                  .opencgaSession="${this.opencgaSession}"
                                                  .cellbaseClient=${this.cellbaseClient}
                                                  .active="${this.activeTab["variant-tab"]}">
                                </rga-variant-view>

        `;
    }

}

customElements.define("rga-browser", RgaBrowser);
<commit_msg>rga-browser attribute path changed and active-filter bar warning message fixed #118<commit_after>/*
 * Copyright 2015-2016 OpenCB
 *
 * 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.
 */

import {LitElement, html} from "lit";
import UtilsNew from "../../../core/utilsNew.js";
import "../../commons/opencga-active-filters.js";
import "../../commons/forms/select-field-filter.js";
import "../../loading-spinner.js";
import "../../commons/tool-header.js";
import "./rga-gene-view.js";
import "./rga-filter.js";
import "./rga-individual-view.js";
import "./rga-variant-view.js";

export default class RgaBrowser extends LitElement {

    constructor() {
        super();

        // Set status and init private properties
        this._init();
    }

    createRenderRoot() {
        return this;
    }

    static get properties() {
        return {
            opencgaSession: {
                type: Object
            },
            cellbaseClient: {
                type: Object
            },
            query: {
                type: Object
            },
            config: {
                type: Object
            }
        };
    }

    _init() {
        this._prefix = "rga" + UtilsNew.randomString(6);
        this.query = {};
        this.preparedQuery = {};
        this.executedQuery = {};
        this.activeFilterAlias = {
        };
        this.activeTab = {"gene-tab": true};
        this.detail = {};
        this.resource = "RGA";

        // LoF as defaultQuery
        this.defaultQuery = {
            /* consequenceType: "transcript_ablation,splice_acceptor_variant,splice_donor_variant,stop_gained,frameshift_variant,stop_lost,start_lost,transcript_amplification,inframe_insertion,inframe_deletion",
            geneName: ["GRIK5", "ACTN3", "COMT", "TTN", "ABCA12", "ALMS1", "ALOX12B", "ATP8A2", "BLM",
                "CCNO", "CEP290", "CNGB3", "CUL7", "DNAAF1", "DOCK6", "EIF2B5", "ERCC6", "FLG", "HADA",
                "INPP5K", "MANIB1", "MERTK", "MUTYH", "NDUFAF5", "NDUFS7", "OTOG", "PAH", "PDZD7", "PHYH",
                "PKHD1", "PMM2", "RARS2", "SACS", "SGCA", "SIGMAR1", "SPG7", "TTN", "TYR", "USH2A", "WFS1"].join(","),
            // individualId: "118000206" // 118000208 is not a proband, 118000206 is a proband
            individualId: "210010274", // no case
            // knockoutType: "COMP_HET"*/
        };
    }

    connectedCallback() {
        super.connectedCallback();
        this._config = {...this.getDefaultConfig(), ...this.config};
    }

    firstUpdated(_changedProperties) {
        $(".bootstrap-select", this).selectpicker();
        UtilsNew.initTooltip(this);
    }

    update(changedProperties) {
        if (changedProperties.has("opencgaSession")) {
            this.opencgaSessionObserver();
        }
        if (changedProperties.has("query")) {
            this.queryObserver();
        }
        super.update(changedProperties);
    }

    opencgaSessionObserver() {
        if (this?.opencgaSession?.study?.fqn) {
            this.query = {study: this.opencgaSession.study.fqn, ...this.defaultQuery};
            this.preparedQuery = {study: this.opencgaSession.study.fqn, ...this.defaultQuery};
            // this.requestUpdate().then(() => $(".bootstrap-select", this).selectpicker());
        }
    }

    queryObserver() {
        if (this?.opencgaSession?.study?.fqn) {
            // NOTE UtilsNew.objectCompare avoid repeating remote requests.
            if (this.forceQuery || !UtilsNew.objectCompare(this.query, this._query)) {
                this._query = this.query;
                if (this.query) {
                    this.preparedQuery = {study: this.opencgaSession.study.fqn, ...this.query};
                    this.executedQuery = {study: this.opencgaSession.study.fqn, ...this.query};
                } else {
                    this.preparedQuery = {study: this.opencgaSession.study.fqn, ...this.defaultQuery};
                    this.executedQuery = {study: this.opencgaSession.study.fqn, ...this.defaultQuery};
                }
                // onServerFilterChange() in opencga-active-filters fires an activeFilterChange event when the Filter dropdown is used
                this.dispatchEvent(new CustomEvent("queryChange", {
                    detail: this.preparedQuery
                }
                ));
                this.detail = {};
            } else {
                // console.error("same queries")
            }
            this.forceQuery = false;
            // this.requestUpdate();
        }
    }

    notifySearch(query) {
        this.dispatchEvent(new CustomEvent("querySearch", {
            detail: {
                query: query
            },
            bubbles: true,
            composed: true
        }));
    }

    async onRun() {
        // NOTE notifySearch() triggers this chain: notifySearch -> onQueryFilterSearch() on iva-app.js -> this.queries updated -> queryObserver() here in rga-browser
        // queryObserver() here stops the repetition of the remote request by checking if it has changed
        this.query = {...this.preparedQuery};
        // forceQuery is meant to force a new search on click on Search, even if the query hasn't changed
        this.forceQuery = true;
        // updates this.queries in iva-app
        this.notifySearch(this.preparedQuery);
    }

    onClickPill(e) {
        this._changeView(e.currentTarget.dataset.tabId);
    }

    _changeView(tabId) {
        $(".content-pills", this).removeClass("active");
        $(".content-tab", this).removeClass("active");
        for (const tab in this.activeTab) {
            if (Object.prototype.hasOwnProperty.call(this.activeTab, tab)) {
                this.activeTab[tab] = false;
            }
        }
        $(`button.content-pills[data-id=${tabId}]`, this).addClass("active");
        $("#" + tabId, this).addClass("active");
        this.activeTab[tabId] = true;
        this.requestUpdate();
    }

    onFilterChange(e) {
        this.query = e.detail;
    }

    onQueryFilterChange(e) {
        this.preparedQuery = e.detail.query;
        this.requestUpdate();
    }

    onActiveFilterChange(e) {
        this.preparedQuery = {study: this.opencgaSession.study.fqn, ...e.detail};
        this.query = {study: this.opencgaSession.study.fqn, ...e.detail};
    }

    onActiveFilterClear() {
        console.log("onActiveFilterClear");
        this.query = {study: this.opencgaSession.study.fqn};
        this.preparedQuery = {...this.query};
    }

    getDefaultConfig() {
        // return BrowserConf.config;
        return {
            title: "Recessive Variant Browser",
            icon: "fas fa-dna",
            active: false,
            searchButtonText: "Search",
            consequenceTypes: [
                "frameshift_variant",
                "incomplete_terminal_codon_variant",
                "inframe_deletion",
                "inframe_insertion",
                "missense_variant",
                "start_lost",
                "stop_gained",
                "stop_lost",
                "splice_acceptor_variant",
                "splice_donor_variant",
                "feature_truncation",
                "transcript_ablation"
            ],
            views: [
                {
                    id: "gene-tab",
                    name: "Genes",
                    icon: "fa fa-table",
                    active: true
                },
                {
                    id: "individual-tab",
                    name: "Individuals",
                    icon: "fas fa-table"
                },
                {
                    id: "variant-tab",
                    name: "Variants",
                    icon: "fas fa-table"
                }
            ],
            filter: {
                title: "Filter",
                activeFilters: {
                    alias: {
                        // Example:
                        // "region": "Region",
                        // "gene": "Gene",
                        "ct": "Consequence Types"
                    },
                    complexFields: [],
                    hiddenFields: []
                },
                sections: [ // sections and subsections, structure and order is respected
                    {
                        title: "Gene",
                        collapsed: false,
                        fields: [
                            {
                                id: "geneName",
                                name: "Gene",
                                description: "Gene selection"
                            },
                            {
                                id: "knockoutType",
                                name: "Knockout Type",
                                allowedValues: [{id: "COMP_HET", name: "Compound Heterozygous"}, {id: "HOM_ALT", name: "Homozygous"}]
                            }
                        ]
                    },
                    {
                        title: "Individual",
                        collapsed: false,
                        fields: [
                            {
                                id: "individualId",
                                name: "Individual",
                                description: "Individual selection"
                            }
                        ]
                    },
                    {
                        title: "Confidence",
                        fields: [
                            {
                                id: "numParents",
                                name: "Include families with",
                                description: "Confidence selection",
                                type: "checkbox",
                                allowedValues: [{id: 0, name: "No parents"}, {id: 1, name: "One parent"}, {id: 2, name: "Two parents"}]
                            }
                            /* {
                                id: "probandOnly",
                                name: "Affected individuals (proband) only",
                                type: "boolean",
                                defaultValue: "no",
                                tooltip: "other info here"
                                // allowedValues: ["father", "mother"]
                            }*/
                        ]
                    },
                    {
                        title: "Variants",
                        fields: [
                            /* {
                                id: "cohort",
                                name: "Cohort",
                                description: "Cohort selection"
                            },*/
                            {
                                id: "variants",
                                name: "Variant Id",
                                placeholder: "13:32906644:-:T,13:32912753:AC:-"
                            },
                            /* {
                                id: "region",
                                name: "Genomic Location",
                                tooltip: tooltips.region
                            },*/
                            {
                                id: "populationFrequency",
                                name: "Select Population Frequency",
                                onlyPopFreqAll: true
                            },
                            {
                                id: "type",
                                name: "Variant types",
                                types: ["SNV", "INDEL", "INSERTION", "DELETION"],
                                tooltip: tooltips.type
                                // layout: "horizontal"
                            },
                            {
                                id: "consequenceType",
                                name: "Consequence type",
                                tooltip: tooltips.consequenceTypeSelect,
                                value: CONSEQUENCE_TYPES.lof
                            },
                            {
                                id: "clinicalSignificance",
                                name: "Clinical Significance",
                            }
                        ]
                    }
                ],
                examples: [
                    {
                        id: "BRCA2 missense variants",
                        active: false,
                        query: {
                            geneName: "BRCA2",
                            ct: "missense_variant"
                        }
                    }
                ],
                result: {
                    grid: {}
                },
                detail: {
                    title: "Selected Variant",
                    views: [
                        {
                            id: "annotationSummary",
                            title: "Summary",
                            active: true
                        }
                    ]
                }
            }
        };
    }

    render() {

        if (!this?.opencgaSession?.study?.fqn) {
            return html`

No public projects available to browse. Please login to continue.

`;
        }

        if (this.opencgaSession.study?.attributes?.RGA?.status !== "INDEXED") {
            return html`

Study ${this?.opencgaSession?.study.name} is not enabled to Recessive Variant Analysis.

`;
        }

        return html`

            <tool-header title="${this._config.title}" icon="${this._config.icon}"></tool-header>

                        <button type="button" class="btn btn-primary ripple" @click="${this.onRun}">
                             ${this._config.searchButtonText || "Run"}
                        </button>

                            Filters

                            <rga-filter
                                    .opencgaSession="${this.opencgaSession}"
                                    .cellbaseClient="${this.cellbaseClient}"
                                    .config="${this._config.filter}"
                                    .query="${this.query}"
                                    .searchButton="${false}"
                                    @queryChange="${this.onQueryFilterChange}">
                            </rga-filter>

                            ${this._config.views && this._config.views.length ? this._config.views.map(tab => html`
                                <button type="button" class="btn btn-success ripple content-pills ${this.activeTab[tab.id] ? "active" : ""}" ?disabled=${tab.disabled} @click="${this.onClickPill}" data-tab-id="${tab.id}">
                                     ${tab.name}
                                </button>
                            `) : html`No view has been configured`}

                        <opencga-active-filters .resource="${this.resource}"
                                                .opencgaSession="${this.opencgaSession}"
                                                .defaultStudy="${this.opencgaSession?.study?.fqn}"
                                                .query="${this.preparedQuery}"
                                                .executedQuery="${this.executedQuery}"
                                                .alias="${this.activeFilterAlias}"
                                                .config="${this._config.activeFilters}"
                                                .filters="${this._config.filter.examples}"
                                                @activeFilterChange="${this.onActiveFilterChange}"
                                                @activeFilterClear="${this.onActiveFilterClear}">
                        </opencga-active-filters>

                                <rga-gene-view .query=${this.executedQuery} .opencgaSession="${this.opencgaSession}" .active="${this.activeTab["gene-tab"]}"></rga-gene-view>

                                <rga-individual-view .query=${this.executedQuery} .config=${this._config} .opencgaSession="${this.opencgaSession}" .active="${this.activeTab["individual-tab"]}"></rga-individual-view>

                                <rga-variant-view .query=${this.executedQuery}
                                                  .config=${this._config}
                                                  .opencgaSession="${this.opencgaSession}"
                                                  .cellbaseClient=${this.cellbaseClient}
                                                  .active="${this.activeTab["variant-tab"]}">
                                </rga-variant-view>

        `;
    }

}

customElements.define("rga-browser", RgaBrowser);
<|endoftext|>


<commit_before>/*
Copyright 2017 New Vector Ltd
Copyright 2019 Travis Ralston

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.
*/

/*
* See - https://docs.google.com/document/d/1uPF7XWY_dXTKVKV7jZQ2KmsI19wn9-kFRgQ1tFQP7wQ/edit?usp=sharing for
* spec. details / documentation.
*/

import FromWidgetPostMessageApi from './FromWidgetPostMessageApi';
import ToWidgetPostMessageApi from './ToWidgetPostMessageApi';
import Modal from "./Modal";
import {MatrixClientPeg} from "./MatrixClientPeg";
import SettingsStore from "./settings/SettingsStore";
import WidgetOpenIDPermissionsDialog from "./components/views/dialogs/WidgetOpenIDPermissionsDialog";
import WidgetUtils from "./utils/WidgetUtils";
import {KnownWidgetActions} from "./widgets/WidgetApi";

if (!global.mxFromWidgetMessaging) {
    global.mxFromWidgetMessaging = new FromWidgetPostMessageApi();
    global.mxFromWidgetMessaging.start();
}
if (!global.mxToWidgetMessaging) {
    global.mxToWidgetMessaging = new ToWidgetPostMessageApi();
    global.mxToWidgetMessaging.start();
}

const OUTBOUND_API_NAME = 'toWidget';

export default class WidgetMessaging {
    /**
     * @param {string} widgetId The widget's ID
     * @param {string} wurl The raw URL of the widget as in the event (the 'wURL')
     * @param {string} renderedUrl The url used in the widget's iframe (either similar to the wURL
     *     or a different URL of the clients choosing if it is using its own impl).
     * @param {bool} isUserWidget If true, the widget is a user widget, otherwise it's a room widget
     * @param {object} target Where widget messages should be sent (eg. the iframe object)
     */
    constructor(widgetId, wurl, renderedUrl, isUserWidget, target) {
        this.widgetId = widgetId;
        this.wurl = wurl;
        this.renderedUrl = renderedUrl;
        this.isUserWidget = isUserWidget;
        this.target = target;
        this.fromWidget = global.mxFromWidgetMessaging;
        this.toWidget = global.mxToWidgetMessaging;
        this._onOpenIdRequest = this._onOpenIdRequest.bind(this);
        this.start();
    }

    messageToWidget(action) {
        action.widgetId = this.widgetId; // Required to be sent for all outbound requests

        return this.toWidget.exec(action, this.target).then((data) => {
            // Check for errors and reject if found
            if (data.response === undefined) { // null is valid
                throw new Error("Missing 'response' field");
            }
            if (data.response && data.response.error) {
                const err = data.response.error;
                const msg = String(err.message ? err.message : "An error was returned");
                if (err._error) {
                    console.error(err._error);
                }
                // Potential XSS attack if 'msg' is not appropriately sanitized,
                // as it is untrusted input by our parent window (which we assume is Element).
                // We can't aggressively sanitize [A-z0-9] since it might be a translation.
                throw new Error(msg);
            }
            // Return the response field for the request
            return data.response;
        });
    }

    /**
     * Tells the widget that the client is ready to handle further widget requests.
     * @returns {Promise<*>} Resolves after the widget has acknowledged the ready message.
     */
    flagReadyToContinue() {
        return this.messageToWidget({
            api: OUTBOUND_API_NAME,
            action: KnownWidgetActions.ClientReady,
        });
    }

    /**
     * Tells the widget that it should terminate now.
     * @returns {Promise<*>} Resolves when widget has acknowledged the message.
     */
    terminate() {
        return this.messageToWidget({
            api: OUTBOUND_API_NAME,
            action: KnownWidgetActions.Terminate,
        });
    }

    /**
     * Request a screenshot from a widget
     * @return {Promise} To be resolved with screenshot data when it has been generated
     */
    getScreenshot() {
        console.log('Requesting screenshot for', this.widgetId);
        return this.messageToWidget({
                api: OUTBOUND_API_NAME,
                action: "screenshot",
            })
            .catch((error) => new Error("Failed to get screenshot: " + error.message))
            .then((response) => response.screenshot);
    }

    /**
     * Request capabilities required by the widget
     * @return {Promise} To be resolved with an array of requested widget capabilities
     */
    getCapabilities() {
        console.log('Requesting capabilities for', this.widgetId);
        return this.messageToWidget({
                api: OUTBOUND_API_NAME,
                action: "capabilities",
            }).then((response) => {
                console.log('Got capabilities for', this.widgetId, response.capabilities);
                return response.capabilities;
            });
    }

    sendVisibility(visible) {
        return this.messageToWidget({
            api: OUTBOUND_API_NAME,
            action: "visibility",
            visible,
        })
        .catch((error) => {
            console.error("Failed to send visibility: ", error);
        });
    }

    start() {
        this.fromWidget.addEndpoint(this.widgetId, this.renderedUrl);
        this.fromWidget.addListener("get_openid", this._onOpenIdRequest);
    }

    stop() {
        this.fromWidget.removeEndpoint(this.widgetId, this.renderedUrl);
        this.fromWidget.removeListener("get_openid", this._onOpenIdRequest);
    }

    async _onOpenIdRequest(ev, rawEv) {
        if (ev.widgetId !== this.widgetId) return; // not interesting

        const widgetSecurityKey = WidgetUtils.getWidgetSecurityKey(this.widgetId, this.wurl, this.isUserWidget);

        const settings = SettingsStore.getValue("widgetOpenIDPermissions");
        if (settings.deny && settings.deny.includes(widgetSecurityKey)) {
            this.fromWidget.sendResponse(rawEv, {state: "blocked"});
            return;
        }
        if (settings.allow && settings.allow.includes(widgetSecurityKey)) {
            const responseBody = {state: "allowed"};
            const credentials = await MatrixClientPeg.get().getOpenIdToken();
            Object.assign(responseBody, credentials);
            this.fromWidget.sendResponse(rawEv, responseBody);
            return;
        }

        // Confirm that we received the request
        this.fromWidget.sendResponse(rawEv, {state: "request"});

        // Actually ask for permission to send the user's data
        Modal.createTrackedDialog("OpenID widget permissions", '',
            WidgetOpenIDPermissionsDialog, {
                widgetUrl: this.wurl,
                widgetId: this.widgetId,
                isUserWidget: this.isUserWidget,

                onFinished: async (confirm) => {
                    const responseBody = {
                        // Legacy (early draft) fields
                        success: confirm,

                        // New style MSC1961 fields
                        state: confirm ? "allowed" : "blocked",
                        original_request_id: ev.requestId, // eslint-disable-line camelcase
                    };
                    if (confirm) {
                        const credentials = await MatrixClientPeg.get().getOpenIdToken();
                        Object.assign(responseBody, credentials);
                    }
                    this.messageToWidget({
                        api: OUTBOUND_API_NAME,
                        action: "openid_credentials",
                        data: responseBody,
                    }).catch((error) => {
                        console.error("Failed to send OpenID credentials: ", error);
                    });
                },
            },
        );
    }
}
<commit_msg>MSC numbers are hard to remember<commit_after>/*
Copyright 2017 New Vector Ltd
Copyright 2019 Travis Ralston

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.
*/

/*
* See - https://docs.google.com/document/d/1uPF7XWY_dXTKVKV7jZQ2KmsI19wn9-kFRgQ1tFQP7wQ/edit?usp=sharing for
* spec. details / documentation.
*/

import FromWidgetPostMessageApi from './FromWidgetPostMessageApi';
import ToWidgetPostMessageApi from './ToWidgetPostMessageApi';
import Modal from "./Modal";
import {MatrixClientPeg} from "./MatrixClientPeg";
import SettingsStore from "./settings/SettingsStore";
import WidgetOpenIDPermissionsDialog from "./components/views/dialogs/WidgetOpenIDPermissionsDialog";
import WidgetUtils from "./utils/WidgetUtils";
import {KnownWidgetActions} from "./widgets/WidgetApi";

if (!global.mxFromWidgetMessaging) {
    global.mxFromWidgetMessaging = new FromWidgetPostMessageApi();
    global.mxFromWidgetMessaging.start();
}
if (!global.mxToWidgetMessaging) {
    global.mxToWidgetMessaging = new ToWidgetPostMessageApi();
    global.mxToWidgetMessaging.start();
}

const OUTBOUND_API_NAME = 'toWidget';

export default class WidgetMessaging {
    /**
     * @param {string} widgetId The widget's ID
     * @param {string} wurl The raw URL of the widget as in the event (the 'wURL')
     * @param {string} renderedUrl The url used in the widget's iframe (either similar to the wURL
     *     or a different URL of the clients choosing if it is using its own impl).
     * @param {bool} isUserWidget If true, the widget is a user widget, otherwise it's a room widget
     * @param {object} target Where widget messages should be sent (eg. the iframe object)
     */
    constructor(widgetId, wurl, renderedUrl, isUserWidget, target) {
        this.widgetId = widgetId;
        this.wurl = wurl;
        this.renderedUrl = renderedUrl;
        this.isUserWidget = isUserWidget;
        this.target = target;
        this.fromWidget = global.mxFromWidgetMessaging;
        this.toWidget = global.mxToWidgetMessaging;
        this._onOpenIdRequest = this._onOpenIdRequest.bind(this);
        this.start();
    }

    messageToWidget(action) {
        action.widgetId = this.widgetId; // Required to be sent for all outbound requests

        return this.toWidget.exec(action, this.target).then((data) => {
            // Check for errors and reject if found
            if (data.response === undefined) { // null is valid
                throw new Error("Missing 'response' field");
            }
            if (data.response && data.response.error) {
                const err = data.response.error;
                const msg = String(err.message ? err.message : "An error was returned");
                if (err._error) {
                    console.error(err._error);
                }
                // Potential XSS attack if 'msg' is not appropriately sanitized,
                // as it is untrusted input by our parent window (which we assume is Element).
                // We can't aggressively sanitize [A-z0-9] since it might be a translation.
                throw new Error(msg);
            }
            // Return the response field for the request
            return data.response;
        });
    }

    /**
     * Tells the widget that the client is ready to handle further widget requests.
     * @returns {Promise<*>} Resolves after the widget has acknowledged the ready message.
     */
    flagReadyToContinue() {
        return this.messageToWidget({
            api: OUTBOUND_API_NAME,
            action: KnownWidgetActions.ClientReady,
        });
    }

    /**
     * Tells the widget that it should terminate now.
     * @returns {Promise<*>} Resolves when widget has acknowledged the message.
     */
    terminate() {
        return this.messageToWidget({
            api: OUTBOUND_API_NAME,
            action: KnownWidgetActions.Terminate,
        });
    }

    /**
     * Request a screenshot from a widget
     * @return {Promise} To be resolved with screenshot data when it has been generated
     */
    getScreenshot() {
        console.log('Requesting screenshot for', this.widgetId);
        return this.messageToWidget({
                api: OUTBOUND_API_NAME,
                action: "screenshot",
            })
            .catch((error) => new Error("Failed to get screenshot: " + error.message))
            .then((response) => response.screenshot);
    }

    /**
     * Request capabilities required by the widget
     * @return {Promise} To be resolved with an array of requested widget capabilities
     */
    getCapabilities() {
        console.log('Requesting capabilities for', this.widgetId);
        return this.messageToWidget({
                api: OUTBOUND_API_NAME,
                action: "capabilities",
            }).then((response) => {
                console.log('Got capabilities for', this.widgetId, response.capabilities);
                return response.capabilities;
            });
    }

    sendVisibility(visible) {
        return this.messageToWidget({
            api: OUTBOUND_API_NAME,
            action: "visibility",
            visible,
        })
        .catch((error) => {
            console.error("Failed to send visibility: ", error);
        });
    }

    start() {
        this.fromWidget.addEndpoint(this.widgetId, this.renderedUrl);
        this.fromWidget.addListener("get_openid", this._onOpenIdRequest);
    }

    stop() {
        this.fromWidget.removeEndpoint(this.widgetId, this.renderedUrl);
        this.fromWidget.removeListener("get_openid", this._onOpenIdRequest);
    }

    async _onOpenIdRequest(ev, rawEv) {
        if (ev.widgetId !== this.widgetId) return; // not interesting

        const widgetSecurityKey = WidgetUtils.getWidgetSecurityKey(this.widgetId, this.wurl, this.isUserWidget);

        const settings = SettingsStore.getValue("widgetOpenIDPermissions");
        if (settings.deny && settings.deny.includes(widgetSecurityKey)) {
            this.fromWidget.sendResponse(rawEv, {state: "blocked"});
            return;
        }
        if (settings.allow && settings.allow.includes(widgetSecurityKey)) {
            const responseBody = {state: "allowed"};
            const credentials = await MatrixClientPeg.get().getOpenIdToken();
            Object.assign(responseBody, credentials);
            this.fromWidget.sendResponse(rawEv, responseBody);
            return;
        }

        // Confirm that we received the request
        this.fromWidget.sendResponse(rawEv, {state: "request"});

        // Actually ask for permission to send the user's data
        Modal.createTrackedDialog("OpenID widget permissions", '',
            WidgetOpenIDPermissionsDialog, {
                widgetUrl: this.wurl,
                widgetId: this.widgetId,
                isUserWidget: this.isUserWidget,

                onFinished: async (confirm) => {
                    const responseBody = {
                        // Legacy (early draft) fields
                        success: confirm,

                        // New style MSC1960 fields
                        state: confirm ? "allowed" : "blocked",
                        original_request_id: ev.requestId, // eslint-disable-line camelcase
                    };
                    if (confirm) {
                        const credentials = await MatrixClientPeg.get().getOpenIdToken();
                        Object.assign(responseBody, credentials);
                    }
                    this.messageToWidget({
                        api: OUTBOUND_API_NAME,
                        action: "openid_credentials",
                        data: responseBody,
                    }).catch((error) => {
                        console.error("Failed to send OpenID credentials: ", error);
                    });
                },
            },
        );
    }
}
<|endoftext|>


<commit_before>import '../src'
import bufferEqual from 'buffer-equal'
import gulp from 'gulp'
import rump from 'rump'
import timeout from 'timeout-then'
import {exec} from 'child-process-promise'
import {colors} from 'gulp-util'
import {readFile, writeFile} from 'mz/fs'
import {sep} from 'path'
import {spy} from 'sinon'

const {stripColor} = colors

describe('tasks', function() {
  this.timeout(0)

  beforeEach(() => {
    rump.configure({paths: {
      source: {root: 'test/fixtures', static: ''},
      destination: {root: 'tmp'},
    }})
  })

  it('are added and defined', () => {
    const callback = spy()
    rump.on('gulp:main', callback)
    rump.on('gulp:static', callback)
    rump.addGulpTasks({prefix: 'spec'})
    callback.should.be.calledTwice()
    gulp.tasks['spec:info:static'].should.be.ok()
    gulp.tasks['spec:build:static'].should.be.ok()
    gulp.tasks['spec:watch:static'].should.be.ok()
  })

  it('display correct information in info task', () => {
    const logs = [],
          {log} = console
    console.log = (...args) => logs.push(stripColor(args.join(' ')))
    gulp.start('spec:info')
    console.log = log
    logs.slice(-6).should.eql([
      '',
      '--- Static v0.7.0',
      `Static files from test${sep}fixtures are copied to tmp`,
      'Affected files:',
      'index.html',
      '',
    ])
  })

  describe('for building', () => {
    let original

    before(async() => {
      original = await readFile('test/fixtures/index.html')
      await startWatching()
      await timeout(1000)
      await exec('sync')
    })

    after(async() => await writeFile('test/fixtures/index.html', original))

    it('handles updates', async() => {
      let content = await readFile('tmp/index.html')
      bufferEqual(content, original).should.be.true()
      await writeFile('test/fixtures/index.html', '
New
')
      await timeout(1000)
      await exec('sync')
      content = await readFile('tmp/index.html')
      bufferEqual(content, original).should.be.false()
    })
  })
})

function startWatching() {
  return new Promise(resolve => {
    gulp.task('postbuild', ['spec:watch'], resolve)
    gulp.start('postbuild')
  })
}
<commit_msg>Run sync twice<commit_after>import '../src'
import bufferEqual from 'buffer-equal'
import gulp from 'gulp'
import rump from 'rump'
import timeout from 'timeout-then'
import {exec} from 'child-process-promise'
import {colors} from 'gulp-util'
import {readFile, writeFile} from 'mz/fs'
import {sep} from 'path'
import {spy} from 'sinon'

const {stripColor} = colors

describe('tasks', function() {
  this.timeout(0)

  beforeEach(() => {
    rump.configure({paths: {
      source: {root: 'test/fixtures', static: ''},
      destination: {root: 'tmp'},
    }})
  })

  it('are added and defined', () => {
    const callback = spy()
    rump.on('gulp:main', callback)
    rump.on('gulp:static', callback)
    rump.addGulpTasks({prefix: 'spec'})
    callback.should.be.calledTwice()
    gulp.tasks['spec:info:static'].should.be.ok()
    gulp.tasks['spec:build:static'].should.be.ok()
    gulp.tasks['spec:watch:static'].should.be.ok()
  })

  it('display correct information in info task', () => {
    const logs = [],
          {log} = console
    console.log = (...args) => logs.push(stripColor(args.join(' ')))
    gulp.start('spec:info')
    console.log = log
    logs.slice(-6).should.eql([
      '',
      '--- Static v0.7.0',
      `Static files from test${sep}fixtures are copied to tmp`,
      'Affected files:',
      'index.html',
      '',
    ])
  })

  describe('for building', () => {
    let original

    before(async() => {
      original = await readFile('test/fixtures/index.html')
      await startWatching()
      await sync()
    })

    after(async() => await writeFile('test/fixtures/index.html', original))

    it('handles updates', async() => {
      let content = await readFile('tmp/index.html')
      bufferEqual(content, original).should.be.true()
      await writeFile('test/fixtures/index.html', '
New
')
      await sync()
      content = await readFile('tmp/index.html')
      bufferEqual(content, original).should.be.false()
    })
  })
})

async function sync() {
  await exec('sync')
  await timeout(1000)
  await exec('sync')
}

function startWatching() {
  return new Promise(resolve => {
    gulp.task('postbuild', ['spec:watch'], resolve)
    gulp.start('postbuild')
  })
}
<|endoftext|>