/* 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-disable-next-line import/no-unassigned-import import "chrome://global/content/elements/moz-button-group.mjs"; import { MigrationWizardConstants } from "chrome://browser/content/migration/migration-wizard-constants.mjs"; /** * This component contains the UI that steps users through migrating their * data from other browsers to this one. This component only contains very * basic logic and structure for the UI, and most of the state management * occurs in the MigrationWizardChild JSWindowActor. */ export class MigrationWizard extends HTMLElement { static #template = null; #deck = null; #browserProfileSelector = null; #shadowRoot = null; static get markup() { return ` `; } static get fragment() { if (!MigrationWizard.#template) { let parser = new DOMParser(); let doc = parser.parseFromString(MigrationWizard.markup, "text/html"); MigrationWizard.#template = document.importNode( doc.querySelector("template"), true ); } let fragment = MigrationWizard.#template.content.cloneNode(true); if (window.IS_STORYBOOK) { // If we're using Storybook, load the CSS from the static local file // system rather than chrome:// to take advantage of auto-reloading. fragment.querySelector("link[rel=stylesheet]").href = "./migration/migration-wizard.css"; } return fragment; } constructor() { super(); const shadow = this.attachShadow({ mode: "closed" }); if (window.MozXULElement) { window.MozXULElement.insertFTLIfNeeded( "locales-preview/migrationWizard.ftl" ); } document.l10n.connectRoot(shadow); shadow.appendChild(MigrationWizard.fragment); this.#deck = shadow.querySelector("#wizard-deck"); this.#browserProfileSelector = shadow.querySelector( "#browser-profile-selector" ); let cancelCloseButtons = shadow.querySelectorAll(".cancel-close"); for (let button of cancelCloseButtons) { button.addEventListener("click", this); } this.#shadowRoot = shadow; } connectedCallback() { this.dispatchEvent( new CustomEvent("MigrationWizard:Init", { bubbles: true }) ); } /** * This is the main entrypoint for updating the state and appearance of * the wizard. * * @param {object} state The state to be represented by the component. * @param {string} state.page The page of the wizard to display. This should * be one of the MigrationWizardConstants.PAGES constants. */ setState(state) { switch (state.page) { case MigrationWizardConstants.PAGES.SELECTION: { this.#onShowingSelection(state); break; } case MigrationWizardConstants.PAGES.PROGRESS: { this.#onShowingProgress(state); break; } } this.#deck.setAttribute("selected-view", `page-${state.page}`); if (window.IS_STORYBOOK) { this.#updateForStorybook(); } } /** * Called when showing the browser/profile selection page of the wizard. * * @param {object} state * The state object passed into setState. The following properties are * used: * @param {string[]} state.migrators An array of source browser names that * can be migrated from. */ #onShowingSelection(state) { this.#browserProfileSelector.textContent = ""; for (let migratorKey of state.migrators) { let opt = document.createElement("option"); opt.value = migratorKey; opt.textContent = migratorKey; this.#browserProfileSelector.appendChild(opt); } } /** * @typedef {object} ProgressState * The migration progress state for a resource. * @property {boolean} inProgress * True if progress is still underway. * @property {string} [message=undefined] * An optional message to display underneath the resource in * the progress dialog. This message is only shown when inProgress * is `false`. */ /** * Called when showing the progress / success page of the wizard. * * @param {object} state * The state object passed into setState. The following properties are * used: * @param {Object} state.progress * An object whose keys match one of DISPLAYED_RESOURCE_TYPES. * * Any resource type not included in state.progress will be hidden. */ #onShowingProgress(state) { // Any resource progress group not included in state.progress is hidden. let resourceGroups = this.#shadowRoot.querySelectorAll( ".resource-progress-group" ); let totalProgressGroups = Object.keys(state.progress).length; let remainingProgressGroups = totalProgressGroups; for (let group of resourceGroups) { let resourceType = group.dataset.resourceType; if (!state.progress.hasOwnProperty(resourceType)) { group.hidden = true; continue; } group.hidden = false; let progressIcon = group.querySelector(".progress-icon"); let successText = group.querySelector(".success-text"); if (state.progress[resourceType].inProgress) { document.l10n.setAttributes( progressIcon, "migration-wizard-progress-icon-in-progress" ); progressIcon.classList.remove("completed"); // With no status text, we re-insert the   so that the status // text area does not fully collapse. successText.appendChild(document.createTextNode("\u00A0")); } else { document.l10n.setAttributes( progressIcon, "migration-wizard-progress-icon-completed" ); progressIcon.classList.add("completed"); successText.textContent = state.progress[resourceType].message; remainingProgressGroups--; } } let headerL10nID = remainingProgressGroups > 0 ? "migration-wizard-progress-header" : "migration-wizard-progress-done-header"; let header = this.#shadowRoot.getElementById("progress-header"); document.l10n.setAttributes(header, headerL10nID); } /** * Certain parts of the MigrationWizard need to be modified slightly * in order to work properly with Storybook. This method should be called * to apply those changes after changing state. */ #updateForStorybook() { // The CSS mask used for the progress spinner cannot be loaded via // chrome:// URIs in Storybook. We work around this by exposing the // progress elements as custom parts that the MigrationWizard story // can style on its own. this.#shadowRoot.querySelectorAll(".progress-icon").forEach(progressEl => { if (progressEl.classList.contains("completed")) { progressEl.removeAttribute("part"); } else { progressEl.setAttribute("part", "progress-spinner"); } }); } handleEvent(event) { if ( event.type == "click" && event.target.classList.contains("cancel-close") ) { this.dispatchEvent( new CustomEvent("MigrationWizard:Close", { bubbles: true }) ); } } } if (globalThis.customElements) { customElements.define("migration-wizard", MigrationWizard); }