GroupsList.sys.mjs (6823B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import { PrivateBrowsingUtils } from "resource://gre/modules/PrivateBrowsingUtils.sys.mjs"; 6 import { TabMetrics } from "moz-src:///browser/components/tabbrowser/TabMetrics.sys.mjs"; 7 8 const MAX_INITIAL_ITEMS = 5; 9 10 export class GroupsPanel { 11 constructor({ view, containerNode, showAll = false }) { 12 this.view = view; 13 this.#showAll = showAll; 14 this.containerNode = containerNode; 15 this.win = containerNode.ownerGlobal; 16 this.doc = containerNode.ownerDocument; 17 this.panelMultiView = null; 18 this.view.addEventListener("ViewShowing", this); 19 } 20 21 handleEvent(event) { 22 switch (event.type) { 23 case "ViewShowing": 24 if (event.target == this.view) { 25 this.panelMultiView = this.view.panelMultiView; 26 this.#populate(); 27 this.#addObservers(); 28 this.win.addEventListener("unload", this); 29 } 30 break; 31 case "PanelMultiViewHidden": 32 if ((this.panelMultiView = event.target)) { 33 this.#cleanup(); 34 this.#removeObservers(); 35 this.panelMultiView = null; 36 } 37 break; 38 case "unload": 39 if (this.panelMultiView) { 40 this.#removeObservers(); 41 } 42 break; 43 case "command": 44 this.#handleCommand(event); 45 break; 46 } 47 } 48 49 #addObservers() { 50 Services.obs.addObserver(this, "sessionstore-closed-objects-changed"); 51 Services.obs.addObserver(this, "browser-tabgroup-removed-from-dom"); 52 } 53 54 #removeObservers() { 55 Services.obs.removeObserver(this, "sessionstore-closed-objects-changed"); 56 Services.obs.removeObserver(this, "browser-tabgroup-removed-from-dom"); 57 } 58 59 observe(aSubject, aTopic) { 60 switch (aTopic) { 61 case "sessionstore-closed-objects-changed": 62 case "browser-tabgroup-removed-from-dom": 63 this.#cleanup(); 64 this.#populate(); 65 break; 66 } 67 } 68 69 #handleCommand(event) { 70 let { tabGroupId, command } = event.target.dataset; 71 72 switch (command) { 73 case "allTabsGroupView_selectGroup": { 74 let group = this.win.gBrowser.getTabGroupById(tabGroupId); 75 group.select(); 76 group.ownerGlobal.focus(); 77 break; 78 } 79 80 case "allTabsGroupView_restoreGroup": 81 this.win.SessionStore.openSavedTabGroup(tabGroupId, this.win, { 82 source: TabMetrics.METRIC_SOURCE.TAB_OVERFLOW_MENU, 83 }); 84 break; 85 } 86 } 87 88 #setupListeners() { 89 this.view.addEventListener("command", this); 90 this.panelMultiView.addEventListener("PanelMultiViewHidden", this); 91 } 92 93 #cleanup() { 94 this.containerNode.innerHTML = ""; 95 this.view.removeEventListener("command", this); 96 } 97 98 #showAll; 99 #populate() { 100 let fragment = this.doc.createDocumentFragment(); 101 102 let openGroups = this.win.gBrowser.getAllTabGroups({ 103 sortByLastSeenActive: true, 104 }); 105 let savedGroups = []; 106 if (!PrivateBrowsingUtils.isWindowPrivate(this.win)) { 107 savedGroups = this.win.SessionStore.savedGroups.toSorted( 108 (group1, group2) => group2.closedAt - group1.closedAt 109 ); 110 } 111 112 let totalItemCount = savedGroups.length + openGroups.length; 113 if (totalItemCount && !this.#showAll) { 114 let header = this.doc.createElement("h2"); 115 header.setAttribute("class", "subview-subheader"); 116 this.doc.l10n.setAttributes( 117 header, 118 "all-tabs-menu-recent-tab-groups-header" 119 ); 120 fragment.appendChild(header); 121 } 122 123 let addShowAllButton = !this.#showAll && totalItemCount > MAX_INITIAL_ITEMS; 124 let itemCount = addShowAllButton ? 1 : 0; 125 for (let groupData of openGroups) { 126 if (itemCount >= MAX_INITIAL_ITEMS && !this.#showAll) { 127 break; 128 } 129 itemCount++; 130 let row = this.#createRow(groupData); 131 fragment.appendChild(row); 132 } 133 134 for (let groupData of savedGroups) { 135 if (itemCount >= MAX_INITIAL_ITEMS && !this.#showAll) { 136 break; 137 } 138 itemCount++; 139 let row = this.#createRow(groupData, { isOpen: false }); 140 fragment.appendChild(row); 141 } 142 143 if (addShowAllButton) { 144 let button = this.doc.createXULElement("toolbarbutton"); 145 button.setAttribute("id", "allTabsMenu-groupsViewShowMore"); 146 button.setAttribute("class", "subviewbutton subviewbutton-nav"); 147 button.setAttribute("closemenu", "none"); 148 button.setAttribute("flex", "1"); 149 this.doc.l10n.setAttributes(button, "all-tabs-menu-tab-groups-show-all"); 150 fragment.appendChild(button); 151 } 152 153 this.containerNode.replaceChildren(fragment); 154 this.#setupListeners(); 155 } 156 157 /** 158 * @param {TabGroupStateData} group 159 * @param {object} [options] 160 * @param {boolean} [options.isOpen] 161 * Set to true if the group is currently open, and false if it's saved 162 * @returns {XULElement} 163 */ 164 #createRow(group, { isOpen = true } = {}) { 165 let { doc } = this; 166 let row = doc.createXULElement("toolbaritem"); 167 row.setAttribute("class", "all-tabs-item all-tabs-group-item"); 168 169 row.style.setProperty( 170 "--tab-group-color", 171 `var(--tab-group-color-${group.color})` 172 ); 173 row.style.setProperty( 174 "--tab-group-color-invert", 175 `var(--tab-group-color-${group.color}-invert)` 176 ); 177 row.style.setProperty( 178 "--tab-group-color-pale", 179 `var(--tab-group-color-${group.color}-pale)` 180 ); 181 let button = doc.createXULElement("toolbarbutton"); 182 button.setAttribute( 183 "class", 184 "all-tabs-button subviewbutton subviewbutton-iconic all-tabs-group-action-button tab-group-icon" 185 ); 186 button.dataset.tabGroupId = group.id; 187 if (!isOpen) { 188 button.classList.add( 189 "all-tabs-group-saved-group", 190 "tab-group-icon-closed" 191 ); 192 button.dataset.command = "allTabsGroupView_restoreGroup"; 193 button.setAttribute("context", "saved-tab-group-context-menu"); 194 } else { 195 button.dataset.command = "allTabsGroupView_selectGroup"; 196 button.setAttribute("context", "open-tab-group-context-menu"); 197 } 198 button.setAttribute("flex", "1"); 199 button.setAttribute("crop", "end"); 200 201 let setName = tabGroupName => { 202 if (group.saved) { 203 doc.l10n.setAttributes(button, "tabbrowser-manager-closed-tab-group", { 204 tabGroupName, 205 }); 206 } else { 207 button.setAttribute("label", tabGroupName); 208 button.setAttribute("tooltiptext", tabGroupName); 209 } 210 }; 211 212 if (group.name) { 213 setName(group.name); 214 } else { 215 doc.l10n 216 .formatValues([{ id: "tab-group-name-default" }]) 217 .then(([msg]) => { 218 setName(msg); 219 }); 220 } 221 row.appendChild(button); 222 return row; 223 } 224 }