mirror of
https://github.com/bitwarden/browser
synced 2026-02-18 10:23:52 +00:00
[PM-31029] Add feature flag for milestone 2 (#18458)
* Add feature flag for milestone 2 * Fix test * Remove OnPush
This commit is contained in:
committed by
jaasen-livefront
parent
7533acb763
commit
2f862b31e1
@@ -1,25 +1,93 @@
|
||||
<!-- Header with Send title and New button -->
|
||||
<app-header>
|
||||
@if (!disableSend()) {
|
||||
<tools-new-send-dropdown-v2 buttonType="primary" (addSend)="addSend($event)" />
|
||||
}
|
||||
</app-header>
|
||||
<!-- Send List Component -->
|
||||
<tools-send-list
|
||||
[sends]="filteredSends()"
|
||||
[loading]="loading()"
|
||||
[disableSend]="disableSend()"
|
||||
[listState]="listState()"
|
||||
[searchText]="currentSearchText()"
|
||||
(editSend)="onEditSend($event)"
|
||||
(copySend)="onCopySend($event)"
|
||||
(deleteSend)="onDeleteSend($event)"
|
||||
(removePassword)="onRemovePassword($event)"
|
||||
>
|
||||
<tools-new-send-dropdown-v2
|
||||
slot="empty-button"
|
||||
[hideIcon]="true"
|
||||
buttonType="primary"
|
||||
(addSend)="addSend($event)"
|
||||
/>
|
||||
</tools-send-list>
|
||||
@if (useDrawerEditMode()) {
|
||||
<div class="tw-m-4 tw-p-4">
|
||||
<!-- New dialog-based layout (feature flag enabled) -->
|
||||
<!-- Header with Send title and New button -->
|
||||
<app-header>
|
||||
@if (!disableSend()) {
|
||||
<tools-new-send-dropdown-v2 buttonType="primary" (addSend)="addSend($event)" />
|
||||
}
|
||||
</app-header>
|
||||
<!-- Send List Component -->
|
||||
<tools-send-list
|
||||
[sends]="filteredSends()"
|
||||
[loading]="loading()"
|
||||
[disableSend]="disableSend()"
|
||||
[listState]="listState()"
|
||||
[searchText]="currentSearchText()"
|
||||
(editSend)="onEditSend($event)"
|
||||
(copySend)="onCopySend($event)"
|
||||
(deleteSend)="onDeleteSend($event)"
|
||||
(removePassword)="onRemovePassword($event)"
|
||||
>
|
||||
<tools-new-send-dropdown-v2
|
||||
slot="empty-button"
|
||||
[hideIcon]="true"
|
||||
buttonType="primary"
|
||||
(addSend)="addSend($event)"
|
||||
/>
|
||||
</tools-send-list>
|
||||
</div>
|
||||
} @else {
|
||||
<!-- Old split-panel layout (feature flag disabled) -->
|
||||
<div id="sends" class="vault">
|
||||
<div class="send-items-panel tw-w-2/5">
|
||||
<!-- Header with Send title and New button -->
|
||||
<app-header class="tw-block tw-pt-6 tw-px-6">
|
||||
@if (!disableSend()) {
|
||||
<button type="button" bitButton buttonType="primary" (click)="addSendWithoutType()">
|
||||
<i class="bwi bwi-plus" aria-hidden="true"></i>
|
||||
{{ "new" | i18n }}
|
||||
</button>
|
||||
}
|
||||
</app-header>
|
||||
<div class="tw-mb-4 tw-px-6">
|
||||
<!-- Send List Component -->
|
||||
<tools-send-list
|
||||
[sends]="filteredSends()"
|
||||
[loading]="loading()"
|
||||
[disableSend]="disableSend()"
|
||||
[listState]="listState()"
|
||||
[searchText]="currentSearchText()"
|
||||
(editSend)="onEditSend($event)"
|
||||
(copySend)="onCopySend($event)"
|
||||
(deleteSend)="onDeleteSend($event)"
|
||||
(removePassword)="onRemovePassword($event)"
|
||||
>
|
||||
<button
|
||||
slot="empty-button"
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
(click)="addSendWithoutType()"
|
||||
>
|
||||
{{ "newSend" | i18n }}
|
||||
</button>
|
||||
</tools-send-list>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit/Add panel (right side) -->
|
||||
@if (action() == "add" || action() == "edit") {
|
||||
<app-send-add-edit
|
||||
id="addEdit"
|
||||
class="details"
|
||||
[sendId]="sendId()"
|
||||
[type]="selectedSendType()"
|
||||
(onSavedSend)="savedSend($event)"
|
||||
(onCancelled)="closeEditPanel()"
|
||||
(onDeletedSend)="closeEditPanel()"
|
||||
></app-send-add-edit>
|
||||
}
|
||||
|
||||
<!-- Bitwarden logo (shown when no send is selected) -->
|
||||
@if (!action()) {
|
||||
<div class="logo tw-w-1/2">
|
||||
<div class="content">
|
||||
<div class="inner-content">
|
||||
<img class="logo-image" alt="Bitwarden" aria-hidden="true" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ describe("SendV2Component", () => {
|
||||
let sendApiService: MockProxy<SendApiService>;
|
||||
let toastService: MockProxy<ToastService>;
|
||||
let i18nService: MockProxy<I18nService>;
|
||||
let configService: MockProxy<ConfigService>;
|
||||
|
||||
beforeEach(async () => {
|
||||
sendService = mock<SendService>();
|
||||
@@ -62,6 +63,10 @@ describe("SendV2Component", () => {
|
||||
sendApiService = mock<SendApiService>();
|
||||
toastService = mock<ToastService>();
|
||||
i18nService = mock<I18nService>();
|
||||
configService = mock<ConfigService>();
|
||||
|
||||
// Setup configService mock - feature flag returns true to test the new drawer mode
|
||||
configService.getFeatureFlag$.mockReturnValue(of(true));
|
||||
|
||||
// Setup environmentService mock
|
||||
environmentService.getEnvironment.mockResolvedValue({
|
||||
@@ -117,7 +122,7 @@ describe("SendV2Component", () => {
|
||||
useValue: mock<BillingAccountProfileStateService>(),
|
||||
},
|
||||
{ provide: MessagingService, useValue: mock<MessagingService>() },
|
||||
{ provide: ConfigService, useValue: mock<ConfigService>() },
|
||||
{ provide: ConfigService, useValue: configService },
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
computed,
|
||||
effect,
|
||||
inject,
|
||||
signal,
|
||||
viewChild,
|
||||
} from "@angular/core";
|
||||
import { toSignal } from "@angular/core/rxjs-interop";
|
||||
import { combineLatest, map, switchMap, lastValueFrom } from "rxjs";
|
||||
@@ -15,6 +17,8 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
@@ -36,12 +40,27 @@ import {
|
||||
|
||||
import { DesktopPremiumUpgradePromptService } from "../../../services/desktop-premium-upgrade-prompt.service";
|
||||
import { DesktopHeaderComponent } from "../../layout/header";
|
||||
import { AddEditComponent } from "../send/add-edit.component";
|
||||
|
||||
const Action = Object.freeze({
|
||||
/** No action is currently active. */
|
||||
None: "",
|
||||
/** The user is adding a new Send. */
|
||||
Add: "add",
|
||||
/** The user is editing an existing Send. */
|
||||
Edit: "edit",
|
||||
} as const);
|
||||
|
||||
type Action = (typeof Action)[keyof typeof Action];
|
||||
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
|
||||
@Component({
|
||||
selector: "app-send-v2",
|
||||
imports: [
|
||||
JslibModule,
|
||||
ButtonModule,
|
||||
AddEditComponent,
|
||||
SendListComponent,
|
||||
NewSendDropdownV2Component,
|
||||
DesktopHeaderComponent,
|
||||
@@ -54,13 +73,19 @@ import { DesktopHeaderComponent } from "../../layout/header";
|
||||
},
|
||||
],
|
||||
templateUrl: "./send-v2.component.html",
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class SendV2Component {
|
||||
protected readonly addEditComponent = viewChild(AddEditComponent);
|
||||
|
||||
protected readonly sendId = signal<string | null>(null);
|
||||
protected readonly action = signal<Action>(Action.None);
|
||||
private readonly selectedSendTypeOverride = signal<SendType | undefined>(undefined);
|
||||
|
||||
private sendFormConfigService = inject(DefaultSendFormConfigService);
|
||||
private sendItemsService = inject(SendItemsService);
|
||||
private policyService = inject(PolicyService);
|
||||
private accountService = inject(AccountService);
|
||||
private configService = inject(ConfigService);
|
||||
private i18nService = inject(I18nService);
|
||||
private platformUtilsService = inject(PlatformUtilsService);
|
||||
private environmentService = inject(EnvironmentService);
|
||||
@@ -70,6 +95,11 @@ export class SendV2Component {
|
||||
private logService = inject(LogService);
|
||||
private cdr = inject(ChangeDetectorRef);
|
||||
|
||||
protected readonly useDrawerEditMode = toSignal(
|
||||
this.configService.getFeatureFlag$(FeatureFlag.DesktopUiMigrationMilestone2),
|
||||
{ initialValue: false },
|
||||
);
|
||||
|
||||
protected readonly filteredSends = toSignal(this.sendItemsService.filteredAndSortedSends$, {
|
||||
initialValue: [],
|
||||
});
|
||||
@@ -119,28 +149,79 @@ export class SendV2Component {
|
||||
});
|
||||
}
|
||||
|
||||
protected readonly selectedSendType = computed(() => {
|
||||
const action = this.action();
|
||||
const typeOverride = this.selectedSendTypeOverride();
|
||||
|
||||
if (action === Action.Add && typeOverride !== undefined) {
|
||||
return typeOverride;
|
||||
}
|
||||
|
||||
const sendId = this.sendId();
|
||||
return this.filteredSends().find((s) => s.id === sendId)?.type;
|
||||
});
|
||||
|
||||
protected async addSend(type: SendType): Promise<void> {
|
||||
const formConfig = await this.sendFormConfigService.buildConfig("add", undefined, type);
|
||||
if (this.useDrawerEditMode()) {
|
||||
const formConfig = await this.sendFormConfigService.buildConfig("add", undefined, type);
|
||||
|
||||
const dialogRef = SendAddEditDialogComponent.openDrawer(this.dialogService, {
|
||||
formConfig,
|
||||
});
|
||||
const dialogRef = SendAddEditDialogComponent.openDrawer(this.dialogService, {
|
||||
formConfig,
|
||||
});
|
||||
|
||||
await lastValueFrom(dialogRef.closed);
|
||||
await lastValueFrom(dialogRef.closed);
|
||||
} else {
|
||||
this.action.set(Action.Add);
|
||||
this.sendId.set(null);
|
||||
this.selectedSendTypeOverride.set(type);
|
||||
|
||||
const component = this.addEditComponent();
|
||||
if (component) {
|
||||
await component.resetAndLoad();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async selectSend(sendId: SendId): Promise<void> {
|
||||
const formConfig = await this.sendFormConfigService.buildConfig("edit", sendId);
|
||||
/** Used by old UI to add a send without specifying type (defaults to Text) */
|
||||
protected async addSendWithoutType(): Promise<void> {
|
||||
await this.addSend(SendType.Text);
|
||||
}
|
||||
|
||||
const dialogRef = SendAddEditDialogComponent.openDrawer(this.dialogService, {
|
||||
formConfig,
|
||||
});
|
||||
protected closeEditPanel(): void {
|
||||
this.action.set(Action.None);
|
||||
this.sendId.set(null);
|
||||
this.selectedSendTypeOverride.set(undefined);
|
||||
}
|
||||
|
||||
await lastValueFrom(dialogRef.closed);
|
||||
protected async savedSend(send: SendView): Promise<void> {
|
||||
await this.selectSend(send.id);
|
||||
}
|
||||
|
||||
protected async selectSend(sendId: string): Promise<void> {
|
||||
if (this.useDrawerEditMode()) {
|
||||
const formConfig = await this.sendFormConfigService.buildConfig("edit", sendId as SendId);
|
||||
|
||||
const dialogRef = SendAddEditDialogComponent.openDrawer(this.dialogService, {
|
||||
formConfig,
|
||||
});
|
||||
|
||||
await lastValueFrom(dialogRef.closed);
|
||||
} else {
|
||||
if (sendId === this.sendId() && this.action() === Action.Edit) {
|
||||
return;
|
||||
}
|
||||
this.action.set(Action.Edit);
|
||||
this.sendId.set(sendId);
|
||||
const component = this.addEditComponent();
|
||||
if (component) {
|
||||
component.sendId = sendId;
|
||||
await component.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async onEditSend(send: SendView): Promise<void> {
|
||||
await this.selectSend(send.id as SendId);
|
||||
await this.selectSend(send.id);
|
||||
}
|
||||
|
||||
protected async onCopySend(send: SendView): Promise<void> {
|
||||
@@ -176,6 +257,11 @@ export class SendV2Component {
|
||||
title: null,
|
||||
message: this.i18nService.t("removedPassword"),
|
||||
});
|
||||
|
||||
if (!this.useDrawerEditMode() && this.sendId() === send.id) {
|
||||
this.sendId.set(null);
|
||||
await this.selectSend(send.id);
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
@@ -199,5 +285,9 @@ export class SendV2Component {
|
||||
title: null,
|
||||
message: this.i18nService.t("deletedSend"),
|
||||
});
|
||||
|
||||
if (!this.useDrawerEditMode()) {
|
||||
this.closeEditPanel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
29
apps/desktop/src/scss/migration.scss
Normal file
29
apps/desktop/src/scss/migration.scss
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Desktop UI Migration
|
||||
*
|
||||
* These are temporary styles during the desktop ui migration.
|
||||
**/
|
||||
|
||||
/**
|
||||
* This removes any padding applied by the bit-layout to content.
|
||||
* This should be revisited once the table is migrated, and again once drawers are migrated.
|
||||
**/
|
||||
bit-layout {
|
||||
#main-content {
|
||||
padding: 0 0 0 0;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Send list panel styling for send-v2 component
|
||||
* Temporary during migration - width handled by tw-w-2/5
|
||||
**/
|
||||
.vault > .send-items-panel {
|
||||
order: 2;
|
||||
min-width: 200px;
|
||||
border-right: 1px solid;
|
||||
|
||||
@include themify($themes) {
|
||||
background-color: themed("backgroundColor");
|
||||
border-right-color: themed("borderColor");
|
||||
}
|
||||
}
|
||||
@@ -15,5 +15,6 @@
|
||||
@import "left-nav.scss";
|
||||
@import "loading.scss";
|
||||
@import "plugins.scss";
|
||||
@import "migration.scss";
|
||||
@import "../../../../libs/angular/src/scss/icons.scss";
|
||||
@import "../../../../libs/components/src/multi-select/scss/bw.theme";
|
||||
|
||||
@@ -76,6 +76,7 @@ export enum FeatureFlag {
|
||||
|
||||
/* Desktop */
|
||||
DesktopUiMigrationMilestone1 = "desktop-ui-migration-milestone-1",
|
||||
DesktopUiMigrationMilestone2 = "desktop-ui-migration-milestone-2",
|
||||
|
||||
/* UIF */
|
||||
RouterFocusManagement = "router-focus-management",
|
||||
@@ -164,6 +165,7 @@ export const DefaultFeatureFlagValue = {
|
||||
|
||||
/* Desktop */
|
||||
[FeatureFlag.DesktopUiMigrationMilestone1]: FALSE,
|
||||
[FeatureFlag.DesktopUiMigrationMilestone2]: FALSE,
|
||||
|
||||
/* UIF */
|
||||
[FeatureFlag.RouterFocusManagement]: FALSE,
|
||||
|
||||
Reference in New Issue
Block a user