1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-11 05:53:42 +00:00

disable all remaining form fields for editing personally owned My Items

This commit is contained in:
jaasen-livefront
2025-08-11 16:04:06 -07:00
parent f4d3f36c56
commit 44a6393e25
10 changed files with 73 additions and 13 deletions

View File

@@ -16,9 +16,15 @@
[submitBtn]="submitBtn"
(formReady)="onFormReady()"
(cipherSaved)="onCipherSaved($event)"
(onFormStatusChange)="formStatusChanged($event)"
>
<bit-item slot="attachment-button">
<button bit-item-content type="button" (click)="openAttachmentsDialog()">
<button
[disabled]="isAttachmentsButtonDisabled"
bit-item-content
type="button"
(click)="openAttachmentsDialog()"
>
<p class="tw-m-0">
{{ "attachments" | i18n }}
<span

View File

@@ -273,6 +273,8 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
protected canDelete = false;
protected isAttachmentsButtonDisabled = false;
constructor(
@Inject(DIALOG_DATA) protected params: VaultItemDialogParams,
private dialogRef: DialogRef<VaultItemDialogResult>,
@@ -341,6 +343,10 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
}
}
formStatusChanged(status: "disabled" | "enabled") {
this.isAttachmentsButtonDisabled = status === "disabled";
}
/**
* Called by the CipherFormComponent when the cipher is saved successfully.
* @param cipherView - The newly saved cipher.

View File

@@ -78,8 +78,8 @@ export abstract class CipherFormContainer {
abstract enableFormFields(): void;
/**
* An observable that emits when the form status changes to enabled.
* This can be used to disable child forms when the parent form is enabled.
* An observable that emits when the form status changes between enabled/disabled.
* This can be used for child forms to react to changes in the form status.
*/
formEnabled$: Observable<void>;
formStatusChange$: Observable<"enabled" | "disabled">;
}

View File

@@ -24,7 +24,7 @@
bitLink
type="button"
linkType="primary"
*ngIf="!hasCustomFields && !isPartialEdit"
*ngIf="!hasCustomFields && !isPartialEdit && allowNewField"
(click)="addCustomField()"
>
<i class="bwi bwi-plus tw-font-bold" aria-hidden="true"></i>

View File

@@ -1,7 +1,15 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { CommonModule } from "@angular/common";
import { ChangeDetectorRef, Component, Input, OnInit, ViewChild } from "@angular/core";
import {
ChangeDetectorRef,
Component,
DestroyRef,
inject,
Input,
OnInit,
ViewChild,
} from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
import { shareReplay } from "rxjs";
@@ -56,8 +64,13 @@ export class AdditionalOptionsSectionComponent implements OnInit {
/** True when the form is in `partial-edit` mode */
isPartialEdit = false;
/** True when the form allows new fields to be added */
allowNewField = true;
@Input() disableSectionMargin: boolean;
private destroyRef = inject(DestroyRef);
constructor(
private cipherFormContainer: CipherFormContainer,
private formBuilder: FormBuilder,
@@ -89,6 +102,17 @@ export class AdditionalOptionsSectionComponent implements OnInit {
this.additionalOptionsForm.disable();
this.isPartialEdit = true;
}
// Disable adding new URIs when the cipher form is disabled
this.cipherFormContainer.formStatusChange$
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((status) => {
if (status === "disabled") {
this.allowNewField = false;
} else if (status === "enabled") {
this.allowNewField = true;
}
});
}
/** Opens the add custom field dialog */

View File

@@ -3,7 +3,7 @@
import { LiveAnnouncer } from "@angular/cdk/a11y";
import { CdkDragDrop, DragDropModule, moveItemInArray } from "@angular/cdk/drag-drop";
import { AsyncPipe, NgForOf, NgIf } from "@angular/common";
import { Component, OnInit, QueryList, ViewChildren } from "@angular/core";
import { Component, DestroyRef, inject, OnInit, QueryList, ViewChildren } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
import { filter, Subject, switchMap, take } from "rxjs";
@@ -88,6 +88,7 @@ export class AutofillOptionsComponent implements OnInit {
* Emits when a new URI input is added to the form and should be focused.
*/
private focusOnNewInput$ = new Subject<void>();
private destroyRef = inject(DestroyRef);
constructor(
private cipherFormContainer: CipherFormContainer,
@@ -102,7 +103,7 @@ export class AutofillOptionsComponent implements OnInit {
this.autofillOptionsForm.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => {
this.cipherFormContainer.patchCipher((cipher) => {
cipher.login.uris = value.uris.map((uri: UriField) =>
cipher.login.uris = value.uris?.map((uri: UriField) =>
Object.assign(new LoginUriView(), {
uri: uri.uri,
match: uri.matchDetection,
@@ -139,6 +140,15 @@ export class AutofillOptionsComponent implements OnInit {
if (this.cipherFormContainer.config.mode === "partial-edit") {
this.autofillOptionsForm.disable();
}
// Disable adding new URIs when the cipher form is disabled
this.cipherFormContainer.formStatusChange$
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((status) => {
if (status === "disabled") {
this.autofillOptionsForm.disable();
}
});
}
private initFromExistingCipher(existingLogin: LoginView) {

View File

@@ -112,11 +112,13 @@ export class CipherFormComponent implements AfterViewInit, OnInit, OnChanges, Ci
@Output() formReady = this.formReadySubject.asObservable();
@Output() onFormStatusChange = new EventEmitter<"enabled" | "disabled">();
/**
* Emitted when the form is enabled
*/
private formEnabledSubject = new Subject<void>();
formEnabled$ = this.formEnabledSubject.asObservable();
private formStatusChangeSubject = new Subject<"enabled" | "disabled">();
formStatusChange$ = this.formStatusChangeSubject.asObservable();
/**
* The original cipher being edited or cloned. Null for add mode.
@@ -158,11 +160,14 @@ export class CipherFormComponent implements AfterViewInit, OnInit, OnChanges, Ci
disableFormFields(): void {
this.cipherForm.disable({ emitEvent: false });
this.formStatusChangeSubject.next("disabled");
this.onFormStatusChange.emit("disabled");
}
enableFormFields(): void {
this.cipherForm.enable({ emitEvent: false });
this.formEnabledSubject.next();
this.formStatusChangeSubject.next("enabled");
this.onFormStatusChange.emit("enabled");
}
/**

View File

@@ -11,6 +11,7 @@
[attr.aria-checked]="itemDetailsForm.value.favorite"
[appA11yTitle]="'favorite' | i18n"
(click)="toggleFavorite()"
[disabled]="isFavoriteButtonDisabled"
></button>
</bit-section-header>
<bit-card>

View File

@@ -82,6 +82,8 @@ export class ItemDetailsSectionComponent implements OnInit {
protected userId: UserId;
protected isFavoriteButtonDisabled = false;
@Input({ required: true })
config: CipherFormConfig;
@@ -248,8 +250,10 @@ export class ItemDetailsSectionComponent implements OnInit {
if (this.itemDetailsForm.controls.organizationId.value === null) {
this.cipherFormContainer.disableFormFields();
this.itemDetailsForm.controls.organizationId.enable();
this.isFavoriteButtonDisabled = true;
} else {
this.cipherFormContainer.enableFormFields();
this.isFavoriteButtonDisabled = false;
}
}
}

View File

@@ -98,9 +98,13 @@ export class SshKeySectionComponent implements OnInit {
// Disable the form if the cipher form container is enabled
// to prevent user interaction
this.cipherFormContainer.formEnabled$
this.cipherFormContainer.formStatusChange$
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => this.sshKeyForm.disable());
.subscribe((status) => {
if (status === "enabled") {
this.sshKeyForm.disable();
}
});
}
/** Set form initial form values from the current cipher */