diff options
Diffstat (limited to '')
-rw-r--r-- | comm/mail/components/accountcreation/views/container.mjs | 50 | ||||
-rw-r--r-- | comm/mail/components/accountcreation/views/email.mjs | 185 | ||||
-rw-r--r-- | comm/mail/components/accountcreation/views/start.mjs | 163 |
3 files changed, 398 insertions, 0 deletions
diff --git a/comm/mail/components/accountcreation/views/container.mjs b/comm/mail/components/accountcreation/views/container.mjs new file mode 100644 index 0000000000..3bf8d9b4dc --- /dev/null +++ b/comm/mail/components/accountcreation/views/container.mjs @@ -0,0 +1,50 @@ +/* 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/. */ + +/** + * Custom Element containing the main account hub dialog. Used to append the + * needed CSS files to the shadowDom to prevent style leakage. + * NOTE: This could directly extend an HTMLDialogElement if it had a shadowRoot. + */ +class AccountHubContainer extends HTMLElement { + /** @type {HTMLDialogElement} */ + modal; + + /** @type {DOMLocalization} */ + l10n; + + connectedCallback() { + if (this.shadowRoot) { + // Already connected, no need to run it again. + return; + } + + const shadowRoot = this.attachShadow({ mode: "open" }); + + // Load styles in the shadowRoot so we don't leak it. + let style = document.createElement("link"); + style.rel = "stylesheet"; + style.href = "chrome://messenger/skin/accountHub.css"; + shadowRoot.appendChild(style); + + let template = document.getElementById("accountHubDialog"); + let clonedNode = template.content.cloneNode(true); + shadowRoot.appendChild(clonedNode); + this.modal = shadowRoot.querySelector("dialog"); + + // We need to create an internal DOM localization in order to let fluent + // see the IDs inside our shadowRoot. + this.l10n = new DOMLocalization([ + "branding/brand.ftl", + "messenger/accountcreation/accountHub.ftl", + "messenger/accountcreation/accountSetup.ftl", + ]); + this.l10n.connectRoot(shadowRoot); + } + + disconnectedCallback() { + this.l10n.disconnectRoot(this.shadowRoot); + } +} +customElements.define("account-hub-container", AccountHubContainer); diff --git a/comm/mail/components/accountcreation/views/email.mjs b/comm/mail/components/accountcreation/views/email.mjs new file mode 100644 index 0000000000..f5a4d628b7 --- /dev/null +++ b/comm/mail/components/accountcreation/views/email.mjs @@ -0,0 +1,185 @@ +/* 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/. */ + +class AccountHubEmail extends HTMLElement { + /** + * The email setup form. + * + * @type {HTMLFormElement} + */ + #form; + + /** + * The account name field. + * + * @type {HTMLInputElement} + */ + #realName; + + /** + * The email field. + * + * @type {HTMLInputElement} + */ + #email; + + /** + * The password field. + * + * @type {HTMLInputElement} + */ + #password; + + /** + * The password visibility button. + * + * @type {HTMLButtonElement} + */ + #passwordToggleButton; + + /** + * The submit form button. + * + * @type {HTMLButtonElement} + */ + #continueButton; + + /** + * The domain name extrapolated from the email address. + * + * @type {string} + */ + #domain = ""; + + connectedCallback() { + if (this.hasConnected) { + return; + } + this.hasConnected = true; + + this.classList.add("account-hub-view"); + + let template = document.getElementById("accountHubEmailSetup"); + this.appendChild(template.content.cloneNode(true)); + + this.#form = this.querySelector("form"); + this.#realName = this.querySelector("#realName"); + this.#email = this.querySelector("#email"); + this.#password = this.querySelector("#password"); + this.#passwordToggleButton = this.querySelector("#passwordToggleButton"); + this.#continueButton = this.querySelector("#emailContinueButton"); + + this.initUI(); + + this.setupEventListeners(); + } + + /** + * Initialize the UI of the email setup flow. + */ + initUI() { + // Populate the account name if we can get some user info. + if ("@mozilla.org/userinfo;1" in Cc) { + let userInfo = Cc["@mozilla.org/userinfo;1"].getService(Ci.nsIUserInfo); + this.#realName.value = userInfo.fullname; + } + + this.#realName.focus(); + } + + /** + * Set up the event listeners for this workflow only once. + */ + setupEventListeners() { + this.#form.addEventListener("submit", event => { + event.preventDefault(); + event.stopPropagation(); + console.log("submit"); + }); + + this.#realName.addEventListener("input", () => this.#checkValidForm()); + this.#email.addEventListener("input", () => this.#checkValidForm()); + this.#password.addEventListener("input", () => this.#onPasswordInput()); + + this.#passwordToggleButton.addEventListener("click", event => { + this.#togglePasswordInput( + event.target.getAttribute("aria-pressed") === "false" + ); + }); + + // Set the Cancel/Back button. + this.querySelector("#emailGoBackButton").addEventListener("click", () => { + // If in first view, go back to start, otherwise go back in the flow. + this.dispatchEvent( + new CustomEvent("open-view", { + bubbles: true, + composed: true, + detail: { type: "START" }, + }) + ); + }); + } + + /** + * Check whether the user entered the minimum amount of information needed to + * leave the first view and is allowed to proceed to the detection step. + */ + #checkValidForm() { + const isValidForm = + this.#email.checkValidity() && this.#realName.checkValidity(); + this.#domain = isValidForm + ? this.#email.value.split("@")[1].toLowerCase() + : ""; + + this.#continueButton.disabled = !isValidForm; + } + + /** + * Handle the password visibility toggle on password input. + */ + #onPasswordInput() { + if (!this.#password.value) { + this.#togglePasswordInput(false); + } + } + + /** + * Toggle the password field type between `password` and `text` to allow users + * reading their typed password. + * + * @param {boolean} show - If the password field should become a text field. + */ + #togglePasswordInput(show) { + this.#password.type = show ? "text" : "password"; + this.#passwordToggleButton.setAttribute("aria-pressed", show.toString()); + document.l10n.setAttributes( + this.#passwordToggleButton, + show + ? "account-setup-password-toggle-hide" + : "account-setup-password-toggle-show" + ); + } + + /** + * Check if any operation is currently in process and return true only if we + * can leave this view. + * + * @returns {boolean} - If the account hub can remove this view. + */ + reset() { + // TODO + // Check for: + // - Non-abortable operations (autoconfig, email account setup, etc) + + this.#form.reset(); + this.#togglePasswordInput(false); + // TODO + // Before resetting we need to: + // - Clean up the fields. + // - Reset the autoconfig (cached server info). + // - Reset the view to the initial screen. + return true; + } +} +customElements.define("account-hub-email", AccountHubEmail); diff --git a/comm/mail/components/accountcreation/views/start.mjs b/comm/mail/components/accountcreation/views/start.mjs new file mode 100644 index 0000000000..66487cc928 --- /dev/null +++ b/comm/mail/components/accountcreation/views/start.mjs @@ -0,0 +1,163 @@ +/* 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/. */ + +/* global gSync */ + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +const { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +const { UIState } = ChromeUtils.importESModule( + "resource://services-sync/UIState.sys.mjs" +); + +class AccountHubStart extends HTMLElement { + #accounts = [ + { + id: "email", + l10n: "account-hub-email-setup-button", + type: "MAIL", + }, + { + id: "calendar", + l10n: "account-hub-calendar-setup-button", + type: "CALENDAR", + }, + { + id: "addressBook", + l10n: "account-hub-address-book-setup-button", + type: "ADDRESS_BOOK", + }, + { + id: "chat", + l10n: "account-hub-chat-setup-button", + type: "CHAT", + }, + { + id: "feed", + l10n: "account-hub-feed-setup-button", + type: "FEED", + }, + { + id: "newsgroup", + l10n: "account-hub-newsgroup-setup-button", + type: "NNTP", + }, + // TODO: Import/Export of profiles is kinda broken so we don't want to + // expose it so much for now. + // { + // id: "import", + // l10n: "account-hub-import-setup-button", + // type: "IMPORT", + // }, + ]; + + connectedCallback() { + if (this.hasConnected) { + return; + } + this.hasConnected = true; + + this.classList.add("account-hub-view"); + + let template = document.getElementById("accountHubStart"); + this.appendChild(template.content.cloneNode(true)); + + this.initUI(); + + this.setupAccountFlows(); + } + + /** + * Update the UI to reflect reality whenever this view is triggered. + */ + initUI() { + const hasAccounts = MailServices.accounts.accounts.length; + this.querySelector("#welcomeHeader").hidden = hasAccounts; + this.querySelector("#defaultHeader").hidden = !hasAccounts; + + if (AppConstants.NIGHTLY_BUILD) { + this.updateFxAButton(); + } + + // Hide the release notes link for nightly builds since we don't have any. + if (AppConstants.NIGHTLY_BUILD) { + this.querySelector("#hubReleaseNotes").closest("li").hidden = true; + return; + } + + if ( + Services.prefs.getPrefType("app.releaseNotesURL") != + Services.prefs.PREF_INVALID + ) { + let relNotesURL = Services.urlFormatter.formatURLPref( + "app.releaseNotesURL" + ); + if (relNotesURL != "about:blank") { + this.querySelector("#hubReleaseNotes").href = relNotesURL; + return; + } + // Hide the release notes link if we don't have a URL to add. + this.querySelector("#hubReleaseNotes").closest("li").hidden = true; + } + } + + /** + * Populate the main container fo the start view with all the available + * account creation flows. + */ + setupAccountFlows() { + const fragment = new DocumentFragment(); + for (const account of this.#accounts) { + const button = document.createElement("button"); + button.id = `${account.id}Button`; + button.classList.add("button", "button-account"); + document.l10n.setAttributes(button, account.l10n); + button.addEventListener("click", () => { + this.dispatchEvent( + new CustomEvent("open-view", { + bubbles: true, + composed: true, + detail: { + type: account.type, + }, + }) + ); + }); + fragment.append(button); + } + this.querySelector(".hub-body-grid").replaceChildren(fragment); + + if (AppConstants.NIGHTLY_BUILD) { + this.querySelector("#hubSyncButton").addEventListener("click", () => { + // FIXME: Open this in a dialog or browser inside the modal, or find a + // way to close the account hub without an account and open it again in + // case the FxA login fails to set up accounts. + gSync.initFxA(); + }); + } + } + + /** + * Set up the Firefox Sync button. + */ + updateFxAButton() { + const state = UIState.get(); + this.querySelector("#hubSyncButton").hidden = + state.status == UIState.STATUS_SIGNED_IN; + } + + /** + * The start view doesn't have any abortable operation that needs to be + * checked, so we always return true. + * + * @returns {boolean} - Always true. + */ + reset() { + return true; + } +} +customElements.define("account-hub-start", AccountHubStart); |