Files
tubestation/browser/components/tabbrowser/content/tabgroup-menu.js

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