summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/accountcreation/content/accountHub.js
blob: a703ffce16c8fde2fcb15ba08cf28c31a4726f95 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
/* 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";

var { MailServices } = ChromeUtils.import(
  "resource:///modules/MailServices.jsm"
);

/**
 * Holds the main controller class.
 *
 * @type {?AccountHubControllerClass}
 */
var AccountHubController;

/**
 * Controller class to handle the primary views of the account setup flow.
 * This class acts as a sort of controller to lazily load the needed views upon
 * request. It doesn't handle any data and it should only be used to switch
 * between the different setup flows.
 * All methods of this class should be private, except for the open() method.
 */
class AccountHubControllerClass {
  /**
   * The account hub main modal dialog.
   *
   * @type {?HTMLElement}
   */
  #modal = null;

  /**
   * The currently visible view inside the dialog.
   *
   * @type {?HTMLElement}
   */
  #currentView = null;

  /**
   * Object containing all strings to trigger the needed methods for the various
   * views.
   */
  #accounts = {
    START: () => this.#viewStart(),
    MAIL: () => this.#viewEmailSetup(),
    CALENDAR: () => this.#viewCalendarSetup(),
    ADDRESS_BOOK: () => this.#viewAddressBookSetup(),
    CHAT: () => this.#viewChatSetup(),
    FEED: () => this.#viewFeedSetup(),
    NNTP: () => this.#viewNNTPSetup(),
    IMPORT: () => this.#viewImportSetup(),
  };

  constructor() {
    this.ready = this.#init();
  }

  async #init() {
    await this.#loadScript("container");
    const element = document.createElement("account-hub-container");
    document.body.appendChild(element);
    this.#modal = element.modal;

    let closeButton = this.#modal.querySelector("#closeButton");
    closeButton.hidden = !MailServices.accounts.accounts.length;
    closeButton.addEventListener("click", () => this.#modal.close());

    // Listen from closing requests coming from child elements.
    this.#modal.addEventListener(
      "request-close",
      event => {
        event.stopPropagation();
        this.#modal.close();
      },
      {
        capture: true,
      }
    );

    this.#modal.addEventListener(
      "open-view",
      event => {
        event.stopPropagation();
        this.open(event.detail.type);
      },
      {
        capture: true,
      }
    );

    this.#modal.addEventListener("close", event => {
      // Don't allow the dialog to be closed if some operations are can't be
      // aborted or the UI can't be cleared.
      if (!this.#reset()) {
        event.preventDefault();
      }
    });

    this.#modal.addEventListener("cancel", event => {
      if (
        !MailServices.accounts.accounts.length &&
        !Services.prefs.getBoolPref("app.use_without_mail_account", false)
      ) {
        // Prevent closing the modal if no account is currently present and the
        // user didn't request using Thunderbird without an email account.
        event.preventDefault();
        return;
      }

      // Don't allow the dialog to be canceled via the ESC key if some
      // operations are in progress and can't be aborted or the UI can't be
      // cleared.
      if (!this.#reset()) {
        event.preventDefault();
      }
    });
  }

  /**
   * Check if we don't currently have the needed custom element for the
   * requested view and load the needed script. We do this to avoid loading all
   * the unnecessary account creation files.
   *
   * @param {string} view - The name of the view to load.
   * @returns {Promise<void>} Resolves when custom element of the view is usable.
   */
  #loadScript(view) {
    if (customElements.get(`account-hub-${view}`)) {
      return Promise.resolve();
    }
    // eslint-disable-next-line no-unsanitized/method
    return import(
      `chrome://messenger/content/accountcreation/views/${view}.mjs`
    );
  }

  /**
   * Create a custom element and append it to the modal inner HTML, or simply
   * show it if it was already loaded.
   *
   * @param {string} id - The ID of the template to clone.
   */
  #loadView(id) {
    this.#hideViews();

    let view = this.#modal.querySelector(id);
    if (view) {
      view.hidden = false;
      this.#currentView = view;
      // Update the UI to make sure we're refreshing old views.
      this.#currentView.initUI();
      return;
    }

    view = document.createElement(id);
    this.#modal.appendChild(view);
    this.#currentView = view;
  }

  /**
   * Hide all the currently visible views.
   */
  #hideViews() {
    for (let view of this.#modal.querySelectorAll(".account-hub-view")) {
      view.hidden = true;
    }
  }

  /**
   * Open the main modal dialog and load the requested account setup view, or
   * fallback to the initial start screen.
   *
   * @param {?string} type - Which account flow to load when the modal opens.
   */
  open(type = "START") {
    // Interrupt if something went wrong while cleaning up a previously loaded
    // view.
    if (!this.#reset()) {
      return;
    }

    this.#accounts[type].call();
    if (!this.#modal.open) {
      this.#modal.showModal();
    }
  }

  /**
   * Check if we have a current class and try to trigger the rest in order to
   * handle abort operations and markup clean up, if possible.
   *
   * @returns {boolean} - True if the reset process was successful or we didn't
   *   have anything to reset.
   */
  #reset() {
    let isClean = this.#currentView?.reset() ?? true;
    // If the reset operation was successful, clear the current class.
    if (isClean) {
      this.#hideViews();
      this.#currentView = null;
    }
    return isClean;
  }

  /**
   * Show the initial view of the account hub dialog.
   */
  async #viewStart() {
    await this.#loadScript("start");
    this.#loadView("account-hub-start");
  }

  /**
   * Show the email setup view.
   */
  async #viewEmailSetup() {
    await this.#loadScript("email");
    this.#loadView("account-hub-email");
  }

  /**
   * TODO: Show the calendar setup view.
   */
  #viewCalendarSetup() {
    console.log("Calendar setup");
  }

  /**
   * TODO: Show the address book setup view.
   */
  #viewAddressBookSetup() {
    console.log("Address Book setup");
  }

  /**
   * TODO: Show the chat setup view.
   */
  #viewChatSetup() {
    console.log("Chat setup");
  }

  /**
   * TODO: Show the feed setup view.
   */
  #viewFeedSetup() {
    console.log("Feed setup");
  }

  /**
   * TODO: Show the newsgroup setup view.
   */
  #viewNNTPSetup() {
    console.log("Newsgroup setup");
  }

  /**
   * TODO: Show the import setup view.
   */
  #viewImportSetup() {
    console.log("Import setup");
  }
}

/**
 * Open the account hub dialog and show the requested view.
 *
 * @param {?string} type - The type of view that should be loaded when the modal
 *   is showed. See AccountHubController::#accounts for a list references.
 */
async function openAccountHub(type) {
  if (!AccountHubController) {
    AccountHubController = new AccountHubControllerClass();
  }
  await AccountHubController.ready;
  AccountHubController.open(type);
}