From 15c301d39f94b71b08a6310c88b036f51609d4d9 Mon Sep 17 00:00:00 2001
From: Jonathan Prusik
Date: Thu, 24 Oct 2024 10:15:24 -0400
Subject: [PATCH] Do not redirect after saving changes to excluded domains
(#11676)
---
.../settings/excluded-domains.component.html | 10 ++-
.../settings/excluded-domains.component.ts | 81 ++++++++++++-------
2 files changed, 61 insertions(+), 30 deletions(-)
diff --git a/apps/browser/src/autofill/popup/settings/excluded-domains.component.html b/apps/browser/src/autofill/popup/settings/excluded-domains.component.html
index 1dec438fdd..e3b6bf5f80 100644
--- a/apps/browser/src/autofill/popup/settings/excluded-domains.component.html
+++ b/apps/browser/src/autofill/popup/settings/excluded-domains.component.html
@@ -11,7 +11,7 @@
accountSwitcherEnabled ? ("excludedDomainsDescAlt" | i18n) : ("excludedDomainsDesc" | i18n)
}}
-
+
{{ "domainsTitle" | i18n }}
{{ excludedDomainsState?.length || 0 }}
@@ -57,7 +57,13 @@
-
diff --git a/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts b/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts
index 381ba90342..f622312ce0 100644
--- a/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts
+++ b/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts
@@ -1,13 +1,19 @@
import { CommonModule } from "@angular/common";
-import { QueryList, Component, ElementRef, OnDestroy, OnInit, ViewChildren } from "@angular/core";
+import {
+ QueryList,
+ Component,
+ ElementRef,
+ OnDestroy,
+ AfterViewInit,
+ ViewChildren,
+} from "@angular/core";
import { FormsModule } from "@angular/forms";
-import { Router, RouterModule } from "@angular/router";
-import { firstValueFrom, Subject, takeUntil } from "rxjs";
+import { RouterModule } from "@angular/router";
+import { Subject, takeUntil } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
import { NeverDomains } from "@bitwarden/common/models/domain/domain-service";
-import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
@@ -29,8 +35,6 @@ import { PopupFooterComponent } from "../../../platform/popup/layout/popup-foote
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
-const BroadcasterSubscriptionId = "excludedDomainsState";
-
@Component({
selector: "app-excluded-domains",
templateUrl: "excluded-domains.component.html",
@@ -55,11 +59,12 @@ const BroadcasterSubscriptionId = "excludedDomainsState";
TypographyModule,
],
})
-export class ExcludedDomainsComponent implements OnInit, OnDestroy {
+export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy {
@ViewChildren("uriInput") uriInputElements: QueryList>;
accountSwitcherEnabled = false;
dataIsPristine = true;
+ isLoading = false;
excludedDomainsState: string[] = [];
storedExcludedDomains: string[] = [];
// How many fields should be non-editable before editable fields
@@ -70,16 +75,27 @@ export class ExcludedDomainsComponent implements OnInit, OnDestroy {
constructor(
private domainSettingsService: DomainSettingsService,
private i18nService: I18nService,
- private router: Router,
- private broadcasterService: BroadcasterService,
private platformUtilsService: PlatformUtilsService,
) {
this.accountSwitcherEnabled = enableAccountSwitching();
}
- async ngOnInit() {
- const neverDomains = await firstValueFrom(this.domainSettingsService.neverDomains$);
+ async ngAfterViewInit() {
+ this.domainSettingsService.neverDomains$
+ .pipe(takeUntil(this.destroy$))
+ .subscribe((neverDomains: NeverDomains) => this.handleStateUpdate(neverDomains));
+ this.uriInputElements.changes.pipe(takeUntil(this.destroy$)).subscribe(({ last }) => {
+ this.focusNewUriInput(last);
+ });
+ }
+
+ ngOnDestroy() {
+ this.destroy$.next();
+ this.destroy$.complete();
+ }
+
+ handleStateUpdate(neverDomains: NeverDomains) {
if (neverDomains) {
this.storedExcludedDomains = Object.keys(neverDomains);
}
@@ -89,15 +105,8 @@ export class ExcludedDomainsComponent implements OnInit, OnDestroy {
// Do not allow the first x (pre-existing) fields to be edited
this.fieldsEditThreshold = this.storedExcludedDomains.length;
- this.uriInputElements.changes.pipe(takeUntil(this.destroy$)).subscribe(({ last }) => {
- this.focusNewUriInput(last);
- });
- }
-
- ngOnDestroy() {
- this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
- this.destroy$.next();
- this.destroy$.complete();
+ this.dataIsPristine = true;
+ this.isLoading = false;
}
focusNewUriInput(elementRef: ElementRef) {
@@ -116,7 +125,7 @@ export class ExcludedDomainsComponent implements OnInit, OnDestroy {
async removeDomain(i: number) {
this.excludedDomainsState.splice(i, 1);
- // if a pre-existing field was dropped, lower the edit threshold
+ // If a pre-existing field was dropped, lower the edit threshold
if (i < this.fieldsEditThreshold) {
this.fieldsEditThreshold--;
}
@@ -132,11 +141,11 @@ export class ExcludedDomainsComponent implements OnInit, OnDestroy {
async saveChanges() {
if (this.dataIsPristine) {
- await this.router.navigate(["/notifications"]);
-
return;
}
+ this.isLoading = true;
+
const newExcludedDomainsSaveState: NeverDomains = {};
const uniqueExcludedDomains = new Set(this.excludedDomainsState);
@@ -151,6 +160,8 @@ export class ExcludedDomainsComponent implements OnInit, OnDestroy {
this.i18nService.t("excludedDomainsInvalidDomain", uri),
);
+ // Don't reset via `handleStateUpdate` to allow existing input value correction
+ this.isLoading = false;
return;
}
@@ -159,7 +170,23 @@ export class ExcludedDomainsComponent implements OnInit, OnDestroy {
}
try {
- await this.domainSettingsService.setNeverDomains(newExcludedDomainsSaveState);
+ const existingState = new Set(this.storedExcludedDomains);
+ const newState = new Set(Object.keys(newExcludedDomainsSaveState));
+ const stateIsUnchanged =
+ existingState.size === newState.size &&
+ new Set([...existingState, ...newState]).size === existingState.size;
+
+ // The subscriber updates don't trigger if `setNeverDomains` sets an equivalent state
+ if (stateIsUnchanged) {
+ // Reset UI state directly
+ const constructedNeverDomainsState = this.storedExcludedDomains.reduce(
+ (neverDomains, uri) => ({ ...neverDomains, [uri]: null }),
+ {},
+ );
+ this.handleStateUpdate(constructedNeverDomainsState);
+ } else {
+ await this.domainSettingsService.setNeverDomains(newExcludedDomainsSaveState);
+ }
this.platformUtilsService.showToast(
"success",
@@ -169,11 +196,9 @@ export class ExcludedDomainsComponent implements OnInit, OnDestroy {
} catch {
this.platformUtilsService.showToast("error", null, this.i18nService.t("unexpectedError"));
- // Do not navigate on error
- return;
+ // Don't reset via `handleStateUpdate` to preserve input values
+ this.isLoading = false;
}
-
- await this.router.navigate(["/notifications"]);
}
trackByFunction(index: number, _: string) {