From e4b6b6cb7fa481342339e491f4fa251d0f49d9c3 Mon Sep 17 00:00:00 2001 From: baku Date: Fri, 20 Jan 2017 10:29:50 +0100 Subject: [PATCH] Plus panel --- data/filters.svg | 9 ++ data/{chrome.css => usercontext.css} | 122 ++++++++++++++-- index.js | 204 ++++++++++++++++++++++----- 3 files changed, 293 insertions(+), 42 deletions(-) create mode 100644 data/filters.svg rename data/{chrome.css => usercontext.css} (61%) diff --git a/data/filters.svg b/data/filters.svg new file mode 100644 index 0000000..6119d59 --- /dev/null +++ b/data/filters.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/data/chrome.css b/data/usercontext.css similarity index 61% rename from data/chrome.css rename to data/usercontext.css index 9785ce9..929f66f 100644 --- a/data/chrome.css +++ b/data/usercontext.css @@ -1,13 +1,3 @@ -/* containers experiment */ -.tabbrowser-tab[usercontextid] { - background-image: linear-gradient(to right, var(--identity-tab-color) 0%, var(--identity-tab-color) 100%) !important; - background-size: var(--identity-stroke-background-size) !important; - background-repeat: no-repeat !important; - background-position: 0 29px !important; - --identity-stroke-background-size: auto 2px; -} - -/* set defaults for ids as currently we don't set identity color */ [usercontextid="1"], [data-identity-color="blue"] { --identity-tab-color: #37adff; @@ -52,6 +42,73 @@ --identity-icon-color: #af51f5; } +[data-identity-icon="fingerprint"] { + /*--identity-icon: url("chrome://browser/content/usercontext.svg#fingerprint"); */ + --identity-icon: url("resource://testpilot-containers/data/usercontext.svg#fingerprint"); +} + +[data-identity-icon="briefcase"] { + /* --identity-icon: url("chrome://browser/content/usercontext.svg#briefcase"); */ + --identity-icon: url("resource://testpilot-containers/data/usercontext.svg#briefcase"); +} + +[data-identity-icon="dollar"] { + /* --identity-icon: url("chrome://browser/content/usercontext.svg#dollar"); */ + --identity-icon: url("resource://testpilot-containers/data/usercontext.svg#dollar"); +} + +[data-identity-icon="cart"] { + /* --identity-icon: url("chrome://browser/content/usercontext.svg#cart"); */ + --identity-icon: url("resource://testpilot-containers/data/usercontext.svg#cart"); +} + +[data-identity-icon="circle"] { + /* --identity-icon: url("chrome://browser/content/usercontext.svg#circle"); */ + --identity-icon: url("resource://testpilot-containers/data/usercontext.svg#circle"); +} + +#userContext-indicator { + height: 16px; + width: 16px; +} + +#userContext-label { + margin-inline-end: 3px; + color: var(--identity-tab-color); +} + +#userContext-icons { + -moz-box-align: center; +} + +.tabbrowser-tab[usercontextid] { + background-image: linear-gradient(to right, transparent 20%, var(--identity-tab-color) 30%, var(--identity-tab-color) 70%, transparent 80%); + background-size: auto 2px; + background-repeat: no-repeat; +} + +.userContext-icon, +.menuitem-iconic[data-usercontextid] > .menu-iconic-left > .menu-iconic-icon, +.subviewbutton[usercontextid] > .toolbarbutton-icon, +#userContext-indicator { + background-image: var(--identity-icon); + filter: url(/img/filters.svg#fill); + filter: url(resource://testpilot-containers/data/filters.svg#fill); + fill: var(--identity-icon-color); + background-size: contain; + background-repeat: no-repeat; + background-position: center center; +} + +/* containers experiment */ +.tabbrowser-tab[usercontextid] { + background-image: linear-gradient(to right, var(--identity-tab-color) 0%, var(--identity-tab-color) 100%) !important; + background-size: var(--identity-stroke-background-size) !important; + background-repeat: no-repeat !important; + background-position: 0 29px !important; + --identity-stroke-background-size: auto 2px; +} + .tabbrowser-tab[usercontextid] .tab-background-start:not([selected="true"]) { background-image: linear-gradient(to left, var(--identity-tab-color) 0%, var(--identity-tab-color) 50%, transparent 50%, transparent 100%); background-position: 0 28px; @@ -114,3 +171,48 @@ background-repeat: repeat-x; } /* end containers experiment */ + +.tabs-newtab-button .toolbarbutton-icon[type="menu"] { + margin-inline-end: 0; +} + +.tabs-newtab-button .toolbarbutton-menu-dropmarker { + display: none; +} + +#new-tab-overlay { + visibility: visible; + block-size: 200px; + inline-size: auto; + display: block; + background: transparent; + position: absolute; + -moz-appearance: none; + offset-block-start: 29px; +} +#new-tab-overlay[hidden=true] { + display: none; +} + +#new-tab-overlay menuitem { + background: white; + margin-block-end: 12px; + border-radius: 20px; + -moz-appearance: none; + color: #4b4b4b; + padding: 6px; + font-size: 1.2rem; + box-shadow: 3px 7px 7px #0006; + --icon-size: 26px; + /* Limited width to 8chars roughly */ + inline-size: calc(calc(8*0.68em) + var(--icon-size) + 3px); +} + +#new-tab-overlay .menuitem-iconic[data-usercontextid] > .menu-iconic-left > .menu-iconic-icon { + block-height: var(--icon-size); + block-width: var(--icon-size); +} + +.menuitem-iconic[data-usercontextid] > .menu-iconic-left { + visibility: visible; +} diff --git a/index.js b/index.js index 37c8889..6dd87d4 100644 --- a/index.js +++ b/index.js @@ -4,6 +4,9 @@ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; +const HIDE_MENU_TIMEOUT = 1000; +const IDENTITY_COLORS = ["blue", "turquoise", "green", "yellow", "orange", "red", "pink", "purple"]; + const { attachTo } = require("sdk/content/mod"); const { ContextualIdentityService } = require("resource://gre/modules/ContextualIdentityService.jsm"); const { getFavicon } = require("sdk/places/favicon"); @@ -16,10 +19,12 @@ const webExtension = require("sdk/webextension"); const windows = require("sdk/windows"); const windowUtils = require("sdk/window/utils"); -const IDENTITY_COLORS = ["blue", "turquoise", "green", "yellow", "orange", "red", "pink", "purple"]; +// ---------------------------------------------------------------------------- +// ContainerService const ContainerService = { _identitiesState: {}, + _windowMap: {}, init() { // Enabling preferences @@ -76,6 +81,7 @@ const ContainerService = { if (userContextId) { ++this._identitiesState[userContextId].openTabs; } + this._hideAllPanels(); }); tabs.on("close", tab => { @@ -83,6 +89,11 @@ const ContainerService = { if (userContextId && this._identitiesState[userContextId].openTabs) { --this._identitiesState[userContextId].openTabs; } + this._hideAllPanels(); + }); + + tabs.on("activate", () => { + this._hideAllPanels(); }); // Modify CSS and other stuff for each window. @@ -95,6 +106,10 @@ const ContainerService = { this.configureWindow(viewFor(window)); }); + windows.browserWindows.on("close", window => { + this.closeWindow(viewFor(window)); + }); + // WebExtension startup webExtension.startup().then(api => { @@ -464,41 +479,166 @@ const ContainerService = { // Styling the window configureWindow(window) { - const tabsElement = window.document.getElementById("tabbrowser-tabs"); - const button = window.document.getAnonymousElementByAttribute(tabsElement, "anonid", "tabs-newtab-button"); - - while (button.firstChild) { - button.removeChild(button.firstChild); + const id = windowUtils.getInnerId(window); + if (!(id in this._windowMap)) { + this._windowMap[id] = new ContainerWindow(window); } - button.setAttribute("type", "menu"); - const popup = window.document.createElementNS(XUL_NS, "menupopup"); + this._windowMap[id].configure(); + }, - popup.setAttribute("anonid", "newtab-popup"); - popup.className = "new-tab-popup"; - popup.setAttribute("position", "after_end"); + closeWindow(window) { + const id = windowUtils.getInnerId(window); + delete this._windowMap[id]; + }, - ContextualIdentityService.getIdentities().forEach(identity => { - identity = this._convert(identity); - - const menuItem = window.document.createElementNS(XUL_NS, "menuitem"); - menuItem.setAttribute("class", "menuitem-iconic"); - menuItem.setAttribute("label", identity.name); - menuItem.setAttribute("image", self.data.url("usercontext.svg") + "#" + identity.image); - - menuItem.addEventListener("command", (event) => { - this.openTab({userContextId: identity.userContextId}); - event.stopPropagation(); - }); - - popup.appendChild(menuItem); - }); - - button.appendChild(popup); - const style = Style({ uri: self.data.url("chrome.css") }); - - attachTo(style, viewFor(window)); - } + _hideAllPanels() { + for (let id in this._windowMap) { // eslint-disable-line prefer-const + this._windowMap[id].hidePanel(); + } + }, }; +// ---------------------------------------------------------------------------- +// ContainerWindow + +// This object is used to configure a single window. +function ContainerWindow(window) { + this._init(window); +} + +ContainerWindow.prototype = { + _window: null, + _panelElement: null, + _timeoutId: 0, + + _init(window) { + this._window = window; + const style = Style({ uri: self.data.url("usercontext.css") }); + attachTo(style, this._window); + }, + + configure() { + const tabsElement = this._window.document.getElementById("tabbrowser-tabs"); + + const button = this._window.document.getAnonymousElementByAttribute(tabsElement, "anonid", "tabs-newtab-button"); + + // Let's remove the tooltip because it can go over our panel. + button.setAttribute("tooltip", ""); + + // Let's remove all the previous panels. + if (this._panelElement) { + this._panelElement.remove(); + } + + this._panelElement = this._window.document.createElementNS(XUL_NS, "panel"); + this._panelElement.setAttribute("id", "new-tab-overlay"); + button.after(this._panelElement); + this._panelElement.hidden = true; + + this._repositionPopup(); + + ContainerService.queryIdentities().then(identities => { + identities.forEach(identity => { + const menuItemElement = this._window.document.createElementNS(XUL_NS, "menuitem"); + this._panelElement.appendChild(menuItemElement); + menuItemElement.className = "menuitem-iconic"; + menuItemElement.setAttribute("label", identity.name); + menuItemElement.setAttribute("data-usercontextid", identity.userContextId); + menuItemElement.setAttribute("data-identity-icon", identity.image); + menuItemElement.setAttribute("data-identity-color", identity.color); + + menuItemElement.addEventListener("command", e => { + ContainerService.openTab({userContextId: identity.userContextId}); + e.stopPropagation(); + }); + + //Command isn't working probably because I'm in a panel + menuItemElement.addEventListener("click", e => { + ContainerService.openTab({userContextId: identity.userContextId}); + e.stopPropagation(); + }); + + menuItemElement.addEventListener("mouseover", () => { + this._cleanTimeout(); + }); + + menuItemElement.addEventListener("mouseout", () => { + this._createTimeout(); + }); + + this._panelElement.appendChild(menuItemElement); + }); + }).catch(() => { + this.hidePanel(); + }); + + button.addEventListener("click", () => { + this._panelElement.hidden = false; + }); + + button.addEventListener("mouseover", () => { + this._repositionPopup(); + this._panelElement.hidden = false; + }); + + button.addEventListener("mouseout", () => { + this._createTimeout(); + }); + + this._panelElement.addEventListener("mouseout", (e) => { + if (e.target !== this._panelElement) { + this._createTimeout(); + return; + } + this._repositionPopup(); + }); + }, + + // This function puts the popup in the correct place. + _repositionPopup() { + const tabsElement = this._window.document.getElementById("tabbrowser-tabs"); + const button = this._window.document.getAnonymousElementByAttribute(tabsElement, "anonid", "tabs-newtab-button"); + + const size = button.getBoxQuads()[0]; + const innerWindow = tabsElement.getBoxQuads()[0]; + const panelElementWidth = 200; + + // 1/4th of the way past the left hand side of the new tab button + // This seems to line up nicely with the left of the + + const offset = ((size.p3.x - size.p4.x) / 4); + let left = size.p4.x + offset; + if (left + panelElementWidth > innerWindow.p2.x) { + left -= panelElementWidth - offset; + } + this._panelElement.style.left = left + "px"; + this._panelElement.style.top = size.p4.y + "px"; + }, + + // This timer is used to hide the panel auto-magically if it's not used in + // the following X seconds. This is need to avoid the leaking of the panel + // when the mouse goes out of of the 'plus' button. + _createTimeout() { + this._cleanTimeout(); + this._timeoutId = this._window.setTimeout(() => { + this.hidePanel(); + this._timeoutId = 0; + }, HIDE_MENU_TIMEOUT); + }, + + _cleanTimeout() { + if (this._timeoutId) { + this._window.clearTimeout(this._timeoutId); + this._timeoutId = 0; + } + }, + + hidePanel() { + this._cleanTimeout(); + this._panelElement.hidden = true; + }, +}; + +// ---------------------------------------------------------------------------- +// Let's start :) ContainerService.init();