/* 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/. */ /* eslint-env mozilla/remote-page */ import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; import { html, ifDefined } from "chrome://global/content/vendor/lit.all.mjs"; /** * Like DeferredTask but usable from content. */ class Debounce { timeout = null; #callback = null; #timeoutId = null; constructor(callback, timeout) { this.#callback = callback; this.timeout = timeout; this.#timeoutId = null; } #trigger() { this.#timeoutId = null; this.#callback(); } arm() { this.disarm(); this.#timeoutId = setTimeout(() => this.#trigger(), this.timeout); } disarm() { if (this.isArmed) { clearTimeout(this.#timeoutId); this.#timeoutId = null; } } finalize() { if (this.isArmed) { this.disarm(); this.#callback(); } } get isArmed() { return this.#timeoutId !== null; } } // eslint-disable-next-line import/no-unassigned-import import "chrome://global/content/elements/moz-card.mjs"; // eslint-disable-next-line import/no-unassigned-import import "chrome://global/content/elements/moz-button.mjs"; // eslint-disable-next-line import/no-unassigned-import import "chrome://global/content/elements/moz-button-group.mjs"; // eslint-disable-next-line import/no-unassigned-import import "chrome://browser/content/profiles/avatar.mjs"; // eslint-disable-next-line import/no-unassigned-import import "chrome://browser/content/profiles/profiles-theme-card.mjs"; // eslint-disable-next-line import/no-unassigned-import import "chrome://browser/content/profiles/profiles-group.mjs"; const SAVE_NAME_TIMEOUT = 2000; const SAVED_MESSAGE_TIMEOUT = 5000; /** * Element used for updating a profile's name, theme, and avatar. */ export class EditProfileCard extends MozLitElement { static properties = { profile: { type: Object }, profiles: { type: Array }, themes: { type: Array }, }; static queries = { mozCard: "moz-card", nameInput: "#profile-name", errorMessage: "#error-message", savedMessage: "#saved-message", deleteButton: "#delete-button", doneButton: "#done-button", moreThemesLink: "#more-themes", headerAvatar: "#header-avatar", avatarsPicker: "#avatars", themesPicker: "#themes", }; updateNameDebouncer = null; clearSavedMessageTimer = null; get avatars() { return this.avatarsPicker.childElements; } get themeCards() { return this.themesPicker.childElements; } constructor() { super(); this.updateNameDebouncer = new Debounce( () => this.updateName(), SAVE_NAME_TIMEOUT ); this.clearSavedMessageTimer = new Debounce( () => this.hideSavedMessage(), SAVED_MESSAGE_TIMEOUT ); } connectedCallback() { super.connectedCallback(); window.addEventListener("beforeunload", this); window.addEventListener("pagehide", this); this.init().then(() => (this.initialized = true)); } async init() { if (this.initialized) { return; } let { currentProfile, profiles, themes, isInAutomation } = await RPMSendQuery("Profiles:GetEditProfileContent"); if (isInAutomation) { this.updateNameDebouncer.timeout = 50; } this.profile = currentProfile; this.profiles = profiles; this.themes = themes; this.setFavicon(); } async getUpdateComplete() { const result = await super.getUpdateComplete(); await Promise.all( Array.from(this.themeCards).map(card => card.updateComplete) ); await this.mozCard.updateComplete; return result; } setFavicon() { let favicon = document.getElementById("favicon"); favicon.href = `chrome://browser/content/profiles/assets/16_${this.profile.avatar}.svg`; } getAvatarL10nId(value) { switch (value) { case "book": return "book-avatar"; case "briefcase": return "briefcase-avatar"; case "flower": return "flower-avatar"; case "heart": return "heart-avatar"; case "shopping": return "shopping-avatar"; case "star": return "star-avatar"; } return ""; } handleEvent(event) { switch (event.type) { case "beforeunload": { let newName = this.nameInput.value.trim(); if (newName === "") { this.showErrorMessage("edit-profile-page-no-name"); event.preventDefault(); } else { this.updateNameDebouncer.finalize(); } break; } case "pagehide": { RPMSendAsyncMessage("Profiles:PageHide"); } } } updated() { super.updated(); if (!this.profile) { return; } let { themeFg, themeBg } = this.profile; this.headerAvatar.style.fill = themeBg; this.headerAvatar.style.stroke = themeFg; } updateName() { this.updateNameDebouncer.disarm(); this.showSavedMessage(); let newName = this.nameInput.value.trim(); if (!newName) { return; } this.profile.name = newName; RPMSendAsyncMessage("Profiles:UpdateProfileName", this.profile); } async updateTheme(newThemeId) { if (newThemeId === this.profile.themeId) { return; } let theme = await RPMSendQuery("Profiles:UpdateProfileTheme", newThemeId); this.profile.themeId = theme.themeId; this.profile.themeFg = theme.themeFg; this.profile.themeBg = theme.themeBg; this.requestUpdate(); } async updateAvatar(newAvatar) { if (newAvatar === this.profile.avatar) { return; } this.profile.avatar = newAvatar; RPMSendAsyncMessage("Profiles:UpdateProfileAvatar", this.profile); this.requestUpdate(); this.setFavicon(); } isDuplicateName(newName) { return !!this.profiles.find( p => p.id !== this.profile.id && p.name === newName ); } async handleInputEvent() { this.hideSavedMessage(); let newName = this.nameInput.value.trim(); if (newName === "") { this.showErrorMessage("edit-profile-page-no-name"); } else if (this.isDuplicateName(newName)) { this.showErrorMessage("edit-profile-page-duplicate-name"); } else { this.hideErrorMessage(); this.updateNameDebouncer.arm(); } } showErrorMessage(l10nId) { this.updateNameDebouncer.disarm(); document.l10n.setAttributes(this.errorMessage, l10nId); this.errorMessage.parentElement.hidden = false; this.nameInput.setCustomValidity("invalid"); } hideErrorMessage() { this.errorMessage.parentElement.hidden = true; this.nameInput.setCustomValidity(""); } showSavedMessage() { this.savedMessage.parentElement.hidden = false; this.clearSavedMessageTimer.arm(); } hideSavedMessage() { this.savedMessage.parentElement.hidden = true; this.clearSavedMessageTimer.disarm(); } headerTemplate() { return html`
`; } nameInputTemplate() { return html``; } profilesNameTemplate() { return html`