569 lines
16 KiB
JavaScript
569 lines
16 KiB
JavaScript
/* 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";
|
|
|
|
// This is loaded into chrome windows with the subscript loader. Wrap in
|
|
// a block to prevent accidentally leaking globals onto `window`.
|
|
{
|
|
const lazy = {};
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
lazy,
|
|
"smartTabGroupsEnabled",
|
|
"browser.tabs.groups.smart.enabled"
|
|
);
|
|
|
|
const { TabStateFlusher } = ChromeUtils.importESModule(
|
|
"resource:///modules/sessionstore/TabStateFlusher.sys.mjs"
|
|
);
|
|
|
|
class MozTabbrowserTabGroupMenu extends MozXULElement {
|
|
static COLORS = [
|
|
"blue",
|
|
"purple",
|
|
"cyan",
|
|
"orange",
|
|
"yellow",
|
|
"pink",
|
|
"green",
|
|
"gray",
|
|
"red",
|
|
];
|
|
static markup = `
|
|
<panel
|
|
type="arrow"
|
|
class="panel tab-group-editor-panel"
|
|
orient="vertical"
|
|
role="dialog"
|
|
ignorekeys="true"
|
|
norolluponanchor="true">
|
|
|
|
<html:div class="panel-header">
|
|
<html:h1
|
|
id="tab-group-editor-title-create"
|
|
class="tab-group-create-mode-only"
|
|
data-l10n-id="tab-group-editor-title-create">
|
|
</html:h1>
|
|
<html:h1
|
|
id="tab-group-editor-title-edit"
|
|
class="tab-group-edit-mode-only"
|
|
data-l10n-id="tab-group-editor-title-edit">
|
|
</html:h1>
|
|
</html:div>
|
|
|
|
<toolbarseparator />
|
|
|
|
<html:div
|
|
class="panel-body
|
|
tab-group-editor-name">
|
|
<html:label
|
|
for="tab-group-name"
|
|
data-l10n-id="tab-group-editor-name-label">
|
|
</html:label>
|
|
<html:input
|
|
id="tab-group-name"
|
|
type="text"
|
|
name="tab-group-name"
|
|
value=""
|
|
data-l10n-id="tab-group-editor-name-field"
|
|
/>
|
|
</html:div>
|
|
|
|
<html:div
|
|
class="panel-body tab-group-editor-swatches"
|
|
role="radiogroup"
|
|
data-l10n-id="tab-group-editor-color-selector"
|
|
/>
|
|
|
|
<html:moz-button-group
|
|
class="panel-body tab-group-create-actions tab-group-create-mode-only">
|
|
<html:moz-button
|
|
id="tab-group-editor-button-cancel"
|
|
data-l10n-id="tab-group-editor-cancel">
|
|
</html:moz-button>
|
|
<html:moz-button
|
|
type="primary"
|
|
id="tab-group-editor-button-create"
|
|
data-l10n-id="tab-group-editor-done">
|
|
</html:moz-button>
|
|
</html:moz-button-group>
|
|
|
|
<toolbarseparator class="tab-group-edit-mode-only" />
|
|
|
|
<html:div
|
|
class="panel-body tab-group-edit-actions tab-group-edit-mode-only">
|
|
<toolbarbutton
|
|
tabindex="0"
|
|
id="tabGroupEditor_addNewTabInGroup"
|
|
class="subviewbutton"
|
|
data-l10n-id="tab-group-editor-action-new-tab">
|
|
</toolbarbutton>
|
|
<toolbarbutton
|
|
tabindex="0"
|
|
id="tabGroupEditor_moveGroupToNewWindow"
|
|
class="subviewbutton"
|
|
data-l10n-id="tab-group-editor-action-new-window">
|
|
</toolbarbutton>
|
|
<toolbarbutton
|
|
tabindex="0"
|
|
id="tabGroupEditor_saveAndCloseGroup"
|
|
class="subviewbutton"
|
|
data-l10n-id="tab-group-editor-action-save">
|
|
</toolbarbutton>
|
|
<toolbarbutton
|
|
tabindex="0"
|
|
id="tabGroupEditor_ungroupTabs"
|
|
class="subviewbutton"
|
|
data-l10n-id="tab-group-editor-action-ungroup">
|
|
</toolbarbutton>
|
|
</html:div>
|
|
|
|
<toolbarseparator class="tab-group-edit-mode-only" />
|
|
|
|
<html:div class="tab-group-edit-mode-only panel-body tab-group-delete">
|
|
<toolbarbutton
|
|
tabindex="0"
|
|
id="tabGroupEditor_deleteGroup"
|
|
class="subviewbutton"
|
|
data-l10n-id="tab-group-editor-action-delete">
|
|
</toolbarbutton>
|
|
</html:div>
|
|
|
|
</panel>
|
|
`;
|
|
|
|
static State = {
|
|
// Standard create mode (No AI UI)
|
|
CREATE_STANDARD_INITIAL: 0,
|
|
// Create mode with AI able to suggest tabs
|
|
CREATE_AI_INITIAL: 1,
|
|
// No ungrouped tabs to suggest (hide AI interactions)
|
|
CREATE_AI_INITIAL_SUGGESTIONS_DISABLED: 2,
|
|
// Create mode with suggestions
|
|
CREATE_AI_WITH_SUGGESTIONS: 3,
|
|
// Create mode with no suggestions
|
|
CREATE_AI_WITH_NO_SUGGESTIONS: 4,
|
|
// Standard edit mode (No AI UI)
|
|
EDIT_STANDARD_INITIAL: 5,
|
|
// Edit mode with AI able to suggest tabs
|
|
EDIT_AI_INITIAL: 6,
|
|
// No ungrouped tabs to suggest
|
|
EDIT_AI_INITIAL_SUGGESTIONS_DISABLED: 7,
|
|
// Edit mode with suggestions
|
|
EDIT_AI_WITH_SUGGESTIONS: 8,
|
|
// Edit mode with no suggestions
|
|
EDIT_AI_WITH_NO_SUGGESTIONS: 9,
|
|
LOADING: 10,
|
|
ERROR: 11,
|
|
};
|
|
|
|
#activeGroup;
|
|
#cancelButton;
|
|
#createButton;
|
|
#createMode;
|
|
#keepNewlyCreatedGroup;
|
|
#nameField;
|
|
#panel;
|
|
#swatches;
|
|
#swatchesContainer;
|
|
#suggestionState = MozTabbrowserTabGroupMenu.State.CREATE_STANDARD_INITIAL;
|
|
|
|
constructor() {
|
|
super();
|
|
}
|
|
|
|
connectedCallback() {
|
|
if (this._initialized) {
|
|
return;
|
|
}
|
|
|
|
this.textContent = "";
|
|
this.appendChild(this.constructor.fragment);
|
|
this.initializeAttributeInheritance();
|
|
|
|
this._initialized = true;
|
|
|
|
this.#cancelButton = this.querySelector(
|
|
"#tab-group-editor-button-cancel"
|
|
);
|
|
this.#createButton = this.querySelector(
|
|
"#tab-group-editor-button-create"
|
|
);
|
|
this.#panel = this.querySelector("panel");
|
|
this.#nameField = this.querySelector("#tab-group-name");
|
|
this.#swatchesContainer = this.querySelector(
|
|
".tab-group-editor-swatches"
|
|
);
|
|
this.#populateSwatches();
|
|
|
|
this.#cancelButton.addEventListener("click", () => {
|
|
this.close(false);
|
|
});
|
|
|
|
this.#createButton.addEventListener("click", () => {
|
|
this.close();
|
|
});
|
|
|
|
this.#nameField.addEventListener("input", () => {
|
|
if (this.activeGroup) {
|
|
this.activeGroup.label = this.#nameField.value;
|
|
}
|
|
});
|
|
|
|
document
|
|
.getElementById("tabGroupEditor_addNewTabInGroup")
|
|
.addEventListener("command", () => {
|
|
this.#handleNewTabInGroup();
|
|
});
|
|
|
|
document
|
|
.getElementById("tabGroupEditor_moveGroupToNewWindow")
|
|
.addEventListener("command", () => {
|
|
gBrowser.replaceGroupWithWindow(this.activeGroup);
|
|
});
|
|
|
|
document
|
|
.getElementById("tabGroupEditor_ungroupTabs")
|
|
.addEventListener("command", () => {
|
|
this.#handleUngroup();
|
|
});
|
|
|
|
document
|
|
.getElementById("tabGroupEditor_saveAndCloseGroup")
|
|
.addEventListener("command", () => {
|
|
this.#handleSaveAndClose();
|
|
});
|
|
|
|
document
|
|
.getElementById("tabGroupEditor_deleteGroup")
|
|
.addEventListener("command", () => {
|
|
this.#handleDelete();
|
|
});
|
|
|
|
this.panel.addEventListener("popupshown", this);
|
|
this.panel.addEventListener("popuphidden", this);
|
|
this.panel.addEventListener("keypress", this);
|
|
this.#swatchesContainer.addEventListener("change", this);
|
|
}
|
|
|
|
#populateSwatches() {
|
|
this.#clearSwatches();
|
|
for (let colorCode of MozTabbrowserTabGroupMenu.COLORS) {
|
|
let input = document.createElement("input");
|
|
input.id = `tab-group-editor-swatch-${colorCode}`;
|
|
input.type = "radio";
|
|
input.name = "tab-group-color";
|
|
input.value = colorCode;
|
|
let label = document.createElement("label");
|
|
label.classList.add("tab-group-editor-swatch");
|
|
label.setAttribute(
|
|
"data-l10n-id",
|
|
`tab-group-editor-color-selector2-${colorCode}`
|
|
);
|
|
label.htmlFor = input.id;
|
|
label.style.setProperty(
|
|
"--tabgroup-swatch-color",
|
|
`var(--tab-group-color-${colorCode})`
|
|
);
|
|
label.style.setProperty(
|
|
"--tabgroup-swatch-color-invert",
|
|
`var(--tab-group-color-${colorCode}-invert)`
|
|
);
|
|
this.#swatchesContainer.append(input, label);
|
|
this.#swatches.push(input);
|
|
}
|
|
}
|
|
|
|
#clearSwatches() {
|
|
this.#swatchesContainer.innerHTML = "";
|
|
this.#swatches = [];
|
|
}
|
|
|
|
get createMode() {
|
|
return this.#createMode;
|
|
}
|
|
|
|
set createMode(enableCreateMode) {
|
|
this.#panel.classList.toggle(
|
|
"tab-group-editor-mode-create",
|
|
enableCreateMode
|
|
);
|
|
this.#panel.setAttribute(
|
|
"aria-labelledby",
|
|
enableCreateMode
|
|
? "tab-group-editor-title-create"
|
|
: "tab-group-editor-title-edit"
|
|
);
|
|
this.#createMode = enableCreateMode;
|
|
}
|
|
|
|
get activeGroup() {
|
|
return this.#activeGroup;
|
|
}
|
|
|
|
set activeGroup(group = null) {
|
|
this.#activeGroup = group;
|
|
this.#nameField.value = group ? group.label : "";
|
|
this.#swatches.forEach(node => {
|
|
if (group && node.value == group.color) {
|
|
node.checked = true;
|
|
} else {
|
|
node.checked = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
get nextUnusedColor() {
|
|
let usedColors = [];
|
|
gBrowser.getAllTabGroups().forEach(group => {
|
|
usedColors.push(group.color);
|
|
});
|
|
let color = MozTabbrowserTabGroupMenu.COLORS.find(
|
|
colorCode => !usedColors.includes(colorCode)
|
|
);
|
|
if (!color) {
|
|
// if all colors are used, pick one randomly
|
|
let randomIndex = Math.floor(
|
|
Math.random() * MozTabbrowserTabGroupMenu.COLORS.length
|
|
);
|
|
color = MozTabbrowserTabGroupMenu.COLORS[randomIndex];
|
|
}
|
|
return color;
|
|
}
|
|
|
|
get panel() {
|
|
return this.children[0];
|
|
}
|
|
|
|
get #panelPosition() {
|
|
if (gBrowser.tabContainer.verticalMode) {
|
|
return SidebarController._positionStart
|
|
? "topleft topright"
|
|
: "topright topleft";
|
|
}
|
|
return "bottomleft topleft";
|
|
}
|
|
|
|
openCreateModal(group) {
|
|
this.activeGroup = group;
|
|
this.createMode = true;
|
|
this.suggestionState =
|
|
MozTabbrowserTabGroupMenu.State.CREATE_STANDARD_INITIAL;
|
|
if (lazy.smartTabGroupsEnabled) {
|
|
//TODO: set appropriate state
|
|
}
|
|
this.#panel.openPopup(group.firstChild, {
|
|
position: this.#panelPosition,
|
|
});
|
|
}
|
|
|
|
openEditModal(group) {
|
|
this.activeGroup = group;
|
|
this.createMode = false;
|
|
this.suggestionState =
|
|
MozTabbrowserTabGroupMenu.State.EDIT_STANDARD_INITIAL;
|
|
if (lazy.smartTabGroupsEnabled) {
|
|
//TODO: set appropriate state
|
|
}
|
|
this.#panel.openPopup(group.firstChild, {
|
|
position: this.#panelPosition,
|
|
});
|
|
document.getElementById("tabGroupEditor_moveGroupToNewWindow").disabled =
|
|
gBrowser.openTabs.length == this.activeGroup?.tabs.length;
|
|
this.#maybeDisableOrHideSaveButton();
|
|
}
|
|
|
|
#maybeDisableOrHideSaveButton() {
|
|
const saveAndCloseGroup = document.getElementById(
|
|
"tabGroupEditor_saveAndCloseGroup"
|
|
);
|
|
if (PrivateBrowsingUtils.isWindowPrivate(this.ownerGlobal)) {
|
|
saveAndCloseGroup.hidden = true;
|
|
return;
|
|
}
|
|
|
|
let flushes = [];
|
|
this.activeGroup.tabs.forEach(tab => {
|
|
flushes.push(TabStateFlusher.flush(tab.linkedBrowser));
|
|
});
|
|
Promise.allSettled(flushes).then(() => {
|
|
saveAndCloseGroup.disabled = !SessionStore.shouldSaveTabGroup(
|
|
this.activeGroup
|
|
);
|
|
});
|
|
}
|
|
|
|
close(keepNewlyCreatedGroup = true) {
|
|
if (this.createMode) {
|
|
this.#keepNewlyCreatedGroup = keepNewlyCreatedGroup;
|
|
}
|
|
this.#panel.hidePopup();
|
|
}
|
|
|
|
on_popupshown() {
|
|
if (this.createMode) {
|
|
this.#keepNewlyCreatedGroup = true;
|
|
}
|
|
this.#nameField.focus();
|
|
}
|
|
|
|
on_popuphidden() {
|
|
if (this.createMode) {
|
|
if (this.#keepNewlyCreatedGroup) {
|
|
this.dispatchEvent(
|
|
new CustomEvent("TabGroupCreateDone", { bubbles: true })
|
|
);
|
|
} else {
|
|
this.activeGroup.ungroupTabs();
|
|
}
|
|
}
|
|
this.activeGroup = null;
|
|
}
|
|
|
|
on_keypress(event) {
|
|
if (event.defaultPrevented) {
|
|
// The event has already been consumed inside of the panel.
|
|
return;
|
|
}
|
|
|
|
switch (event.keyCode) {
|
|
case KeyEvent.DOM_VK_ESCAPE:
|
|
this.close(false);
|
|
break;
|
|
case KeyEvent.DOM_VK_RETURN:
|
|
this.close();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* change handler for color input
|
|
*/
|
|
on_change(aEvent) {
|
|
if (aEvent.target.name != "tab-group-color") {
|
|
return;
|
|
}
|
|
if (this.activeGroup) {
|
|
this.activeGroup.color = aEvent.target.value;
|
|
}
|
|
}
|
|
|
|
async #handleNewTabInGroup() {
|
|
let lastTab = this.activeGroup?.tabs.at(-1);
|
|
let onTabOpened = async aEvent => {
|
|
this.activeGroup?.addTabs([aEvent.target]);
|
|
this.close();
|
|
window.removeEventListener("TabOpen", onTabOpened);
|
|
};
|
|
window.addEventListener("TabOpen", onTabOpened);
|
|
gBrowser.addAdjacentNewTab(lastTab);
|
|
}
|
|
|
|
#handleUngroup() {
|
|
this.activeGroup?.ungroupTabs();
|
|
}
|
|
|
|
#handleSaveAndClose() {
|
|
this.activeGroup.save();
|
|
this.activeGroup.dispatchEvent(
|
|
new CustomEvent("TabGroupSaved", { bubbles: true })
|
|
);
|
|
gBrowser.removeTabGroup(this.activeGroup);
|
|
}
|
|
|
|
#handleDelete() {
|
|
gBrowser.removeTabGroup(this.activeGroup);
|
|
}
|
|
|
|
/**
|
|
* @param {number} newState - See MozTabbrowserTabGroupMenu.State
|
|
*/
|
|
set suggestionState(newState) {
|
|
if (this.#suggestionState === newState) {
|
|
return;
|
|
}
|
|
this.#suggestionState = newState;
|
|
this.#renderSuggestionState();
|
|
}
|
|
|
|
#resetCommonUI() {
|
|
// TODO - has commun UI reset logic
|
|
}
|
|
|
|
#renderSuggestionState() {
|
|
switch (this.#suggestionState) {
|
|
// CREATE STANDARD INITIAL
|
|
case MozTabbrowserTabGroupMenu.State.CREATE_STANDARD_INITIAL:
|
|
this.#resetCommonUI();
|
|
//TODO
|
|
break;
|
|
|
|
//CREATE AI INITIAL
|
|
case MozTabbrowserTabGroupMenu.State.CREATE_AI_INITIAL:
|
|
this.#resetCommonUI();
|
|
//TODO
|
|
break;
|
|
|
|
// CREATE AI INITIAL SUGGESTIONS DISABLED
|
|
case MozTabbrowserTabGroupMenu.State
|
|
.CREATE_AI_INITIAL_SUGGESTIONS_DISABLED:
|
|
this.#resetCommonUI();
|
|
//TODO
|
|
break;
|
|
|
|
// CREATE AI WITH SUGGESTIONS
|
|
case MozTabbrowserTabGroupMenu.State.CREATE_AI_WITH_SUGGESTIONS:
|
|
//TODO
|
|
break;
|
|
|
|
// CREATE AI WITH NO SUGGESTIONS
|
|
case MozTabbrowserTabGroupMenu.State.CREATE_AI_WITH_NO_SUGGESTIONS:
|
|
//TODO
|
|
break;
|
|
|
|
// EDIT STANDARD INITIAL
|
|
case MozTabbrowserTabGroupMenu.State.EDIT_STANDARD_INITIAL:
|
|
this.#resetCommonUI();
|
|
//TODO
|
|
break;
|
|
|
|
// EDIT AI INITIAL
|
|
case MozTabbrowserTabGroupMenu.State.EDIT_AI_INITIAL:
|
|
this.#resetCommonUI();
|
|
//TODO
|
|
break;
|
|
|
|
// EDIT AI INITIAL SUGGESTIONS DISABLED
|
|
case MozTabbrowserTabGroupMenu.State
|
|
.EDIT_AI_INITIAL_SUGGESTIONS_DISABLED:
|
|
this.#resetCommonUI();
|
|
//TODO
|
|
break;
|
|
|
|
// EDIT AI WITH SUGGESTIONS
|
|
case MozTabbrowserTabGroupMenu.State.EDIT_AI_WITH_SUGGESTIONS:
|
|
//TODO
|
|
break;
|
|
|
|
// EDIT AI WITH NO SUGGESTIONS
|
|
case MozTabbrowserTabGroupMenu.State.EDIT_AI_WITH_NO_SUGGESTIONS:
|
|
//TODO
|
|
break;
|
|
|
|
// LOADING
|
|
case MozTabbrowserTabGroupMenu.State.LOADING:
|
|
//TODO
|
|
break;
|
|
|
|
// ERROR
|
|
case MozTabbrowserTabGroupMenu.State.ERROR:
|
|
//TODO
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
customElements.define("tabgroup-menu", MozTabbrowserTabGroupMenu);
|
|
}
|