diff options
Diffstat (limited to 'application/basilisk/components/syncedtabs/TabListView.js')
-rw-r--r-- | application/basilisk/components/syncedtabs/TabListView.js | 568 |
1 files changed, 0 insertions, 568 deletions
diff --git a/application/basilisk/components/syncedtabs/TabListView.js b/application/basilisk/components/syncedtabs/TabListView.js deleted file mode 100644 index dab15101b..000000000 --- a/application/basilisk/components/syncedtabs/TabListView.js +++ /dev/null @@ -1,568 +0,0 @@ -/* 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"; - -const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; - -Cu.import("resource://gre/modules/Services.jsm"); - -let { getChromeWindow } = Cu.import("resource:///modules/syncedtabs/util.js", {}); - -let log = Cu.import("resource://gre/modules/Log.jsm", {}) - .Log.repository.getLogger("Sync.RemoteTabs"); - -this.EXPORTED_SYMBOLS = [ - "TabListView" -]; - -function getContextMenu(window) { - return getChromeWindow(window).document.getElementById("SyncedTabsSidebarContext"); -} - -function getTabsFilterContextMenu(window) { - return getChromeWindow(window).document.getElementById("SyncedTabsSidebarTabsFilterContext"); -} - -/* - * TabListView - * - * Given a state, this object will render the corresponding DOM. - * It maintains no state of it's own. It listens for DOM events - * and triggers actions that may cause the state to change and - * ultimately the view to rerender. - */ -function TabListView(window, props) { - this.props = props; - - this._window = window; - this._doc = this._window.document; - - this._tabsContainerTemplate = this._doc.getElementById("tabs-container-template"); - this._clientTemplate = this._doc.getElementById("client-template"); - this._emptyClientTemplate = this._doc.getElementById("empty-client-template"); - this._tabTemplate = this._doc.getElementById("tab-template"); - this.tabsFilter = this._doc.querySelector(".tabsFilter"); - this.clearFilter = this._doc.querySelector(".textbox-search-clear"); - this.searchBox = this._doc.querySelector(".search-box"); - this.searchIcon = this._doc.querySelector(".textbox-search-icon"); - - this.container = this._doc.createElement("div"); - - this._attachFixedListeners(); - - this._setupContextMenu(); -} - -TabListView.prototype = { - render(state) { - // Don't rerender anything; just update attributes, e.g. selection - if (state.canUpdateAll) { - this._update(state); - return; - } - // Rerender the tab list - if (state.canUpdateInput) { - this._updateSearchBox(state); - this._createList(state); - return; - } - // Create the world anew - this._create(state); - }, - - // Create the initial DOM from templates - _create(state) { - let wrapper = this._doc.importNode(this._tabsContainerTemplate.content, true).firstElementChild; - this._clearChilden(); - this.container.appendChild(wrapper); - - this.list = this.container.querySelector(".list"); - - this._createList(state); - this._updateSearchBox(state); - - this._attachListListeners(); - }, - - _createList(state) { - this._clearChilden(this.list); - for (let client of state.clients) { - if (state.filter) { - this._renderFilteredClient(client); - } else { - this._renderClient(client); - } - } - if (this.list.firstChild) { - const firstTab = this.list.firstChild.querySelector(".item.tab:first-child .item-title"); - if (firstTab) { - firstTab.setAttribute("tabindex", 2); - } - } - }, - - destroy() { - this._teardownContextMenu(); - this.container.remove(); - }, - - _update(state) { - this._updateSearchBox(state); - for (let client of state.clients) { - let clientNode = this._doc.getElementById("item-" + client.id); - if (clientNode) { - this._updateClient(client, clientNode); - } - - client.tabs.forEach((tab, index) => { - let tabNode = this._doc.getElementById('tab-' + client.id + '-' + index); - this._updateTab(tab, tabNode, index); - }); - } - }, - - // Client rows are hidden when the list is filtered - _renderFilteredClient(client, filter) { - client.tabs.forEach((tab, index) => { - let node = this._renderTab(client, tab, index); - this.list.appendChild(node); - }); - }, - - _renderClient(client) { - let itemNode = client.tabs.length ? - this._createClient(client) : - this._createEmptyClient(client); - - this._updateClient(client, itemNode); - - let tabsList = itemNode.querySelector(".item-tabs-list"); - client.tabs.forEach((tab, index) => { - let node = this._renderTab(client, tab, index); - tabsList.appendChild(node); - }); - - this.list.appendChild(itemNode); - return itemNode; - }, - - _renderTab(client, tab, index) { - let itemNode = this._createTab(tab); - this._updateTab(tab, itemNode, index); - return itemNode; - }, - - _createClient(item) { - return this._doc.importNode(this._clientTemplate.content, true).firstElementChild; - }, - - _createEmptyClient(item) { - return this._doc.importNode(this._emptyClientTemplate.content, true).firstElementChild; - }, - - _createTab(item) { - return this._doc.importNode(this._tabTemplate.content, true).firstElementChild; - }, - - _clearChilden(node) { - let parent = node || this.container; - while (parent.firstChild) { - parent.removeChild(parent.firstChild); - } - }, - - // These listeners are attached only once, when we initialize the view - _attachFixedListeners() { - this.tabsFilter.addEventListener("input", this.onFilter.bind(this)); - this.tabsFilter.addEventListener("focus", this.onFilterFocus.bind(this)); - this.tabsFilter.addEventListener("blur", this.onFilterBlur.bind(this)); - this.clearFilter.addEventListener("click", this.onClearFilter.bind(this)); - this.searchIcon.addEventListener("click", this.onFilterFocus.bind(this)); - }, - - // These listeners have to be re-created every time since we re-create the list - _attachListListeners() { - this.list.addEventListener("click", this.onClick.bind(this)); - this.list.addEventListener("mouseup", this.onMouseUp.bind(this)); - this.list.addEventListener("keydown", this.onKeyDown.bind(this)); - }, - - _updateSearchBox(state) { - if (state.filter) { - this.searchBox.classList.add("filtered"); - } else { - this.searchBox.classList.remove("filtered"); - } - this.tabsFilter.value = state.filter; - if (state.inputFocused) { - this.searchBox.setAttribute("focused", true); - this.tabsFilter.focus(); - } else { - this.searchBox.removeAttribute("focused"); - } - }, - - /** - * Update the element representing an item, ensuring it's in sync with the - * underlying data. - * @param {client} item - Item to use as a source. - * @param {Element} itemNode - Element to update. - */ - _updateClient(item, itemNode) { - itemNode.setAttribute("id", "item-" + item.id); - let lastSync = new Date(item.lastModified); - let lastSyncTitle = getChromeWindow(this._window).gSyncUI.formatLastSyncDate(lastSync); - itemNode.setAttribute("title", lastSyncTitle); - if (item.closed) { - itemNode.classList.add("closed"); - } else { - itemNode.classList.remove("closed"); - } - if (item.selected) { - itemNode.classList.add("selected"); - } else { - itemNode.classList.remove("selected"); - } - if (item.isMobile) { - itemNode.classList.add("device-image-mobile"); - } else { - itemNode.classList.add("device-image-desktop"); - } - if (item.focused) { - itemNode.focus(); - } - itemNode.dataset.id = item.id; - itemNode.querySelector(".item-title").textContent = item.name; - }, - - /** - * Update the element representing a tab, ensuring it's in sync with the - * underlying data. - * @param {tab} item - Item to use as a source. - * @param {Element} itemNode - Element to update. - */ - _updateTab(item, itemNode, index) { - itemNode.setAttribute("title", `${item.title}\n${item.url}`); - itemNode.setAttribute("id", "tab-" + item.client + '-' + index); - if (item.selected) { - itemNode.classList.add("selected"); - } else { - itemNode.classList.remove("selected"); - } - if (item.focused) { - itemNode.focus(); - } - itemNode.dataset.url = item.url; - - itemNode.querySelector(".item-title").textContent = item.title; - - if (item.icon) { - let icon = itemNode.querySelector(".item-icon-container"); - icon.style.backgroundImage = "url(" + item.icon + ")"; - } - }, - - onMouseUp(event) { - if (event.which == 2) { // Middle click - this.onClick(event); - } - }, - - onClick(event) { - let itemNode = this._findParentItemNode(event.target); - if (!itemNode) { - return; - } - - if (itemNode.classList.contains("tab")) { - let url = itemNode.dataset.url; - if (url) { - this.onOpenSelected(url, event); - } - } - - // Middle click on a client - if (itemNode.classList.contains("client")) { - let where = getChromeWindow(this._window).whereToOpenLink(event); - if (where != "current") { - const tabs = itemNode.querySelector(".item-tabs-list").childNodes; - const urls = [...tabs].map(tab => tab.dataset.url); - this.props.onOpenTabs(urls, where); - } - } - - if (event.target.classList.contains("item-twisty-container") - && event.which != 2) { - this.props.onToggleBranch(itemNode.dataset.id); - return; - } - - let position = this._getSelectionPosition(itemNode); - this.props.onSelectRow(position); - }, - - /** - * Handle a keydown event on the list box. - * @param {Event} event - Triggering event. - */ - onKeyDown(event) { - if (event.keyCode == this._window.KeyEvent.DOM_VK_DOWN) { - event.preventDefault(); - this.props.onMoveSelectionDown(); - } else if (event.keyCode == this._window.KeyEvent.DOM_VK_UP) { - event.preventDefault(); - this.props.onMoveSelectionUp(); - } else if (event.keyCode == this._window.KeyEvent.DOM_VK_RETURN) { - let selectedNode = this.container.querySelector('.item.selected'); - if (selectedNode.dataset.url) { - this.onOpenSelected(selectedNode.dataset.url, event); - } else if (selectedNode) { - this.props.onToggleBranch(selectedNode.dataset.id); - } - } - }, - - onBookmarkTab() { - let item = this._getSelectedTabNode(); - if (item) { - let title = item.querySelector(".item-title").textContent; - this.props.onBookmarkTab(item.dataset.url, title); - } - }, - - onCopyTabLocation() { - let item = this._getSelectedTabNode(); - if (item) { - this.props.onCopyTabLocation(item.dataset.url); - } - }, - - onOpenSelected(url, event) { - let where = getChromeWindow(this._window).whereToOpenLink(event); - this.props.onOpenTab(url, where, {}); - }, - - onOpenSelectedFromContextMenu(event) { - let item = this._getSelectedTabNode(); - if (item) { - let where = event.target.getAttribute("where"); - let params = { - private: event.target.hasAttribute("private"), - }; - this.props.onOpenTab(item.dataset.url, where, params); - } - }, - - onFilter(event) { - let query = event.target.value; - if (query) { - this.props.onFilter(query); - } else { - this.props.onClearFilter(); - } - }, - - onClearFilter() { - this.props.onClearFilter(); - }, - - onFilterFocus() { - this.props.onFilterFocus(); - }, - onFilterBlur() { - this.props.onFilterBlur(); - }, - - _getSelectedTabNode() { - let item = this.container.querySelector('.item.selected'); - if (this._isTab(item) && item.dataset.url) { - return item; - } - return null; - }, - - // Set up the custom context menu - _setupContextMenu() { - Services.els.addSystemEventListener(this._window, "contextmenu", this, false); - for (let getMenu of [getContextMenu, getTabsFilterContextMenu]) { - let menu = getMenu(this._window); - menu.addEventListener("popupshowing", this, true); - menu.addEventListener("command", this, true); - } - }, - - _teardownContextMenu() { - // Tear down context menu - Services.els.removeSystemEventListener(this._window, "contextmenu", this, false); - for (let getMenu of [getContextMenu, getTabsFilterContextMenu]) { - let menu = getMenu(this._window); - menu.removeEventListener("popupshowing", this, true); - menu.removeEventListener("command", this, true); - } - }, - - handleEvent(event) { - switch (event.type) { - case "contextmenu": - this.handleContextMenu(event); - break; - - case "popupshowing": { - if (event.target.getAttribute("id") == "SyncedTabsSidebarTabsFilterContext") { - this.handleTabsFilterContextMenuShown(event); - } - break; - } - - case "command": { - let menu = event.target.closest("menupopup"); - switch (menu.getAttribute("id")) { - case "SyncedTabsSidebarContext": - this.handleContentContextMenuCommand(event); - break; - - case "SyncedTabsSidebarTabsFilterContext": - this.handleTabsFilterContextMenuCommand(event); - break; - } - break; - } - } - }, - - handleTabsFilterContextMenuShown(event) { - let document = event.target.ownerDocument; - let focusedElement = document.commandDispatcher.focusedElement; - if (focusedElement != this.tabsFilter) { - this.tabsFilter.focus(); - } - for (let item of event.target.children) { - if (!item.hasAttribute("cmd")) { - continue; - } - let command = item.getAttribute("cmd"); - let controller = document.commandDispatcher.getControllerForCommand(command); - if (controller.isCommandEnabled(command)) { - item.removeAttribute("disabled"); - } else { - item.setAttribute("disabled", "true"); - } - } - }, - - handleContentContextMenuCommand(event) { - let id = event.target.getAttribute("id"); - switch (id) { - case "syncedTabsOpenSelected": - case "syncedTabsOpenSelectedInTab": - case "syncedTabsOpenSelectedInWindow": - case "syncedTabsOpenSelectedInPrivateWindow": - this.onOpenSelectedFromContextMenu(event); - break; - case "syncedTabsBookmarkSelected": - this.onBookmarkTab(); - break; - case "syncedTabsCopySelected": - this.onCopyTabLocation(); - break; - case "syncedTabsRefresh": - case "syncedTabsRefreshFilter": - this.props.onSyncRefresh(); - break; - } - }, - - handleTabsFilterContextMenuCommand(event) { - let command = event.target.getAttribute("cmd"); - let dispatcher = getChromeWindow(this._window).document.commandDispatcher; - let controller = dispatcher.focusedElement.controllers.getControllerForCommand(command); - controller.doCommand(command); - }, - - handleContextMenu(event) { - let menu; - - if (event.target == this.tabsFilter) { - menu = getTabsFilterContextMenu(this._window); - } else { - let itemNode = this._findParentItemNode(event.target); - if (itemNode) { - let position = this._getSelectionPosition(itemNode); - this.props.onSelectRow(position); - } - menu = getContextMenu(this._window); - this.adjustContextMenu(menu); - } - - menu.openPopupAtScreen(event.screenX, event.screenY, true, event); - }, - - adjustContextMenu(menu) { - let item = this.container.querySelector('.item.selected'); - let showTabOptions = this._isTab(item); - - let el = menu.firstChild; - - while (el) { - if (showTabOptions || el.getAttribute("id") === "syncedTabsRefresh") { - el.hidden = false; - } else { - el.hidden = true; - } - - el = el.nextSibling; - } - }, - - /** - * Find the parent item element, from a given child element. - * @param {Element} node - Child element. - * @return {Element} Element for the item, or null if not found. - */ - _findParentItemNode(node) { - while (node && node !== this.list && node !== this._doc.documentElement && - !node.classList.contains("item")) { - node = node.parentNode; - } - - if (node !== this.list && node !== this._doc.documentElement) { - return node; - } - - return null; - }, - - _findParentBranchNode(node) { - while (node && !node.classList.contains("list") && node !== this._doc.documentElement && - !node.parentNode.classList.contains("list")) { - node = node.parentNode; - } - - if (node !== this.list && node !== this._doc.documentElement) { - return node; - } - - return null; - }, - - _getSelectionPosition(itemNode) { - let parent = this._findParentBranchNode(itemNode); - let parentPosition = this._indexOfNode(parent.parentNode, parent); - let childPosition = -1; - // if the node is not a client, find its position within the parent - if (parent !== itemNode) { - childPosition = this._indexOfNode(itemNode.parentNode, itemNode); - } - return [parentPosition, childPosition]; - }, - - _indexOfNode(parent, child) { - return Array.prototype.indexOf.call(parent.childNodes, child); - }, - - _isTab(item) { - return item && item.classList.contains("tab"); - } -}; |