mirror of
https://github.com/bitwarden/browser
synced 2026-01-27 14:53:44 +00:00
autofill info dialog
This commit is contained in:
@@ -3765,6 +3765,12 @@
|
||||
"autofillSelectInfoWithoutCommand": {
|
||||
"message": "Select an item from this screen, or explore other options in settings."
|
||||
},
|
||||
"simplifiedAutofill": {
|
||||
"message": "Simplified autofill"
|
||||
},
|
||||
"simplifiedAutofillDesc": {
|
||||
"message": "Now, when you click a suggested autofill item, it fills rather than taking you to details. You can still view these items from the More menu."
|
||||
},
|
||||
"gotIt": {
|
||||
"message": "Got it"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<bit-dialog [title]="'simplifiedAutofill' | i18n">
|
||||
<div bitDialogContent>
|
||||
<p bitTypography="body2">
|
||||
{{ "autofillSelectInfoWithoutCommand" | i18n }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ng-container bitDialogFooter>
|
||||
<button type="button" bitButton buttonType="primary" (click)="close()">
|
||||
{{ "gotIt" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
@@ -0,0 +1,18 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { ChangeDetectionStrategy, Component, inject } from "@angular/core";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { ButtonModule, DialogModule, DialogRef, TypographyModule } from "@bitwarden/components";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./autofill-suggestions-info-dialog.component.html",
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [CommonModule, DialogModule, TypographyModule, ButtonModule, JslibModule],
|
||||
})
|
||||
export class AutofillSuggestionsInfoDialogComponent {
|
||||
private dialogRef = inject(DialogRef<void>);
|
||||
|
||||
close() {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,10 @@
|
||||
[showRefresh]="showRefresh"
|
||||
(onRefresh)="refreshCurrentTab()"
|
||||
[description]="(showEmptyAutofillTip$ | async) ? ('autofillSuggestionsTip' | i18n) : undefined"
|
||||
[showInfoIcon]="showInfoIcon$ | async"
|
||||
[pingInfoIcon]="pingInfoIcon()"
|
||||
(onInfoIconClick)="openAutofillSuggestionsInfo()"
|
||||
(onInfoIconDismissed)="dismissAutofillSuggestionsInfo()"
|
||||
showAutofillButton
|
||||
[disableDescriptionMargin]="showEmptyAutofillTip$ | async"
|
||||
[primaryActionAutofill]="clickItemsToAutofillVaultView$ | async"
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
import { toSignal } from "@angular/core/rxjs-interop";
|
||||
import { combineLatest, map, Observable, startWith } from "rxjs";
|
||||
import { Component, DestroyRef, OnInit, inject, signal } from "@angular/core";
|
||||
import { takeUntilDestroyed, toSignal } from "@angular/core/rxjs-interop";
|
||||
import { combineLatest, filter, from, map, Observable, startWith, switchMap, take } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherViewLikeUtils } from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
||||
import { IconButtonModule, TypographyModule } from "@bitwarden/components";
|
||||
|
||||
import BrowserPopupUtils from "../../../../../platform/browser/browser-popup-utils";
|
||||
import { VaultPopupAutofillSuggestionsInfoService } from "../../../services/vault-popup-autofill-suggestions-info.service";
|
||||
import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service";
|
||||
import { VaultPopupItemsService } from "../../../services/vault-popup-items.service";
|
||||
import { PopupCipherViewLike } from "../../../views/popup-cipher.view";
|
||||
@@ -28,7 +31,10 @@ import { VaultListItemsContainerComponent } from "../vault-list-items-container/
|
||||
selector: "app-autofill-vault-list-items",
|
||||
templateUrl: "autofill-vault-list-items.component.html",
|
||||
})
|
||||
export class AutofillVaultListItemsComponent {
|
||||
export class AutofillVaultListItemsComponent implements OnInit {
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly activeUserId$ = this.accountService.activeAccount$.pipe(getUserId);
|
||||
|
||||
/**
|
||||
* The list of ciphers that can be used to autofill the current page.
|
||||
* @protected
|
||||
@@ -48,6 +54,35 @@ export class AutofillVaultListItemsComponent {
|
||||
startWith(true), // Start with true to avoid flashing the fill button on first load
|
||||
);
|
||||
|
||||
/** When true, the info icon should ping (4 iterations). */
|
||||
protected readonly pingInfoIcon = signal(false);
|
||||
|
||||
/** Flag indicating that the current tab location is blocked */
|
||||
protected readonly currentURIIsBlocked$: Observable<boolean> =
|
||||
this.vaultPopupAutofillService.currentTabIsOnBlocklist$;
|
||||
|
||||
/** Computed state for the per-user info icon (dismissed/ping completed). */
|
||||
private readonly infoState$ = this.activeUserId$.pipe(
|
||||
switchMap((userId) => this.autofillSuggestionsInfoService.state$(userId)),
|
||||
);
|
||||
|
||||
/**
|
||||
* Show the info icon only when:
|
||||
* - the current page is not blocked
|
||||
* - the autofill suggestions list is populated
|
||||
* - the user hasn't dismissed the info dialog
|
||||
*/
|
||||
protected readonly showInfoIcon$ = combineLatest([
|
||||
this.autofillCiphers$,
|
||||
this.currentURIIsBlocked$,
|
||||
this.infoState$,
|
||||
]).pipe(
|
||||
map(([ciphers, isBlocked, infoState]) => {
|
||||
const hasItems = (ciphers ?? []).length > 0;
|
||||
return !isBlocked && hasItems && !(infoState.dismissed ?? false);
|
||||
}),
|
||||
);
|
||||
|
||||
protected readonly groupByType = toSignal(
|
||||
this.vaultPopupItemsService.hasFilterApplied$.pipe(map((hasFilter) => !hasFilter)),
|
||||
);
|
||||
@@ -71,18 +106,61 @@ export class AutofillVaultListItemsComponent {
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Flag indicating that the current tab location is blocked
|
||||
*/
|
||||
currentURIIsBlocked$: Observable<boolean> =
|
||||
this.vaultPopupAutofillService.currentTabIsOnBlocklist$;
|
||||
|
||||
constructor(
|
||||
private vaultPopupItemsService: VaultPopupItemsService,
|
||||
private vaultPopupAutofillService: VaultPopupAutofillService,
|
||||
private vaultSettingsService: VaultSettingsService,
|
||||
private accountService: AccountService,
|
||||
private autofillSuggestionsInfoService: VaultPopupAutofillSuggestionsInfoService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
// Start the ping animation once (4 iterations) the first time suggestions become populated.
|
||||
combineLatest([
|
||||
this.activeUserId$,
|
||||
this.autofillCiphers$,
|
||||
this.currentURIIsBlocked$,
|
||||
this.infoState$,
|
||||
])
|
||||
.pipe(
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
filter(([_userId, ciphers, isBlocked, infoState]) => {
|
||||
const hasItems = (ciphers ?? []).length > 0;
|
||||
return (
|
||||
!isBlocked &&
|
||||
hasItems &&
|
||||
!(infoState.dismissed ?? false) &&
|
||||
!(infoState.pingCompleted ?? false)
|
||||
);
|
||||
}),
|
||||
take(1),
|
||||
)
|
||||
.subscribe(([userId]) => {
|
||||
// Mark completed immediately so it never replays across vault opens.
|
||||
void this.autofillSuggestionsInfoService.markPingCompleted(userId);
|
||||
|
||||
this.pingInfoIcon.set(true);
|
||||
window.setTimeout(() => this.pingInfoIcon.set(false), 4000);
|
||||
});
|
||||
}
|
||||
|
||||
protected openAutofillSuggestionsInfo(): void {
|
||||
// Keep the icon visible until the user dismisses the popover, but stop any ping immediately.
|
||||
this.pingInfoIcon.set(false);
|
||||
}
|
||||
|
||||
protected dismissAutofillSuggestionsInfo(): void {
|
||||
this.pingInfoIcon.set(false);
|
||||
|
||||
this.activeUserId$
|
||||
.pipe(
|
||||
take(1),
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
switchMap((userId) => from(this.autofillSuggestionsInfoService.markDismissed(userId))),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the current tab to re-populate the autofill ciphers.
|
||||
* @protected
|
||||
|
||||
@@ -33,9 +33,52 @@
|
||||
|
||||
<ng-template #sectionHeader>
|
||||
<bit-section-header class="tw-p-0.5 -tw-mx-0.5">
|
||||
<h2 bitTypography="h6">
|
||||
{{ title() }}
|
||||
</h2>
|
||||
<div class="tw-flex tw-items-center tw-gap-2">
|
||||
<h2 bitTypography="h6" class="!tw-mb-0">
|
||||
{{ title() }}
|
||||
</h2>
|
||||
|
||||
@if (showInfoIcon()) {
|
||||
<ng-container>
|
||||
<button
|
||||
type="button"
|
||||
class="tw-relative tw-inline-flex tw-items-center tw-justify-center tw-w-6 tw-h-6 tw-rounded-md hover:tw-bg-secondary-100 focus-visible:tw-outline-none focus-visible:tw-ring-inset focus-visible:tw-ring-2 focus-visible:tw-ring-primary-600"
|
||||
[bitPopoverTriggerFor]="autofillSuggestionsInfoPopover"
|
||||
[position]="'below-center'"
|
||||
#autofillSuggestionsInfoTrigger="popoverTrigger"
|
||||
(click)="$event.stopPropagation(); onInfoIconClick.emit()"
|
||||
[attr.aria-label]="'learnMore' | i18n"
|
||||
[title]="'learnMore' | i18n"
|
||||
>
|
||||
@if (pingInfoIcon()) {
|
||||
<span
|
||||
class="tw-absolute tw-inline-flex tw-h-6 tw-w-6 tw-rounded-full tw-bg-primary-600/20 tw-animate-ping tw-[animation-iteration-count:4]"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
}
|
||||
<i class="bwi bwi-info-circle tw-text-muted" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
||||
<bit-popover
|
||||
[title]="'simplifiedAutofill' | i18n"
|
||||
#autofillSuggestionsInfoPopover
|
||||
(closed)="onInfoIconDismissed.emit()"
|
||||
>
|
||||
<p class="tw-mb-0">{{ "simplifiedAutofillDesc" | i18n }}</p>
|
||||
<div class="tw-mt-4 tw-flex tw-justify-start">
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
(click)="autofillSuggestionsInfoTrigger.closePopover(); onInfoIconDismissed.emit()"
|
||||
>
|
||||
{{ "gotIt" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</bit-popover>
|
||||
</ng-container>
|
||||
}
|
||||
</div>
|
||||
<button
|
||||
*ngIf="showRefresh()"
|
||||
bitIconButton="bwi-refresh"
|
||||
|
||||
@@ -4,15 +4,14 @@ import {
|
||||
AfterViewInit,
|
||||
booleanAttribute,
|
||||
Component,
|
||||
EventEmitter,
|
||||
inject,
|
||||
Output,
|
||||
Signal,
|
||||
signal,
|
||||
ViewChild,
|
||||
computed,
|
||||
ChangeDetectionStrategy,
|
||||
input,
|
||||
output,
|
||||
} from "@angular/core";
|
||||
import { toSignal } from "@angular/core/rxjs-interop";
|
||||
import { Router } from "@angular/router";
|
||||
@@ -40,6 +39,7 @@ import {
|
||||
DialogService,
|
||||
IconButtonModule,
|
||||
ItemModule,
|
||||
PopoverModule,
|
||||
SectionComponent,
|
||||
SectionHeaderComponent,
|
||||
TypographyModule,
|
||||
@@ -80,6 +80,7 @@ import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options
|
||||
DisclosureComponent,
|
||||
DisclosureTriggerForDirective,
|
||||
ScrollLayoutDirective,
|
||||
PopoverModule,
|
||||
],
|
||||
selector: "app-vault-list-items-container",
|
||||
templateUrl: "vault-list-items-container.component.html",
|
||||
@@ -100,9 +101,7 @@ export class VaultListItemsContainerComponent implements AfterViewInit {
|
||||
/**
|
||||
* Indicates whether the section should be open or closed if collapsibleKey is provided
|
||||
*/
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
protected sectionOpenState: Signal<boolean> = computed(() => {
|
||||
protected readonly sectionOpenState: Signal<boolean> = computed(() => {
|
||||
if (!this.collapsibleKey()) {
|
||||
return true;
|
||||
}
|
||||
@@ -136,24 +135,18 @@ export class VaultListItemsContainerComponent implements AfterViewInit {
|
||||
*/
|
||||
private viewCipherTimeout?: number;
|
||||
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
ciphers = input<PopupCipherViewLike[]>([]);
|
||||
readonly ciphers = input<PopupCipherViewLike[]>([]);
|
||||
|
||||
/**
|
||||
* If true, we will group ciphers by type (Login, Card, Identity)
|
||||
* within subheadings in a single container, converted to a WritableSignal.
|
||||
*/
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
groupByType = input<boolean | undefined>(false);
|
||||
readonly groupByType = input<boolean | undefined>(false);
|
||||
|
||||
/**
|
||||
* Computed signal for a grouped list of ciphers with an optional header
|
||||
*/
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
cipherGroups = computed<
|
||||
readonly cipherGroups = computed<
|
||||
{
|
||||
subHeaderKey?: string;
|
||||
ciphers: PopupCipherViewLike[];
|
||||
@@ -195,9 +188,7 @@ export class VaultListItemsContainerComponent implements AfterViewInit {
|
||||
/**
|
||||
* Title for the vault list item section.
|
||||
*/
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
title = input<string | undefined>(undefined);
|
||||
readonly title = input<string | undefined>(undefined);
|
||||
|
||||
/**
|
||||
* Optionally allow the items to be collapsed.
|
||||
@@ -205,46 +196,53 @@ export class VaultListItemsContainerComponent implements AfterViewInit {
|
||||
* The key must be added to the state definition in `vault-popup-section.service.ts` since the
|
||||
* collapsed state is stored locally.
|
||||
*/
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
collapsibleKey = input<keyof PopupSectionOpen | undefined>(undefined);
|
||||
readonly collapsibleKey = input<keyof PopupSectionOpen | undefined>(undefined);
|
||||
|
||||
/**
|
||||
* Optional description for the vault list item section. Will be shown below the title even when
|
||||
* no ciphers are available.
|
||||
*/
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
description = input<string | undefined>(undefined);
|
||||
readonly description = input<string | undefined>(undefined);
|
||||
|
||||
/**
|
||||
* Option to show a refresh button in the section header.
|
||||
*/
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
showRefresh = input(false, { transform: booleanAttribute });
|
||||
readonly showRefresh = input(false, { transform: booleanAttribute });
|
||||
|
||||
/**
|
||||
* Option to show an info icon next to the section title.
|
||||
*/
|
||||
readonly showInfoIcon = input(false, { transform: booleanAttribute });
|
||||
|
||||
/**
|
||||
* When true, applies a Tailwind ping animation behind the info icon.
|
||||
*/
|
||||
readonly pingInfoIcon = input(false, { transform: booleanAttribute });
|
||||
|
||||
/**
|
||||
* Event emitted when the refresh button is clicked.
|
||||
*/
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref
|
||||
@Output()
|
||||
onRefresh = new EventEmitter<void>();
|
||||
readonly onRefresh = output();
|
||||
|
||||
/**
|
||||
* Event emitted when the header info icon is clicked.
|
||||
*/
|
||||
readonly onInfoIconClick = output();
|
||||
|
||||
/**
|
||||
* Event emitted when the header info popover is dismissed.
|
||||
*/
|
||||
readonly onInfoIconDismissed = output();
|
||||
|
||||
/**
|
||||
* Flag indicating that the current tab location is blocked
|
||||
*/
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
currentURIIsBlocked = toSignal(this.vaultPopupAutofillService.currentTabIsOnBlocklist$);
|
||||
readonly currentURIIsBlocked = toSignal(this.vaultPopupAutofillService.currentTabIsOnBlocklist$);
|
||||
|
||||
/**
|
||||
* Resolved i18n key to use for suggested cipher items
|
||||
*/
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
cipherItemTitleKey = computed(() => {
|
||||
readonly cipherItemTitleKey = computed(() => {
|
||||
return (cipher: CipherViewLike) => {
|
||||
const login = CipherViewLikeUtils.getLogin(cipher);
|
||||
const hasUsername = login?.username != null;
|
||||
@@ -259,47 +257,37 @@ export class VaultListItemsContainerComponent implements AfterViewInit {
|
||||
/**
|
||||
* Option to show the autofill button for each item.
|
||||
*/
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
showAutofillButton = input(false, { transform: booleanAttribute });
|
||||
readonly showAutofillButton = input(false, { transform: booleanAttribute });
|
||||
|
||||
/**
|
||||
* Flag indicating whether the suggested cipher item autofill button should be shown or not
|
||||
*/
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
hideAutofillButton = computed(
|
||||
readonly hideAutofillButton = computed(
|
||||
() => !this.showAutofillButton() || this.currentURIIsBlocked() || this.primaryActionAutofill(),
|
||||
);
|
||||
|
||||
/**
|
||||
* Flag indicating whether the cipher item autofill menu options should be shown or not
|
||||
*/
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
hideAutofillMenuOptions = computed(() => this.currentURIIsBlocked() || this.showAutofillButton());
|
||||
readonly hideAutofillMenuOptions = computed(
|
||||
() => this.currentURIIsBlocked() || this.showAutofillButton(),
|
||||
);
|
||||
|
||||
/**
|
||||
* Option to perform autofill operation as the primary action for autofill suggestions.
|
||||
*/
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
primaryActionAutofill = input(false, { transform: booleanAttribute });
|
||||
readonly primaryActionAutofill = input(false, { transform: booleanAttribute });
|
||||
|
||||
/**
|
||||
* Remove the bottom margin from the bit-section in this component
|
||||
* (used for containers at the end of the page where bottom margin is not needed)
|
||||
*/
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
disableSectionMargin = input(false, { transform: booleanAttribute });
|
||||
readonly disableSectionMargin = input(false, { transform: booleanAttribute });
|
||||
|
||||
/**
|
||||
* Remove the description margin
|
||||
*/
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
disableDescriptionMargin = input(false, { transform: booleanAttribute });
|
||||
readonly disableDescriptionMargin = input(false, { transform: booleanAttribute });
|
||||
|
||||
/**
|
||||
* The tooltip text for the organization icon for ciphers that belong to an organization.
|
||||
@@ -313,9 +301,7 @@ export class VaultListItemsContainerComponent implements AfterViewInit {
|
||||
return collections[0]?.name;
|
||||
}
|
||||
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
protected autofillShortcutTooltip = signal<string | undefined>(undefined);
|
||||
readonly autofillShortcutTooltip = signal<string | undefined>(undefined);
|
||||
|
||||
constructor(
|
||||
private i18nService: I18nService,
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { map, Observable } from "rxjs";
|
||||
|
||||
import {
|
||||
StateProvider,
|
||||
UserKeyDefinition,
|
||||
VAULT_SETTINGS_DISK,
|
||||
} from "@bitwarden/common/platform/state";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
export type AutofillSuggestionsInfoState = {
|
||||
dismissed?: boolean;
|
||||
pingCompleted?: boolean;
|
||||
};
|
||||
|
||||
const DEFAULT_STATE: AutofillSuggestionsInfoState = {
|
||||
dismissed: false,
|
||||
pingCompleted: false,
|
||||
};
|
||||
|
||||
const AUTOFILL_SUGGESTIONS_INFO_KEY = new UserKeyDefinition<AutofillSuggestionsInfoState>(
|
||||
VAULT_SETTINGS_DISK,
|
||||
"autofillSuggestionsInfo",
|
||||
{
|
||||
deserializer: (obj) => obj,
|
||||
clearOn: [],
|
||||
},
|
||||
);
|
||||
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
})
|
||||
export class VaultPopupAutofillSuggestionsInfoService {
|
||||
constructor(private stateProvider: StateProvider) {}
|
||||
|
||||
private stateForUser(userId: UserId) {
|
||||
return this.stateProvider.getUser(userId, AUTOFILL_SUGGESTIONS_INFO_KEY);
|
||||
}
|
||||
|
||||
state$(userId: UserId): Observable<AutofillSuggestionsInfoState> {
|
||||
return this.stateForUser(userId).state$.pipe(map((state) => state ?? DEFAULT_STATE));
|
||||
}
|
||||
|
||||
async markPingCompleted(userId: UserId): Promise<void> {
|
||||
await this.stateForUser(userId).update((current) => ({
|
||||
...(current ?? DEFAULT_STATE),
|
||||
pingCompleted: true,
|
||||
}));
|
||||
}
|
||||
|
||||
async markDismissed(userId: UserId): Promise<void> {
|
||||
await this.stateForUser(userId).update((current) => ({
|
||||
...(current ?? DEFAULT_STATE),
|
||||
dismissed: true,
|
||||
}));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user