diff --git a/webextension/css/popup.css b/webextension/css/popup.css index af78306..d9899da 100644 --- a/webextension/css/popup.css +++ b/webextension/css/popup.css @@ -450,7 +450,7 @@ manage things like container crud */ margin-inline-start: var(--inline-item-space-size); } -#container-panel #sort-containers-link { +#container-panel .action-link { align-items: center; block-size: var(--block-url-label-size); border: 1px solid #d8d8d8; @@ -460,11 +460,12 @@ manage things like container crud */ font-size: var(--small-text-size); inline-size: var(--inline-button-size); justify-content: center; + margin-left: var(--block-line-space-size); text-decoration: none; } -#container-panel #sort-containers-link:hover, -#container-panel #sort-containers-link:focus { +#container-panel .action-link:hover, +#container-panel .action-link:focus { background: #f2f2f2; } diff --git a/webextension/js/background/backgroundLogic.js b/webextension/js/background/backgroundLogic.js index 36bcb47..c16dfc1 100644 --- a/webextension/js/background/backgroundLogic.js +++ b/webextension/js/background/backgroundLogic.js @@ -14,6 +14,25 @@ const backgroundLogic = { return extensionInfo; }, + async getContainers(windowId) { + const [identities, state] = await Promise.all([ + browser.contextualIdentities.query({}), + backgroundLogic.queryIdentitiesState(windowId) + ]); + + return identities + .filter(identity => { + return identity.cookieStoreId in state; + }) + .map(identity => { + const stateObject = state[identity.cookieStoreId]; + identity.hasOpenTabs = stateObject.hasOpenTabs; + identity.hasHiddenTabs = stateObject.hasHiddenTabs; + + return identity; + }); + }, + getUserContextIdFromCookieStoreId(cookieStoreId) { if (!cookieStoreId) { return false; @@ -299,8 +318,40 @@ const backgroundLogic = { return await identityState.storageArea.set(options.cookieStoreId, containerState); }, + async unhideAllTabs(windowId) { + const promises = []; + (await backgroundLogic.getContainers(windowId)) + .filter(identity => identity.hasHiddenTabs) + .map(identity => identity.cookieStoreId) + .forEach(cookieStoreId => { + // We need to call unhideContainer in messageHandler to prevent it from + // unhiding multiple times + promises.push(messageHandler.unhideContainer(cookieStoreId)); + }); + return Promise.all(promises); + }, + + async showOnly(options) { + if (!("windowId" in options) || !("cookieStoreId" in options)) { + return Promise.reject("showOnly needs both a windowId and a cookieStoreId"); + } + + const promises = []; + (await backgroundLogic.getContainers(options.windowId)) + .filter(identity => identity.cookieStoreId !== options.cookieStoreId && identity.hasOpenTabs) + .map(identity => identity.cookieStoreId) + .forEach(cookieStoreId => { + promises.push(backgroundLogic.hideTabs({cookieStoreId, windowId: options.windowId})); + }); + + // We need to call unhideContainer in messageHandler to prevent it from + // unhiding multiple times + promises.push(messageHandler.unhideContainer(options.cookieStoreId)); + + return Promise.all(promises); + }, + cookieStoreId(userContextId) { return `firefox-container-${userContextId}`; } }; - diff --git a/webextension/js/background/messageHandler.js b/webextension/js/background/messageHandler.js index 1971953..96e3628 100644 --- a/webextension/js/background/messageHandler.js +++ b/webextension/js/background/messageHandler.js @@ -68,6 +68,15 @@ const messageHandler = { case "exemptContainerAssignment": response = assignManager._exemptTab(m); break; + case "unhideAllTabs": + response = backgroundLogic.unhideAllTabs(m.message.windowId); + break; + case "showOnly": + response = backgroundLogic.showOnly({ + windowId: m.windowId, + cookieStoreId: m.cookieStoreId + }); + break; } return response; }); diff --git a/webextension/js/popup.js b/webextension/js/popup.js index a644322..dbd4104 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -500,6 +500,20 @@ Logic.registerPanel(P_CONTAINERS_LIST, { } }); + Logic.addEnterHandler(document.querySelector("#unhide-all-containers-link"), async function () { + try { + await browser.runtime.sendMessage({ + method: "unhideAllTabs", + message: { + windowId: browser.windows.WINDOW_ID_CURRENT + } + }); + window.close(); + } catch (e) { + window.close(); + } + }); + document.addEventListener("keydown", (e) => { const selectables = [...document.querySelectorAll("[tabindex='0'], [tabindex='-1']")]; const element = document.activeElement; @@ -603,6 +617,7 @@ Logic.registerPanel(P_CONTAINERS_LIST, { const hasTabs = (identity.hasHiddenTabs || identity.hasOpenTabs); const tr = document.createElement("tr"); const context = document.createElement("td"); + const hide = document.createElement("td"); const manage = document.createElement("td"); tr.classList.add("container-panel-row"); @@ -623,11 +638,24 @@ Logic.registerPanel(P_CONTAINERS_LIST, { context.querySelector(".container-name").textContent = identity.name; manage.innerHTML = ""; + hide.classList.add("hide-or-show-tabs", "pop-button"); + const img = document.createElement("img"); + img.classList.add("hide-or-show-tabs", "pop-button-image-small"); + if (identity.hasHiddenTabs) { + hide.title = escaped`Show ${identity.name} container`; + img.setAttribute("src", CONTAINER_HIDE_SRC); + } else { + hide.title = escaped`Hide ${identity.name} container`; + img.setAttribute("src", CONTAINER_UNHIDE_SRC); + } + hide.appendChild(img); + fragment.appendChild(tr); tr.appendChild(context); if (hasTabs) { + tr.appendChild(hide); tr.appendChild(manage); } @@ -635,16 +663,19 @@ Logic.registerPanel(P_CONTAINERS_LIST, { if (e.target.matches(".open-newtab") || e.target.parentNode.matches(".open-newtab") || e.type === "keydown") { - try { - browser.tabs.create({ - cookieStoreId: identity.cookieStoreId - }); - window.close(); - } catch (e) { - window.close(); - } - } else if (hasTabs) { + browser.tabs.create({ + cookieStoreId: identity.cookieStoreId + }).then(window.close).catch(window.close); + } else if (e.target.matches(".show-tabs") + || e.target.parentNode.matches(".show-tabs")) { Logic.showPanel(P_CONTAINER_INFO, identity); + } else if (e.target.matches(".hide-or-show-tabs") + || e.target.parentNode.matches(".hide-or-show-tabs")) { + browser.runtime.sendMessage({ + method: identity.hasHiddenTabs ? "showTabs" : "hideTabs", + windowId: browser.windows.WINDOW_ID_CURRENT, + cookieStoreId: identity.cookieStoreId + }).then(window.close).catch(window.close); } }); }); @@ -696,6 +727,19 @@ Logic.registerPanel(P_CONTAINER_INFO, { } }); + Logic.addEnterHandler(document.querySelector("#container-info-hideothers"), async function () { + try { + browser.runtime.sendMessage({ + method: "showOnly", + windowId: browser.windows.WINDOW_ID_CURRENT, + cookieStoreId: Logic.currentCookieStoreId() + }); + window.close(); + } catch (e) { + window.close(); + } + }); + // Check if the user has incompatible add-ons installed try { const incompatible = await browser.runtime.sendMessage({ @@ -752,6 +796,9 @@ Logic.registerPanel(P_CONTAINER_INFO, { const hideShowLabel = document.getElementById("container-info-hideorshow-label"); hideShowLabel.textContent = identity.hasHiddenTabs ? "Show this container" : "Hide this container"; + const hideOthersLabel = document.getElementById("container-info-hideothers"); + hideOthersLabel.textContent = identity.hasHiddenTabs ? "Show only this container" : "Hide other containers"; + // Let's remove all the previous tabs. const table = document.getElementById("container-info-table"); while (table.firstChild) { diff --git a/webextension/popup.html b/webextension/popup.html index 8cde498..1634f4b 100644 --- a/webextension/popup.html +++ b/webextension/popup.html @@ -1,3 +1,4 @@ + @@ -108,6 +109,7 @@
+ Show all Sort Tabs
@@ -140,6 +142,7 @@ Hide Container icon Hide this container
+
Hide other containers
Move tabs to a new window