Bug 1926706 - Loading state in account hub. r=aleca
Differential Revision: https://phabricator.services.mozilla.com/D229021 --HG-- extra : amend_source : d668ead45c02ccdd2c99c614aee8715a004c4bf2
This commit is contained in:
Родитель
41ae1dd1ea
Коммит
ec2809549f
|
@ -50,15 +50,14 @@ class AccountHubFooter extends HTMLElement {
|
|||
}
|
||||
|
||||
toggleForwardDisabled(value) {
|
||||
this.querySelector("#forward").disabled = value;
|
||||
this.querySelector("#forward").disabled = value || this.disabled;
|
||||
}
|
||||
|
||||
canCustom(value) {
|
||||
const customAction = this.querySelector("#custom");
|
||||
customAction.hidden = !value;
|
||||
customAction.disabled = !value;
|
||||
customAction.disabled = !value || this.disabled;
|
||||
if (value) {
|
||||
customAction.disabled = false;
|
||||
customAction.addEventListener("click", this);
|
||||
document.l10n.setAttributes(customAction, value);
|
||||
}
|
||||
|
@ -86,6 +85,19 @@ class AccountHubFooter extends HTMLElement {
|
|||
relNotesLink.href = relNotesURL;
|
||||
relNotesLink.closest("li[hidden]").hidden = false;
|
||||
}
|
||||
|
||||
get disabled() {
|
||||
return this.querySelector("#back").disabled;
|
||||
}
|
||||
|
||||
set disabled(val) {
|
||||
this.toggleForwardDisabled(val);
|
||||
this.querySelector("#back").disabled = val;
|
||||
const customAction = this.querySelector("#custom");
|
||||
if (!customAction.hidden) {
|
||||
customAction.disabled = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("account-hub-footer", AccountHubFooter);
|
||||
|
|
|
@ -116,6 +116,12 @@ class EmailAutoForm extends AccountHubStep {
|
|||
captureState() {
|
||||
return this.#currentConfig;
|
||||
}
|
||||
|
||||
set disabled(val) {
|
||||
for (const input of this.querySelectorAll("input")) {
|
||||
input.disabled = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("email-auto-form", EmailAutoForm);
|
||||
|
|
|
@ -341,6 +341,16 @@ class EmailOutgoingForm extends AccountHubStep {
|
|||
|
||||
this.#adjustOAuth2Visibility(config);
|
||||
}
|
||||
|
||||
set disabled(val) {
|
||||
this.#outgoingPort.disabled = val;
|
||||
this.#outgoingUsername.disabled =
|
||||
val || this.#outgoingAuthenticationMethod == 1;
|
||||
this.#outgoingHostname.disabled = val;
|
||||
this.#outgoingConnectionSecurity.disabled = val;
|
||||
this.#outgoingAuthenticationMethod.disabled = val;
|
||||
this.querySelector("#advancedConfigurationOutgoing").disabled = val;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("email-manual-outgoing-form", EmailOutgoingForm);
|
||||
|
|
|
@ -105,4 +105,10 @@
|
|||
</account-hub-step>
|
||||
|
||||
<account-hub-footer id="emailFooter" class="hub-footer"></account-hub-footer>
|
||||
|
||||
<div id="loadingOverlay">
|
||||
<div class="loader-outside">
|
||||
<div class="loader-inside"></div>
|
||||
</div>
|
||||
</div>
|
||||
</html:template>
|
||||
|
|
|
@ -289,6 +289,7 @@ class AccountHubEmail extends HTMLElement {
|
|||
* @param {string} subview - Subview for which the UI is being inititialized.
|
||||
*/
|
||||
async #initUI(subview) {
|
||||
this.#stopLoading();
|
||||
this.#hideSubviews();
|
||||
this.#clearNotifications();
|
||||
this.#currentState = subview;
|
||||
|
@ -352,6 +353,48 @@ class AccountHubEmail extends HTMLElement {
|
|||
}
|
||||
}
|
||||
|
||||
#loadingTimeout = null;
|
||||
|
||||
/**
|
||||
* Show a loading notification and disable all inputs (except closing the
|
||||
* dialog). If the load takes too long, a spinner is overlaid.
|
||||
*
|
||||
* TODO: should be able to cancel some loads, if they're abortable.
|
||||
*
|
||||
* @param {string} loadingFluentId
|
||||
*/
|
||||
#startLoading(loadingFluentId) {
|
||||
this.#states[this.#currentState].subview.showNotification({
|
||||
fluentTitleId: loadingFluentId,
|
||||
type: "info",
|
||||
});
|
||||
this.classList.add("busy");
|
||||
this.#states[this.#currentState].subview.disabled = true;
|
||||
this.#emailFooter.disabled = true;
|
||||
this.#loadingTimeout = setTimeout(() => {
|
||||
this.classList.add("spinner");
|
||||
this.#loadingTimeout = null;
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop loading, clearing the notification, restoring form controls and hiding
|
||||
* the spinner if it was visible.
|
||||
*/
|
||||
#stopLoading() {
|
||||
if (!this.classList.contains("busy")) {
|
||||
return;
|
||||
}
|
||||
this.#clearNotifications();
|
||||
this.#states[this.#currentState].subview.disabled = false;
|
||||
this.#emailFooter.disabled = false;
|
||||
this.classList.remove("busy", "spinner");
|
||||
if (this.#loadingTimeout) {
|
||||
clearTimeout(this.#loadingTimeout);
|
||||
this.#loadingTimeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the events from the subviews.
|
||||
*
|
||||
|
@ -393,7 +436,11 @@ class AccountHubEmail extends HTMLElement {
|
|||
this.#states[this.#currentState].subview.setState(config);
|
||||
} catch (error) {
|
||||
this.#handleAbortable();
|
||||
stateDetails.subview.showErrorNotification(error.title, error.text);
|
||||
stateDetails.subview.showNotification({
|
||||
title: error.title || error.message,
|
||||
description: error.text,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "custom-footer-action":
|
||||
|
@ -487,6 +534,7 @@ class AccountHubEmail extends HTMLElement {
|
|||
async #handleForwardAction(currentState, stateData) {
|
||||
switch (currentState) {
|
||||
case "autoConfigSubview":
|
||||
this.#startLoading("account-hub-lookup-email-configuration-title");
|
||||
try {
|
||||
this.#emailFooter.canBack(true);
|
||||
this.#email = stateData.email;
|
||||
|
@ -501,25 +549,32 @@ class AccountHubEmail extends HTMLElement {
|
|||
this.#currentConfig = this.#fillAccountConfig(
|
||||
this.#getEmptyAccountConfig()
|
||||
);
|
||||
this.#stopLoading();
|
||||
await this.#initUI("incomingConfigSubview");
|
||||
this.#states[this.#currentState].previousStep =
|
||||
"autoConfigSubview";
|
||||
} else {
|
||||
this.#hasCancelled = false;
|
||||
this.#states[this.#currentState].subview.showNotification({
|
||||
fluentTitleId: "account-hub-find-settings-failed",
|
||||
type: "warning",
|
||||
});
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
this.#currentConfig = this.#fillAccountConfig(config);
|
||||
await this.#initUI(this.#states[this.#currentState].nextStep);
|
||||
this.#states.incomingConfigSubview.previousStep =
|
||||
"emailConfigFoundSubview";
|
||||
this.#states[this.#currentState].subview.showNotification({
|
||||
fluentTitleId: "account-hub-config-success",
|
||||
type: "success",
|
||||
});
|
||||
this.#hasCancelled = false;
|
||||
this.#stopLoading();
|
||||
break;
|
||||
}
|
||||
this.#currentConfig = this.#fillAccountConfig(config);
|
||||
this.#stopLoading();
|
||||
await this.#initUI(this.#states[this.#currentState].nextStep);
|
||||
this.#states.incomingConfigSubview.previousStep =
|
||||
"emailConfigFoundSubview";
|
||||
this.#states[this.#currentState].subview.showNotification({
|
||||
fluentTitleId: "account-hub-config-success",
|
||||
type: "success",
|
||||
});
|
||||
} catch (error) {
|
||||
this.#emailFooter.canBack(false);
|
||||
this.#stopLoading();
|
||||
if (!(error instanceof UserCancelledException)) {
|
||||
// TODO: Throw proper error here;
|
||||
throw error;
|
||||
|
@ -581,11 +636,11 @@ class AccountHubEmail extends HTMLElement {
|
|||
case "incomingConfigSubview":
|
||||
break;
|
||||
case "outgoingConfigSubview":
|
||||
this.#startLoading("account-hub-adding-account-subheader");
|
||||
stateData = this.#states[this.#currentState].subview.captureState();
|
||||
stateData.incoming =
|
||||
this.#states.incomingConfigSubview.subview.captureState().config.incoming;
|
||||
stateData = this.#fillAccountConfig(stateData);
|
||||
|
||||
try {
|
||||
const config = await this.#guessConfig(
|
||||
this.#email.split("@")[1],
|
||||
|
@ -593,17 +648,32 @@ class AccountHubEmail extends HTMLElement {
|
|||
);
|
||||
|
||||
if (config.isComplete()) {
|
||||
// TODO: Show success message here.
|
||||
this.#stopLoading();
|
||||
this.#states[this.#currentState].subview.showNotification({
|
||||
fluentTitleId: "account-hub-config-test-scucess",
|
||||
type: "success",
|
||||
});
|
||||
this.#emailFooter.toggleForwardDisabled(false);
|
||||
} else {
|
||||
this.#stopLoading();
|
||||
// The config is not complete, go back to the incoming view and
|
||||
// show an error.
|
||||
this.#initUI(this.#states[this.#currentState].previousStep);
|
||||
// TODO: Show error message here.
|
||||
this.#states[this.#currentState].subview.showNotification({
|
||||
fluentTitleId: "account-hub-find-settings-failed",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.#stopLoading();
|
||||
this.#initUI(this.#states[this.#currentState].previousStep);
|
||||
// TODO: Show error message here.
|
||||
this.#states[this.#currentState].subview.showNotification({
|
||||
fluentTitleId: "account-hub-find-settings-failed",
|
||||
error,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "emailAddedSubview":
|
||||
|
@ -705,9 +775,9 @@ class AccountHubEmail extends HTMLElement {
|
|||
gAccountSetupLogger.warn(`guessConfig failed: ${e}`);
|
||||
reject(e);
|
||||
|
||||
this.showNotification({
|
||||
title: "account-hub-find-settings-failed",
|
||||
e,
|
||||
this.#states[this.#currentState].subview.showNotification({
|
||||
fluentTitleId: "account-hub-find-settings-failed",
|
||||
error: e,
|
||||
type: "error",
|
||||
});
|
||||
this.abortable = null;
|
||||
|
@ -939,6 +1009,7 @@ class AccountHubEmail extends HTMLElement {
|
|||
return false;
|
||||
}
|
||||
|
||||
this.#stopLoading();
|
||||
this.#currentState = "autoConfigSubview";
|
||||
this.#currentConfig = {};
|
||||
this.#hideSubviews();
|
||||
|
|
|
@ -173,3 +173,5 @@ account-hub-password-info = Your credentials will only be stored locally on your
|
|||
account-hub-sync-success = Thunderbird found some connected services
|
||||
|
||||
account-hub-email-added-success = Email account connected successfully
|
||||
|
||||
account-hub-config-test-scucess = Configuration settings valid
|
||||
|
|
|
@ -35,6 +35,8 @@ dialog {
|
|||
--hub-account-footer-link-color: var(--color-primary-default);
|
||||
--hub-border-color: light-dark(var(--color-primary-soft), var(--color-primary-default));
|
||||
--hub-divider-color: var(--color-neutral-base);
|
||||
--hub-loader-background: var(--color-neutral-base);
|
||||
--hub-loader-color: var(--color-primary-default);
|
||||
|
||||
--hub-box-shadow: 0 2px 4px rgba(58, 57, 68, 0.3);
|
||||
--hub-input-height: 33px;
|
||||
|
@ -64,21 +66,20 @@ dialog {
|
|||
.account-hub-dialog {
|
||||
display: grid;
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
min-height: 600px;
|
||||
overflow: initial;
|
||||
padding: 0;
|
||||
box-shadow: none;
|
||||
|
||||
&::after {
|
||||
--hub-blur-radius: 15px;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 17%;
|
||||
left: 50%;
|
||||
width: 765px;
|
||||
height: 500px;
|
||||
transform: translateX(-50%);
|
||||
inset-block-end: -2px;
|
||||
inset-inline: calc(2 * var(--hub-blur-radius) + 5px);
|
||||
min-height: calc(3 * var(--hub-blur-radius));
|
||||
background: linear-gradient(to right, rgba(159, 244, 240, 1), rgba(76, 177, 249, 1), rgba(168, 85, 247, 1));
|
||||
filter: blur(15px);
|
||||
filter: blur(var(--hub-blur-radius));
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
|
@ -118,6 +119,106 @@ dialog {
|
|||
@media (prefers-color-scheme: dark) {
|
||||
background-image: url("chrome://messenger/skin/images/accounthub-bg-dark.webp");
|
||||
}
|
||||
|
||||
&.busy {
|
||||
cursor: wait;
|
||||
}
|
||||
|
||||
#loadingOverlay {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&.spinner #loadingOverlay {
|
||||
display: grid;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
place-items: center;
|
||||
background-color: var(--hub-loader-background);
|
||||
opacity: 0.8;
|
||||
z-index: 2;
|
||||
border-radius: inherit;
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
animation: 0.5s linear 0s hub-reveal-loader;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes hub-reveal-loader {
|
||||
0% {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
1% {
|
||||
display: grid;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes hub-loader-loading {
|
||||
from {
|
||||
rotate: 0deg;
|
||||
}
|
||||
to {
|
||||
rotate: 360deg;
|
||||
}
|
||||
}
|
||||
|
||||
.loader-outside {
|
||||
--hub-loader-width: 8px;
|
||||
--hub-loader-size: 64px;
|
||||
--hub-trail-offset: 360deg;
|
||||
|
||||
position: relative;
|
||||
height: var(--hub-loader-size);
|
||||
aspect-ratio: 1;
|
||||
background-image: conic-gradient(
|
||||
from 0deg,
|
||||
transparent 0deg,
|
||||
var(--hub-loader-color) 360deg,
|
||||
transparent var(--hub-trail-offset)
|
||||
);
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
animation: 1.1s cubic-bezier(0.61, 0.12, 0, 0.99) 0s infinite hub-loader-loading;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: var(--hub-loader-width);
|
||||
height: calc(var(--hub-loader-size) - 2 * var(--hub-loader-width));
|
||||
aspect-ratio: 1;
|
||||
background: var(--hub-loader-background);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.loader-inside {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: var(--hub-loader-width);
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
background: var(--hub-loader-color);
|
||||
inset-block-start: 0;
|
||||
inset-inline: calc(50% - var(--hub-loader-width) / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
|
|
Загрузка…
Ссылка в новой задаче