Plus panel

This commit is contained in:
baku 2017-01-20 10:29:50 +01:00
parent fe41394da8
commit e4b6b6cb7f
3 changed files with 293 additions and 42 deletions

9
data/filters.svg Normal file
View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg">
<filter id="fill">
<feComposite in="FillPaint" in2="SourceGraphic" operator="in"/>
</filter>
</svg>

After

Width:  |  Height:  |  Size: 400 B

View file

@ -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;
}

202
index.js
View file

@ -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();