mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-07-09 13:35:10 +00:00
Again, removing wrappers
This commit is contained in:
+7
-7
@@ -9,9 +9,9 @@
|
||||
<label class="form-label" for="metadataDocumentID" i18n>Documents:</label>
|
||||
<ul class="list-group"
|
||||
cdkDropList
|
||||
[cdkDropListData]="documentIDs"
|
||||
[cdkDropListData]="documentIDs()"
|
||||
(cdkDropListDropped)="onDrop($event)">
|
||||
@for (documentID of documentIDs; track documentID) {
|
||||
@for (documentID of documentIDs(); track documentID) {
|
||||
@let document = getDocument(documentID);
|
||||
@if (document) {
|
||||
<li class="list-group-item d-flex align-items-center" cdkDrag>
|
||||
@@ -36,22 +36,22 @@
|
||||
</div>
|
||||
<div class="form-group mt-4">
|
||||
<label class="form-label" for="metadataDocumentID" i18n>Use metadata from:</label>
|
||||
<select class="form-select" [(ngModel)]="metadataDocumentID">
|
||||
<select class="form-select" [ngModel]="metadataDocumentID()" (ngModelChange)="metadataDocumentID.set($event)">
|
||||
<option [ngValue]="-1" i18n>Regenerate all metadata</option>
|
||||
@for (document of documents; track document.id) {
|
||||
@for (document of documents(); track document.id) {
|
||||
<option [ngValue]="document.id">{{document.title}}</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-check form-switch mt-4">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="archiveFallbackSwitch" [(ngModel)]="archiveFallback">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="archiveFallbackSwitch" [ngModel]="archiveFallback()" (ngModelChange)="archiveFallback.set($event)">
|
||||
<label class="form-check-label" for="archiveFallbackSwitch" i18n>Try to include archive version in merge for non-PDF files</label>
|
||||
</div>
|
||||
<div class="form-check form-switch mt-2">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="deleteOriginalsSwitch" [(ngModel)]="deleteOriginals" [disabled]="!userOwnsAllDocuments">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="deleteOriginalsSwitch" [ngModel]="deleteOriginals()" (ngModelChange)="deleteOriginals.set($event)" [disabled]="!userOwnsAllDocuments">
|
||||
<label class="form-check-label" for="deleteOriginalsSwitch" i18n>Delete original documents after successful merge</label>
|
||||
</div>
|
||||
@if (!archiveFallback) {
|
||||
@if (!archiveFallback()) {
|
||||
<p class="small text-muted fst-italic mt-4" i18n>Note that only PDFs will be included.</p>
|
||||
}
|
||||
</div>
|
||||
|
||||
+4
-4
@@ -50,12 +50,12 @@ describe('MergeConfirmDialogComponent', () => {
|
||||
|
||||
component.ngOnInit()
|
||||
|
||||
expect(component.documents).toEqual(documents)
|
||||
expect(documentService.getFew).toHaveBeenCalledWith(component.documentIDs)
|
||||
expect(component.documents()).toEqual(documents)
|
||||
expect(documentService.getFew).toHaveBeenCalledWith(component.documentIDs())
|
||||
})
|
||||
|
||||
it('should move documentIDs on drop', () => {
|
||||
component.documentIDs = [1, 2, 3]
|
||||
component.documentIDs.set([1, 2, 3])
|
||||
const event = {
|
||||
previousIndex: 1,
|
||||
currentIndex: 2,
|
||||
@@ -63,7 +63,7 @@ describe('MergeConfirmDialogComponent', () => {
|
||||
|
||||
component.onDrop(event as any)
|
||||
|
||||
expect(component.documentIDs).toEqual([1, 3, 2])
|
||||
expect(component.documentIDs()).toEqual([1, 3, 2])
|
||||
})
|
||||
|
||||
it('should get document by ID', () => {
|
||||
|
||||
+11
-47
@@ -36,47 +36,11 @@ export class MergeConfirmDialogComponent
|
||||
private documentService = inject(DocumentService)
|
||||
private permissionService = inject(PermissionsService)
|
||||
|
||||
private documentIDsSignal = signal<number[]>([])
|
||||
private archiveFallbackSignal = signal(false)
|
||||
private deleteOriginalsSignal = signal(false)
|
||||
private documentsSignal = signal<Document[]>([])
|
||||
private metadataDocumentIDSignal = signal(-1)
|
||||
|
||||
public get documentIDs(): number[] {
|
||||
return this.documentIDsSignal()
|
||||
}
|
||||
|
||||
public set documentIDs(documentIDs: number[]) {
|
||||
this.documentIDsSignal.set(documentIDs)
|
||||
}
|
||||
|
||||
public get archiveFallback(): boolean {
|
||||
return this.archiveFallbackSignal()
|
||||
}
|
||||
|
||||
public set archiveFallback(archiveFallback: boolean) {
|
||||
this.archiveFallbackSignal.set(archiveFallback)
|
||||
}
|
||||
|
||||
public get deleteOriginals(): boolean {
|
||||
return this.deleteOriginalsSignal()
|
||||
}
|
||||
|
||||
public set deleteOriginals(deleteOriginals: boolean) {
|
||||
this.deleteOriginalsSignal.set(deleteOriginals)
|
||||
}
|
||||
|
||||
get documents(): Document[] {
|
||||
return this.documentsSignal()
|
||||
}
|
||||
|
||||
public get metadataDocumentID(): number {
|
||||
return this.metadataDocumentIDSignal()
|
||||
}
|
||||
|
||||
public set metadataDocumentID(metadataDocumentID: number) {
|
||||
this.metadataDocumentIDSignal.set(metadataDocumentID)
|
||||
}
|
||||
readonly documentIDs = signal<number[]>([])
|
||||
readonly archiveFallback = signal(false)
|
||||
readonly deleteOriginals = signal(false)
|
||||
readonly documents = signal<Document[]>([])
|
||||
readonly metadataDocumentID = signal(-1)
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
@@ -84,25 +48,25 @@ export class MergeConfirmDialogComponent
|
||||
|
||||
ngOnInit() {
|
||||
this.documentService
|
||||
.getFew(this.documentIDs)
|
||||
.getFew(this.documentIDs())
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe((r) => {
|
||||
this.documentsSignal.set(r.results)
|
||||
this.documents.set(r.results)
|
||||
})
|
||||
}
|
||||
|
||||
onDrop(event: CdkDragDrop<number[]>) {
|
||||
const documentIDs = this.documentIDs.concat()
|
||||
const documentIDs = this.documentIDs().concat()
|
||||
moveItemInArray(documentIDs, event.previousIndex, event.currentIndex)
|
||||
this.documentIDs = documentIDs
|
||||
this.documentIDs.set(documentIDs)
|
||||
}
|
||||
|
||||
getDocument(documentID: number): Document {
|
||||
return this.documents.find((d) => d.id === documentID)
|
||||
return this.documents().find((d) => d.id === documentID)
|
||||
}
|
||||
|
||||
get userOwnsAllDocuments(): boolean {
|
||||
return this.documents.every((d) =>
|
||||
return this.documents().every((d) =>
|
||||
this.permissionService.currentUserOwnsObject(d)
|
||||
)
|
||||
}
|
||||
|
||||
+3
-3
@@ -13,7 +13,7 @@
|
||||
<pngx-input-text [horizontal]="true" i18n-title title="Name" formControlName="name" [error]="error?.name" autocomplete="off"></pngx-input-text>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<pngx-input-select [horizontal]="true" i18n-title title="Account" [items]="accounts" formControlName="account"></pngx-input-select>
|
||||
<pngx-input-select [horizontal]="true" i18n-title title="Account" [items]="accounts()" formControlName="account"></pngx-input-select>
|
||||
</div>
|
||||
<div class="col-md-2 pt-2">
|
||||
<pngx-input-switch [horizontal]="true" i18n-title title="Enabled" formControlName="enabled"></pngx-input-switch>
|
||||
@@ -65,10 +65,10 @@
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<pngx-input-tags [horizontal]="true" [allowCreate]="false" formControlName="assign_tags"></pngx-input-tags>
|
||||
<pngx-input-select [horizontal]="true" i18n-title title="Assign document type" [items]="documentTypes" [allowNull]="true" formControlName="assign_document_type"></pngx-input-select>
|
||||
<pngx-input-select [horizontal]="true" i18n-title title="Assign document type" [items]="documentTypes()" [allowNull]="true" formControlName="assign_document_type"></pngx-input-select>
|
||||
<pngx-input-select [horizontal]="true" i18n-title title="Assign correspondent from" [items]="metadataCorrespondentOptions" formControlName="assign_correspondent_from"></pngx-input-select>
|
||||
@if (showCorrespondentField) {
|
||||
<pngx-input-select [horizontal]="true" i18n-title title="Assign correspondent" [items]="correspondents" [allowNull]="true" formControlName="assign_correspondent"></pngx-input-select>
|
||||
<pngx-input-select [horizontal]="true" i18n-title title="Assign correspondent" [items]="correspondents()" [allowNull]="true" formControlName="assign_correspondent"></pngx-input-select>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+18
-50
@@ -1,11 +1,12 @@
|
||||
import { Component, inject, signal } from '@angular/core'
|
||||
import { Component, inject } from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import {
|
||||
FormControl,
|
||||
FormGroup,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
} from '@angular/forms'
|
||||
import { first } from 'rxjs'
|
||||
import { map } from 'rxjs'
|
||||
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'
|
||||
import { Correspondent } from 'src/app/data/correspondent'
|
||||
import { DocumentType } from 'src/app/data/document-type'
|
||||
@@ -154,61 +155,28 @@ const METADATA_CORRESPONDENT_OPTIONS = [
|
||||
],
|
||||
})
|
||||
export class MailRuleEditDialogComponent extends EditDialogComponent<MailRule> {
|
||||
private accountService: MailAccountService
|
||||
private correspondentService: CorrespondentService
|
||||
private documentTypeService: DocumentTypeService
|
||||
private accountService = inject(MailAccountService)
|
||||
private correspondentService = inject(CorrespondentService)
|
||||
private documentTypeService = inject(DocumentTypeService)
|
||||
|
||||
private accountsSignal = signal<MailAccount[]>(undefined)
|
||||
private correspondentsSignal = signal<Correspondent[]>(undefined)
|
||||
private documentTypesSignal = signal<DocumentType[]>(undefined)
|
||||
|
||||
get accounts(): MailAccount[] {
|
||||
return this.accountsSignal()
|
||||
}
|
||||
|
||||
set accounts(accounts: MailAccount[]) {
|
||||
this.accountsSignal.set(accounts)
|
||||
}
|
||||
|
||||
get correspondents(): Correspondent[] {
|
||||
return this.correspondentsSignal()
|
||||
}
|
||||
|
||||
set correspondents(correspondents: Correspondent[]) {
|
||||
this.correspondentsSignal.set(correspondents)
|
||||
}
|
||||
|
||||
get documentTypes(): DocumentType[] {
|
||||
return this.documentTypesSignal()
|
||||
}
|
||||
|
||||
set documentTypes(documentTypes: DocumentType[]) {
|
||||
this.documentTypesSignal.set(documentTypes)
|
||||
}
|
||||
readonly accounts = toSignal(
|
||||
this.accountService.listAll().pipe(map((result) => result.results)),
|
||||
{ initialValue: undefined as MailAccount[] }
|
||||
)
|
||||
readonly correspondents = toSignal(
|
||||
this.correspondentService.listAll().pipe(map((result) => result.results)),
|
||||
{ initialValue: undefined as Correspondent[] }
|
||||
)
|
||||
readonly documentTypes = toSignal(
|
||||
this.documentTypeService.listAll().pipe(map((result) => result.results)),
|
||||
{ initialValue: undefined as DocumentType[] }
|
||||
)
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.service = inject(MailRuleService)
|
||||
this.accountService = inject(MailAccountService)
|
||||
this.correspondentService = inject(CorrespondentService)
|
||||
this.documentTypeService = inject(DocumentTypeService)
|
||||
this.userService = inject(UserService)
|
||||
this.settingsService = inject(SettingsService)
|
||||
|
||||
this.accountService
|
||||
.listAll()
|
||||
.pipe(first())
|
||||
.subscribe((result) => (this.accounts = result.results))
|
||||
|
||||
this.correspondentService
|
||||
.listAll()
|
||||
.pipe(first())
|
||||
.subscribe((result) => (this.correspondents = result.results))
|
||||
|
||||
this.documentTypeService
|
||||
.listAll()
|
||||
.pipe(first())
|
||||
.subscribe((result) => (this.documentTypes = result.results))
|
||||
}
|
||||
|
||||
getCreateTitle() {
|
||||
|
||||
+2
-2
@@ -31,7 +31,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<pngx-input-select i18n-title title="Groups" [items]="groups" multiple="true" formControlName="groups"></pngx-input-select>
|
||||
<pngx-input-select i18n-title title="Groups" [items]="groups()" multiple="true" formControlName="groups"></pngx-input-select>
|
||||
|
||||
@if (object?.is_mfa_enabled && currentUserIsSuperUser) {
|
||||
<label class="form-label" i18n>Two-factor Authentication</label>
|
||||
@@ -42,7 +42,7 @@
|
||||
i18n-title
|
||||
buttonClasses="btn-outline-danger btn-sm"
|
||||
iconName="trash"
|
||||
[disabled]="totpLoading"
|
||||
[disabled]="totpLoading()"
|
||||
(confirm)="deactivateTotp()">
|
||||
</pngx-confirm-button>
|
||||
}
|
||||
|
||||
+3
-3
@@ -114,17 +114,17 @@ describe('UserEditDialogComponent', () => {
|
||||
it('should detect whether password was changed in form on save', () => {
|
||||
component.objectForm.get('password').setValue(null)
|
||||
component.save()
|
||||
expect(component.passwordIsSet).toBeFalsy()
|
||||
expect(component.passwordIsSet()).toBeFalsy()
|
||||
|
||||
// unchanged pw
|
||||
component.objectForm.get('password').setValue('*******')
|
||||
component.save()
|
||||
expect(component.passwordIsSet).toBeFalsy()
|
||||
expect(component.passwordIsSet()).toBeFalsy()
|
||||
|
||||
// unchanged pw
|
||||
component.objectForm.get('password').setValue('helloworld')
|
||||
component.save()
|
||||
expect(component.passwordIsSet).toBeTruthy()
|
||||
expect(component.passwordIsSet()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should support deactivation of TOTP', () => {
|
||||
|
||||
+15
-40
@@ -1,11 +1,12 @@
|
||||
import { Component, OnInit, inject, signal } from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import {
|
||||
FormControl,
|
||||
FormGroup,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
} from '@angular/forms'
|
||||
import { first } from 'rxjs'
|
||||
import { first, map } from 'rxjs'
|
||||
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'
|
||||
import { Group } from 'src/app/data/group'
|
||||
import { User } from 'src/app/data/user'
|
||||
@@ -38,46 +39,19 @@ export class UserEditDialogComponent
|
||||
implements OnInit
|
||||
{
|
||||
private toastService = inject(ToastService)
|
||||
private groupsService: GroupService
|
||||
private groupsService = inject(GroupService)
|
||||
|
||||
private groupsSignal = signal<Group[]>(undefined)
|
||||
private passwordIsSetSignal = signal(false)
|
||||
private totpLoadingSignal = signal(false)
|
||||
|
||||
get groups(): Group[] {
|
||||
return this.groupsSignal()
|
||||
}
|
||||
|
||||
set groups(groups: Group[]) {
|
||||
this.groupsSignal.set(groups)
|
||||
}
|
||||
|
||||
get passwordIsSet(): boolean {
|
||||
return this.passwordIsSetSignal()
|
||||
}
|
||||
|
||||
set passwordIsSet(passwordIsSet: boolean) {
|
||||
this.passwordIsSetSignal.set(passwordIsSet)
|
||||
}
|
||||
|
||||
public get totpLoading(): boolean {
|
||||
return this.totpLoadingSignal()
|
||||
}
|
||||
|
||||
public set totpLoading(totpLoading: boolean) {
|
||||
this.totpLoadingSignal.set(totpLoading)
|
||||
}
|
||||
readonly groups = toSignal(
|
||||
this.groupsService.listAll().pipe(map((result) => result.results)),
|
||||
{ initialValue: undefined as Group[] }
|
||||
)
|
||||
readonly passwordIsSet = signal(false)
|
||||
readonly totpLoading = signal(false)
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.service = inject(UserService)
|
||||
this.groupsService = inject(GroupService)
|
||||
this.settingsService = inject(SettingsService)
|
||||
|
||||
this.groupsService
|
||||
.listAll()
|
||||
.pipe(first())
|
||||
.subscribe((result) => (this.groups = result.results))
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -127,14 +101,15 @@ export class UserEditDialogComponent
|
||||
if (!groupsVal) return []
|
||||
else
|
||||
return groupsVal.flatMap(
|
||||
(id) => this.groups?.find((g) => g.id == id)?.permissions
|
||||
(id) => this.groups()?.find((g) => g.id == id)?.permissions
|
||||
)
|
||||
}
|
||||
|
||||
save(): void {
|
||||
this.passwordIsSet =
|
||||
this.passwordIsSet.set(
|
||||
this.objectForm.get('password').value?.toString().replaceAll('*', '')
|
||||
.length > 0
|
||||
)
|
||||
super.save()
|
||||
}
|
||||
|
||||
@@ -143,13 +118,13 @@ export class UserEditDialogComponent
|
||||
}
|
||||
|
||||
deactivateTotp() {
|
||||
this.totpLoading = true
|
||||
this.totpLoading.set(true)
|
||||
;(this.service as UserService)
|
||||
.deactivateTotp(this.object)
|
||||
.pipe(first())
|
||||
.subscribe({
|
||||
next: (result) => {
|
||||
this.totpLoading = false
|
||||
this.totpLoading.set(false)
|
||||
if (result) {
|
||||
this.toastService.showInfo($localize`Totp deactivated`)
|
||||
this.object.is_mfa_enabled = false
|
||||
@@ -158,7 +133,7 @@ export class UserEditDialogComponent
|
||||
}
|
||||
},
|
||||
error: (e) => {
|
||||
this.totpLoading = false
|
||||
this.totpLoading.set(false)
|
||||
this.toastService.showError($localize`Totp deactivation failed`, e)
|
||||
},
|
||||
})
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<ng-select name="inputId" [(ngModel)]="value"
|
||||
[disabled]="disabled"
|
||||
clearable="true"
|
||||
[items]="groups"
|
||||
[items]="groups()"
|
||||
multiple="true"
|
||||
bindLabel="name"
|
||||
bindValue="id"
|
||||
|
||||
+8
-21
@@ -1,11 +1,12 @@
|
||||
import { Component, forwardRef, inject, signal } from '@angular/core'
|
||||
import { Component, forwardRef, inject } from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import {
|
||||
FormsModule,
|
||||
NG_VALUE_ACCESSOR,
|
||||
ReactiveFormsModule,
|
||||
} from '@angular/forms'
|
||||
import { NgSelectComponent } from '@ng-select/ng-select'
|
||||
import { first } from 'rxjs/operators'
|
||||
import { map } from 'rxjs/operators'
|
||||
import { Group } from 'src/app/data/group'
|
||||
import { GroupService } from 'src/app/services/rest/group.service'
|
||||
import { AbstractInputComponent } from '../../abstract-input'
|
||||
@@ -24,23 +25,9 @@ import { AbstractInputComponent } from '../../abstract-input'
|
||||
imports: [NgSelectComponent, FormsModule, ReactiveFormsModule],
|
||||
})
|
||||
export class PermissionsGroupComponent extends AbstractInputComponent<Group> {
|
||||
private groupsSignal = signal<Group[]>(undefined)
|
||||
|
||||
get groups(): Group[] {
|
||||
return this.groupsSignal()
|
||||
}
|
||||
|
||||
set groups(groups: Group[]) {
|
||||
this.groupsSignal.set(groups)
|
||||
}
|
||||
|
||||
constructor() {
|
||||
const groupService = inject(GroupService)
|
||||
|
||||
super()
|
||||
groupService
|
||||
.listAll()
|
||||
.pipe(first())
|
||||
.subscribe((result) => (this.groups = result.results))
|
||||
}
|
||||
private readonly groupService = inject(GroupService)
|
||||
readonly groups = toSignal(
|
||||
this.groupService.listAll().pipe(map((result) => result.results)),
|
||||
{ initialValue: undefined as Group[] }
|
||||
)
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<ng-select name="inputId" [(ngModel)]="value"
|
||||
[disabled]="disabled"
|
||||
clearable="true"
|
||||
[items]="users"
|
||||
[items]="users()"
|
||||
multiple="true"
|
||||
bindLabel="username"
|
||||
bindValue="id"
|
||||
|
||||
+8
-21
@@ -1,11 +1,12 @@
|
||||
import { Component, forwardRef, inject, signal } from '@angular/core'
|
||||
import { Component, forwardRef, inject } from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import {
|
||||
FormsModule,
|
||||
NG_VALUE_ACCESSOR,
|
||||
ReactiveFormsModule,
|
||||
} from '@angular/forms'
|
||||
import { NgSelectComponent } from '@ng-select/ng-select'
|
||||
import { first } from 'rxjs/operators'
|
||||
import { map } from 'rxjs/operators'
|
||||
import { User } from 'src/app/data/user'
|
||||
import { UserService } from 'src/app/services/rest/user.service'
|
||||
import { AbstractInputComponent } from '../../abstract-input'
|
||||
@@ -24,23 +25,9 @@ import { AbstractInputComponent } from '../../abstract-input'
|
||||
imports: [NgSelectComponent, FormsModule, ReactiveFormsModule],
|
||||
})
|
||||
export class PermissionsUserComponent extends AbstractInputComponent<User[]> {
|
||||
private usersSignal = signal<User[]>(undefined)
|
||||
|
||||
get users(): User[] {
|
||||
return this.usersSignal()
|
||||
}
|
||||
|
||||
set users(users: User[]) {
|
||||
this.usersSignal.set(users)
|
||||
}
|
||||
|
||||
constructor() {
|
||||
const userService = inject(UserService)
|
||||
|
||||
super()
|
||||
userService
|
||||
.listAll()
|
||||
.pipe(first())
|
||||
.subscribe((result) => (this.users = result.results))
|
||||
}
|
||||
private readonly userService = inject(UserService)
|
||||
readonly users = toSignal(
|
||||
this.userService.listAll().pipe(map((result) => result.results)),
|
||||
{ initialValue: undefined as User[] }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -146,8 +146,8 @@ describe('PDFEditorComponent', () => {
|
||||
const previewSpy = jest
|
||||
.spyOn(documentService, 'getPreviewUrl')
|
||||
.mockReturnValue('preview-version')
|
||||
component.documentID = 3
|
||||
component.versionID = 10
|
||||
component.documentID.set(3)
|
||||
component.versionID.set(10)
|
||||
|
||||
expect(component.pdfSrc).toBe('preview-version')
|
||||
expect(previewSpy).toHaveBeenCalledWith(3, false, 10)
|
||||
|
||||
@@ -45,24 +45,8 @@ export class PDFEditorComponent extends ConfirmDialogComponent {
|
||||
private readonly settingsService = inject(SettingsService)
|
||||
activeModal: NgbActiveModal = inject(NgbActiveModal)
|
||||
|
||||
private documentIDSignal = signal<number>(undefined)
|
||||
private versionIDSignal = signal<number>(undefined)
|
||||
|
||||
get documentID(): number {
|
||||
return this.documentIDSignal()
|
||||
}
|
||||
|
||||
set documentID(documentID: number) {
|
||||
this.documentIDSignal.set(documentID)
|
||||
}
|
||||
|
||||
get versionID(): number {
|
||||
return this.versionIDSignal()
|
||||
}
|
||||
|
||||
set versionID(versionID: number) {
|
||||
this.versionIDSignal.set(versionID)
|
||||
}
|
||||
readonly documentID = signal<number>(undefined)
|
||||
readonly versionID = signal<number>(undefined)
|
||||
|
||||
pages: PageOperation[] = []
|
||||
totalPages = 0
|
||||
@@ -74,9 +58,9 @@ export class PDFEditorComponent extends ConfirmDialogComponent {
|
||||
|
||||
get pdfSrc(): string {
|
||||
return this.documentService.getPreviewUrl(
|
||||
this.documentID,
|
||||
this.documentID(),
|
||||
false,
|
||||
this.versionID
|
||||
this.versionID()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+8
-8
@@ -8,18 +8,18 @@
|
||||
<div>
|
||||
<p class="mb-1">
|
||||
<ng-container i18n>Selected documents:</ng-container>
|
||||
{{ selectionCount }}
|
||||
{{ selectionCount() }}
|
||||
</p>
|
||||
@if (documentPreview.length > 0) {
|
||||
@if (documentPreview().length > 0) {
|
||||
<ul class="list-unstyled small mb-0">
|
||||
@for (doc of documentPreview; track doc.id) {
|
||||
@for (doc of documentPreview(); track doc.id) {
|
||||
<li>
|
||||
<strong>{{ doc.title | documentTitle }}</strong>
|
||||
</li>
|
||||
}
|
||||
@if (selectionCount > documentPreview.length) {
|
||||
@if (selectionCount() > documentPreview().length) {
|
||||
<li>
|
||||
<ng-container i18n>+ {{ selectionCount - documentPreview.length }} more…</ng-container>
|
||||
<ng-container i18n>+ {{ selectionCount() - documentPreview().length }} more…</ng-container>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
@@ -72,10 +72,10 @@
|
||||
type="button"
|
||||
(click)="copy(createdBundle)"
|
||||
>
|
||||
@if (copied) {
|
||||
@if (copied()) {
|
||||
<i-bs name="clipboard-check"></i-bs>
|
||||
}
|
||||
@if (!copied) {
|
||||
@if (!copied()) {
|
||||
<i-bs name="clipboard"></i-bs>
|
||||
}
|
||||
<span class="visually-hidden" i18n>Copy link</span>
|
||||
@@ -118,7 +118,7 @@
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm d-inline-flex align-items-center gap-2 text-nowrap"
|
||||
(click)="submit()"
|
||||
[disabled]="loading || !buttonsEnabled">
|
||||
[disabled]="loading() || !buttonsEnabled">
|
||||
@if (loading()) {
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||
}
|
||||
|
||||
+8
-8
@@ -58,10 +58,10 @@ describe('ShareLinkBundleDialogComponent', () => {
|
||||
|
||||
it('builds payload and emits confirm on submit', () => {
|
||||
const confirmSpy = jest.spyOn(component.confirmClicked, 'emit')
|
||||
component.documents = [
|
||||
component.setDocuments([
|
||||
{ id: 1, title: 'Doc 1' } as any,
|
||||
{ id: 2, title: 'Doc 2' } as any,
|
||||
]
|
||||
])
|
||||
component.form.setValue({
|
||||
shareArchiveVersion: false,
|
||||
expirationDays: 3,
|
||||
@@ -101,11 +101,11 @@ describe('ShareLinkBundleDialogComponent', () => {
|
||||
const docs = Array.from({ length: 12 }).map((_, index) => ({
|
||||
id: index + 1,
|
||||
}))
|
||||
component.documents = docs as any
|
||||
component.setDocuments(docs as any)
|
||||
|
||||
expect(component.selectionCount).toBe(12)
|
||||
expect(component.documentPreview).toHaveLength(10)
|
||||
expect(component.documentPreview[0].id).toBe(1)
|
||||
expect(component.selectionCount()).toBe(12)
|
||||
expect(component.documentPreview()).toHaveLength(10)
|
||||
expect(component.documentPreview()[0].id).toBe(1)
|
||||
})
|
||||
|
||||
it('copies share link and resets state after timeout', fakeAsync(() => {
|
||||
@@ -118,11 +118,11 @@ describe('ShareLinkBundleDialogComponent', () => {
|
||||
component.copy(bundle)
|
||||
|
||||
expect(copySpy).toHaveBeenCalledWith(component.getShareUrl(bundle))
|
||||
expect(component.copied).toBe(true)
|
||||
expect(component.copied()).toBe(true)
|
||||
expect(toastService.showInfo).toHaveBeenCalled()
|
||||
|
||||
tick(3000)
|
||||
expect(component.copied).toBe(false)
|
||||
expect(component.copied()).toBe(false)
|
||||
}))
|
||||
|
||||
it('generates share URLs based on API base URL', () => {
|
||||
|
||||
+12
-29
@@ -1,6 +1,6 @@
|
||||
import { Clipboard } from '@angular/cdk/clipboard'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { Component, Input, inject, signal } from '@angular/core'
|
||||
import { Component, inject, signal } from '@angular/core'
|
||||
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'
|
||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
import { Document } from 'src/app/data/document'
|
||||
@@ -38,26 +38,10 @@ export class ShareLinkBundleDialogComponent extends ConfirmDialogComponent {
|
||||
private readonly clipboard = inject(Clipboard)
|
||||
private readonly toastService = inject(ToastService)
|
||||
|
||||
private documentsSignal = signal<Document[]>([])
|
||||
private selectionCountSignal = signal(0)
|
||||
private documentPreviewSignal = signal<Document[]>([])
|
||||
private copiedSignal = signal(false)
|
||||
|
||||
get selectionCount(): number {
|
||||
return this.selectionCountSignal()
|
||||
}
|
||||
|
||||
get documentPreview(): Document[] {
|
||||
return this.documentPreviewSignal()
|
||||
}
|
||||
|
||||
get copied(): boolean {
|
||||
return this.copiedSignal()
|
||||
}
|
||||
|
||||
set copied(copied: boolean) {
|
||||
this.copiedSignal.set(copied)
|
||||
}
|
||||
readonly documents = signal<Document[]>([])
|
||||
readonly selectionCount = signal(0)
|
||||
readonly documentPreview = signal<Document[]>([])
|
||||
readonly copied = signal(false)
|
||||
|
||||
form: FormGroup = this.formBuilder.group({
|
||||
shareArchiveVersion: true,
|
||||
@@ -78,18 +62,17 @@ export class ShareLinkBundleDialogComponent extends ConfirmDialogComponent {
|
||||
this.btnCaption = $localize`Create link`
|
||||
}
|
||||
|
||||
@Input()
|
||||
set documents(docs: Document[]) {
|
||||
setDocuments(docs: Document[]) {
|
||||
const documents = docs.concat()
|
||||
this.documentsSignal.set(documents)
|
||||
this.selectionCountSignal.set(documents.length)
|
||||
this.documentPreviewSignal.set(documents.slice(0, 10))
|
||||
this.documents.set(documents)
|
||||
this.selectionCount.set(documents.length)
|
||||
this.documentPreview.set(documents.slice(0, 10))
|
||||
}
|
||||
|
||||
submit() {
|
||||
if (this.createdBundle) return
|
||||
this.payload = {
|
||||
document_ids: this.documentsSignal().map((doc) => doc.id),
|
||||
document_ids: this.documents().map((doc) => doc.id),
|
||||
file_version: this.form.value.shareArchiveVersion
|
||||
? FileVersion.Archive
|
||||
: FileVersion.Original,
|
||||
@@ -109,10 +92,10 @@ export class ShareLinkBundleDialogComponent extends ConfirmDialogComponent {
|
||||
copy(bundle: ShareLinkBundleSummary): void {
|
||||
const success = this.clipboard.copy(this.getShareUrl(bundle))
|
||||
if (success) {
|
||||
this.copied = true
|
||||
this.copied.set(true)
|
||||
this.toastService.showInfo($localize`Share link copied to clipboard.`)
|
||||
setTimeout(() => {
|
||||
this.copied = false
|
||||
this.copied.set(false)
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
|
||||
+8
-8
@@ -10,21 +10,21 @@
|
||||
<span i18n>Loading share link bundles…</span>
|
||||
</div>
|
||||
}
|
||||
@if (!loading && error) {
|
||||
@if (!loading() && error()) {
|
||||
<div class="alert alert-danger mb-0" role="alert">
|
||||
{{ error }}
|
||||
{{ error() }}
|
||||
</div>
|
||||
}
|
||||
@if (!loading && !error) {
|
||||
@if (!loading() && !error()) {
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<p class="mb-0 text-muted small">
|
||||
<ng-container i18n>Status updates every few seconds while bundles are being prepared.</ng-container>
|
||||
</p>
|
||||
</div>
|
||||
@if (bundles.length === 0) {
|
||||
@if (bundles().length === 0) {
|
||||
<p class="mb-0 text-muted fst-italic" i18n>No share link bundles currently exist.</p>
|
||||
}
|
||||
@if (bundles.length > 0) {
|
||||
@if (bundles().length > 0) {
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle mb-0">
|
||||
<thead>
|
||||
@@ -39,7 +39,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (bundle of bundles; track bundle.id) {
|
||||
@for (bundle of bundles(); track bundle.id) {
|
||||
<tr>
|
||||
<td>
|
||||
<div>{{ bundle.created | date: 'short' }}</div>
|
||||
@@ -113,10 +113,10 @@
|
||||
title="Copy share link"
|
||||
i18n-title
|
||||
>
|
||||
@if (copiedSlug === bundle.slug) {
|
||||
@if (copiedSlug() === bundle.slug) {
|
||||
<i-bs name="clipboard-check"></i-bs>
|
||||
}
|
||||
@if (copiedSlug !== bundle.slug) {
|
||||
@if (copiedSlug() !== bundle.slug) {
|
||||
<i-bs name="clipboard"></i-bs>
|
||||
}
|
||||
<span class="visually-hidden" i18n>Copy share link</span>
|
||||
|
||||
+10
-10
@@ -96,9 +96,9 @@ describe('ShareLinkBundleManageDialogComponent', () => {
|
||||
tick()
|
||||
|
||||
expect(service.listAllBundles).toHaveBeenCalledTimes(1)
|
||||
expect(component.bundles).toEqual(bundles)
|
||||
expect(component.bundles()).toEqual(bundles)
|
||||
expect(component.loading()).toBe(false)
|
||||
expect(component.error).toBeNull()
|
||||
expect(component.error()).toBeNull()
|
||||
|
||||
tick(5000)
|
||||
expect(service.listAllBundles).toHaveBeenCalledTimes(2)
|
||||
@@ -113,7 +113,7 @@ describe('ShareLinkBundleManageDialogComponent', () => {
|
||||
fixture.detectChanges()
|
||||
tick()
|
||||
|
||||
expect(component.error).toContain('Failed to load share link bundles.')
|
||||
expect(component.error()).toContain('Failed to load share link bundles.')
|
||||
expect(toastService.showError).toHaveBeenCalled()
|
||||
expect(component.loading()).toBe(false)
|
||||
|
||||
@@ -135,11 +135,11 @@ describe('ShareLinkBundleManageDialogComponent', () => {
|
||||
expect(clipboard.copy).toHaveBeenCalledWith(
|
||||
component.getShareUrl(readyBundle)
|
||||
)
|
||||
expect(component.copiedSlug).toBe('ready-slug')
|
||||
expect(component.copiedSlug()).toBe('ready-slug')
|
||||
expect(toastService.showInfo).toHaveBeenCalled()
|
||||
|
||||
tick(3000)
|
||||
expect(component.copiedSlug).toBeNull()
|
||||
expect(component.copiedSlug()).toBeNull()
|
||||
}))
|
||||
|
||||
it('ignores copy requests for non-ready bundles', fakeAsync(() => {
|
||||
@@ -190,12 +190,12 @@ describe('ShareLinkBundleManageDialogComponent', () => {
|
||||
fixture.detectChanges()
|
||||
tick()
|
||||
|
||||
component.bundles = [sampleBundle()]
|
||||
component.retry(component.bundles[0])
|
||||
component.bundles.set([sampleBundle()])
|
||||
component.retry(component.bundles()[0])
|
||||
tick()
|
||||
|
||||
expect(service.rebuildBundle).toHaveBeenCalledWith(updated.id)
|
||||
expect(component.bundles[0].status).toBe(ShareLinkBundleStatus.Ready)
|
||||
expect(component.bundles()[0].status).toBe(ShareLinkBundleStatus.Ready)
|
||||
expect(toastService.showInfo).toHaveBeenCalled()
|
||||
}))
|
||||
|
||||
@@ -208,11 +208,11 @@ describe('ShareLinkBundleManageDialogComponent', () => {
|
||||
fixture.detectChanges()
|
||||
tick()
|
||||
|
||||
component.bundles = [sampleBundle()]
|
||||
component.bundles.set([sampleBundle()])
|
||||
component.retry({ id: 99 } as ShareLinkBundleSummary)
|
||||
tick()
|
||||
|
||||
expect(component.bundles.find((bundle) => bundle.id === 99)).toBeTruthy()
|
||||
expect(component.bundles().find((bundle) => bundle.id === 99)).toBeTruthy()
|
||||
}))
|
||||
|
||||
it('handles retry errors', fakeAsync(() => {
|
||||
|
||||
+18
-41
@@ -40,33 +40,9 @@ export class ShareLinkBundleManageDialogComponent
|
||||
private readonly clipboard = inject(Clipboard)
|
||||
|
||||
title = $localize`Share link bundles`
|
||||
private bundlesSignal = signal<ShareLinkBundleSummary[]>([])
|
||||
private errorSignal = signal<string | null>(null)
|
||||
private copiedSlugSignal = signal<string | null>(null)
|
||||
|
||||
get bundles(): ShareLinkBundleSummary[] {
|
||||
return this.bundlesSignal()
|
||||
}
|
||||
|
||||
set bundles(bundles: ShareLinkBundleSummary[]) {
|
||||
this.bundlesSignal.set(bundles)
|
||||
}
|
||||
|
||||
get error(): string | null {
|
||||
return this.errorSignal()
|
||||
}
|
||||
|
||||
set error(error: string | null) {
|
||||
this.errorSignal.set(error)
|
||||
}
|
||||
|
||||
get copiedSlug(): string | null {
|
||||
return this.copiedSlugSignal()
|
||||
}
|
||||
|
||||
set copiedSlug(copiedSlug: string | null) {
|
||||
this.copiedSlugSignal.set(copiedSlug)
|
||||
}
|
||||
readonly bundles = signal<ShareLinkBundleSummary[]>([])
|
||||
readonly error = signal<string | null>(null)
|
||||
readonly copiedSlug = signal<string | null>(null)
|
||||
|
||||
readonly statuses = ShareLinkBundleStatus
|
||||
readonly fileVersions = FileVersion
|
||||
@@ -80,13 +56,13 @@ export class ShareLinkBundleManageDialogComponent
|
||||
if (!silent) {
|
||||
this.loading.set(true)
|
||||
}
|
||||
this.error = null
|
||||
this.error.set(null)
|
||||
return this.shareLinkBundleService.listAllBundles().pipe(
|
||||
catchError((error) => {
|
||||
if (!silent) {
|
||||
this.loading.set(false)
|
||||
}
|
||||
this.error = $localize`Failed to load share link bundles.`
|
||||
this.error.set($localize`Failed to load share link bundles.`)
|
||||
this.toastService.showError(
|
||||
$localize`Error retrieving share link bundles.`,
|
||||
error
|
||||
@@ -99,8 +75,8 @@ export class ShareLinkBundleManageDialogComponent
|
||||
)
|
||||
.subscribe((results) => {
|
||||
if (results) {
|
||||
this.bundles = results
|
||||
this.copiedSlug = null
|
||||
this.bundles.set(results)
|
||||
this.copiedSlug.set(null)
|
||||
}
|
||||
this.loading.set(false)
|
||||
})
|
||||
@@ -128,16 +104,16 @@ export class ShareLinkBundleManageDialogComponent
|
||||
}
|
||||
const success = this.clipboard.copy(this.getShareUrl(bundle))
|
||||
if (success) {
|
||||
this.copiedSlug = bundle.slug
|
||||
this.copiedSlug.set(bundle.slug)
|
||||
setTimeout(() => {
|
||||
this.copiedSlug = null
|
||||
this.copiedSlug.set(null)
|
||||
}, 3000)
|
||||
this.toastService.showInfo($localize`Share link copied to clipboard.`)
|
||||
}
|
||||
}
|
||||
|
||||
delete(bundle: ShareLinkBundleSummary): void {
|
||||
this.error = null
|
||||
this.error.set(null)
|
||||
this.loading.set(true)
|
||||
this.shareLinkBundleService.delete(bundle).subscribe({
|
||||
next: () => {
|
||||
@@ -155,7 +131,7 @@ export class ShareLinkBundleManageDialogComponent
|
||||
}
|
||||
|
||||
retry(bundle: ShareLinkBundleSummary): void {
|
||||
this.error = null
|
||||
this.error.set(null)
|
||||
this.shareLinkBundleService.rebuildBundle(bundle.id).subscribe({
|
||||
next: (updated) => {
|
||||
this.toastService.showInfo(
|
||||
@@ -182,15 +158,16 @@ export class ShareLinkBundleManageDialogComponent
|
||||
}
|
||||
|
||||
private replaceBundle(updated: ShareLinkBundleSummary): void {
|
||||
const index = this.bundles.findIndex((bundle) => bundle.id === updated.id)
|
||||
const bundles = this.bundles()
|
||||
const index = bundles.findIndex((bundle) => bundle.id === updated.id)
|
||||
if (index >= 0) {
|
||||
this.bundles = [
|
||||
...this.bundles.slice(0, index),
|
||||
this.bundles.set([
|
||||
...bundles.slice(0, index),
|
||||
updated,
|
||||
...this.bundles.slice(index + 1),
|
||||
]
|
||||
...bundles.slice(index + 1),
|
||||
])
|
||||
} else {
|
||||
this.bundles = [updated, ...this.bundles]
|
||||
this.bundles.set([updated, ...bundles])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1697,8 +1697,8 @@ describe('DocumentDetailComponent', () => {
|
||||
component.selectedVersionId = 10
|
||||
component.editPdf()
|
||||
expect(modal).not.toBeUndefined()
|
||||
modal.componentInstance.documentID = doc.id
|
||||
expect(modal.componentInstance.versionID).toBe(10)
|
||||
modal.componentInstance.documentID.set(doc.id)
|
||||
expect(modal.componentInstance.versionID()).toBe(10)
|
||||
modal.componentInstance.pages = [{ page: 1, rotate: 0, splitAfter: false }]
|
||||
modal.componentInstance.confirm()
|
||||
let req = httpTestingController.expectOne(
|
||||
@@ -1716,7 +1716,7 @@ describe('DocumentDetailComponent', () => {
|
||||
expect(errorSpy).toHaveBeenCalled()
|
||||
|
||||
component.editPdf()
|
||||
modal.componentInstance.documentID = doc.id
|
||||
modal.componentInstance.documentID.set(doc.id)
|
||||
modal.componentInstance.pages = [{ page: 1, rotate: 0, splitAfter: true }]
|
||||
modal.componentInstance.deleteOriginal = true
|
||||
modal.componentInstance.confirm()
|
||||
|
||||
@@ -1994,8 +1994,8 @@ export class DocumentDetailComponent
|
||||
const sourceDocumentId = this.selectedVersionId ?? this.document.id
|
||||
modal.componentInstance.title = $localize`PDF Editor`
|
||||
modal.componentInstance.btnCaption = $localize`Proceed`
|
||||
modal.componentInstance.documentID = this.document.id
|
||||
modal.componentInstance.versionID = sourceDocumentId
|
||||
modal.componentInstance.documentID.set(this.document.id)
|
||||
modal.componentInstance.versionID.set(sourceDocumentId)
|
||||
modal.componentInstance.confirmClicked
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe(() => {
|
||||
|
||||
@@ -1146,7 +1146,7 @@ describe('BulkEditorComponent', () => {
|
||||
fixture.detectChanges()
|
||||
component.mergeSelected()
|
||||
expect(modal).not.toBeUndefined()
|
||||
modal.componentInstance.metadataDocumentID = 3
|
||||
modal.componentInstance.metadataDocumentID.set(3)
|
||||
modal.componentInstance.confirm()
|
||||
let req = httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}documents/merge/`
|
||||
@@ -1164,7 +1164,7 @@ describe('BulkEditorComponent', () => {
|
||||
) // listAllFilteredIds
|
||||
|
||||
// Test with Delete Originals enabled
|
||||
modal.componentInstance.deleteOriginals = true
|
||||
modal.componentInstance.deleteOriginals.set(true)
|
||||
modal.componentInstance.confirm()
|
||||
req = httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}documents/merge/`
|
||||
@@ -1184,8 +1184,8 @@ describe('BulkEditorComponent', () => {
|
||||
expect(documentListViewService.selected.size).toEqual(0)
|
||||
|
||||
// Test with archiveFallback enabled
|
||||
modal.componentInstance.deleteOriginals = false
|
||||
modal.componentInstance.archiveFallback = true
|
||||
modal.componentInstance.deleteOriginals.set(false)
|
||||
modal.componentInstance.archiveFallback.set(true)
|
||||
modal.componentInstance.confirm()
|
||||
req = httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}documents/merge/`
|
||||
@@ -1629,6 +1629,9 @@ describe('BulkEditorComponent', () => {
|
||||
close: jest.fn(),
|
||||
componentInstance: {
|
||||
documents: [],
|
||||
setDocuments(docs) {
|
||||
this.documents = docs
|
||||
},
|
||||
confirmClicked,
|
||||
payload: {
|
||||
document_ids: [5, 7],
|
||||
@@ -1637,7 +1640,7 @@ describe('BulkEditorComponent', () => {
|
||||
},
|
||||
loading: signal(false),
|
||||
buttonsEnabled: true,
|
||||
copied: false,
|
||||
copied: signal(false),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1698,6 +1701,9 @@ describe('BulkEditorComponent', () => {
|
||||
const modalRef: Partial<NgbModalRef> = {
|
||||
componentInstance: {
|
||||
documents: [],
|
||||
setDocuments(docs) {
|
||||
this.documents = docs
|
||||
},
|
||||
confirmClicked,
|
||||
payload: {
|
||||
document_ids: [9],
|
||||
|
||||
@@ -956,24 +956,24 @@ export class BulkEditorComponent
|
||||
mergeDialog.title = $localize`Merge confirm`
|
||||
mergeDialog.messageBold = $localize`This operation will merge ${this.getSelectionSize()} selected documents into a new document.`
|
||||
mergeDialog.btnCaption = $localize`Proceed`
|
||||
mergeDialog.documentIDs = Array.from(this.list.selected)
|
||||
mergeDialog.documentIDs.set(Array.from(this.list.selected))
|
||||
mergeDialog.confirmClicked
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe(() => {
|
||||
const args: MergeDocumentsRequest = {}
|
||||
if (mergeDialog.metadataDocumentID > -1) {
|
||||
args.metadata_document_id = mergeDialog.metadataDocumentID
|
||||
if (mergeDialog.metadataDocumentID() > -1) {
|
||||
args.metadata_document_id = mergeDialog.metadataDocumentID()
|
||||
}
|
||||
if (mergeDialog.deleteOriginals) {
|
||||
if (mergeDialog.deleteOriginals()) {
|
||||
args.delete_originals = true
|
||||
}
|
||||
if (mergeDialog.archiveFallback) {
|
||||
if (mergeDialog.archiveFallback()) {
|
||||
args.archive_fallback = true
|
||||
}
|
||||
mergeDialog.buttonsEnabled = false
|
||||
this.executeDocumentAction(
|
||||
modal,
|
||||
this.documentService.mergeDocuments(mergeDialog.documentIDs, args),
|
||||
this.documentService.mergeDocuments(mergeDialog.documentIDs(), args),
|
||||
{ deleteOriginals: !!args.delete_originals }
|
||||
)
|
||||
this.toastService.showInfo(
|
||||
@@ -1029,7 +1029,7 @@ export class BulkEditorComponent
|
||||
const selectedDocuments = this.list.documents.filter((d) =>
|
||||
this.list.selected.has(d.id)
|
||||
)
|
||||
dialog.documents = selectedDocuments
|
||||
dialog.setDocuments(selectedDocuments)
|
||||
dialog.confirmClicked
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe(() => {
|
||||
@@ -1043,7 +1043,7 @@ export class BulkEditorComponent
|
||||
dialog.loading.set(false)
|
||||
dialog.buttonsEnabled = false
|
||||
dialog.createdBundle = result
|
||||
dialog.copied = false
|
||||
dialog.copied.set(false)
|
||||
dialog.payload = null
|
||||
dialog.onOpenManage = () => {
|
||||
modal.close()
|
||||
|
||||
@@ -730,7 +730,9 @@ describe('DocumentListComponent', () => {
|
||||
showInSideBar: true,
|
||||
})
|
||||
expect(updateVisibilitySpy).not.toHaveBeenCalled()
|
||||
expect(openModal.componentInstance.error).toEqual({ filter_rules: ['11'] })
|
||||
expect(openModal.componentInstance.error()).toEqual({
|
||||
filter_rules: ['11'],
|
||||
})
|
||||
})
|
||||
|
||||
it('should detect saved view changes', () => {
|
||||
|
||||
@@ -449,9 +449,11 @@ export class DocumentListComponent
|
||||
let modal = this.modalService.open(SaveViewConfigDialogComponent, {
|
||||
backdrop: 'static',
|
||||
})
|
||||
modal.componentInstance.defaultName = this.filterEditor.generateFilterName()
|
||||
modal.componentInstance.setDefaultName(
|
||||
this.filterEditor.generateFilterName()
|
||||
)
|
||||
modal.componentInstance.saveClicked.pipe(first()).subscribe((formValue) => {
|
||||
modal.componentInstance.buttonsEnabled = false
|
||||
modal.componentInstance.buttonsEnabled.set(false)
|
||||
let savedView: SavedView = {
|
||||
name: formValue.name,
|
||||
filter_rules: this.list.filterRules,
|
||||
@@ -502,8 +504,8 @@ export class DocumentListComponent
|
||||
if (error.filter_rules) {
|
||||
error.filter_rules = error.filter_rules.map((r) => r.value)
|
||||
}
|
||||
modal.componentInstance.error = error
|
||||
modal.componentInstance.buttonsEnabled = true
|
||||
modal.componentInstance.error.set(error)
|
||||
modal.componentInstance.buttonsEnabled.set(true)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
+6
-6
@@ -1,24 +1,24 @@
|
||||
<form [formGroup]="saveViewConfigForm" (ngSubmit)="save()">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title" i18n>Save current view</h4>
|
||||
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
|
||||
<button type="button" [disabled]="!closeEnabled()" class="btn-close" aria-label="Close" (click)="cancel()">
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error?.name" autocomplete="off"></pngx-input-text>
|
||||
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error()?.name" autocomplete="off"></pngx-input-text>
|
||||
<pngx-input-check i18n-title title="Show in sidebar" formControlName="showInSideBar"></pngx-input-check>
|
||||
<pngx-input-check i18n-title title="Show on dashboard" formControlName="showOnDashboard"></pngx-input-check>
|
||||
<pngx-permissions-form accordion="true" formControlName="permissions_form"></pngx-permissions-form>
|
||||
@if (error?.filter_rules) {
|
||||
@if (error()?.filter_rules) {
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<h6 class="alert-heading" i18n>Filter rules error occurred while saving this view</h6>
|
||||
<ng-container i18n>The error returned was</ng-container>:<br/>
|
||||
{{ error.filter_rules }}
|
||||
{{ error().filter_rules }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="!buttonsEnabled">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" i18n [disabled]="!buttonsEnabled">Save</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="!buttonsEnabled()">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" i18n [disabled]="!buttonsEnabled()">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
+2
-2
@@ -63,9 +63,9 @@ describe('SaveViewConfigDialogComponent', () => {
|
||||
const name = 'Tag: Inbox'
|
||||
let result
|
||||
component.saveClicked.subscribe((saveResult) => (result = saveResult))
|
||||
component.defaultName = name
|
||||
component.setDefaultName(name)
|
||||
component.save()
|
||||
expect(component.defaultName).toEqual(name)
|
||||
expect(component.defaultName()).toEqual(name)
|
||||
expect(result).toEqual({
|
||||
name,
|
||||
showInSideBar: false,
|
||||
|
||||
+7
-39
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
inject,
|
||||
@@ -33,49 +32,18 @@ import { TextComponent } from '../../common/input/text/text.component'
|
||||
})
|
||||
export class SaveViewConfigDialogComponent implements OnInit {
|
||||
private modal = inject(NgbActiveModal)
|
||||
private errorSignal = signal(undefined)
|
||||
private buttonsEnabledSignal = signal(true)
|
||||
private defaultNameSignal = signal('')
|
||||
private closeEnabledSignal = signal(false)
|
||||
readonly error = signal(undefined)
|
||||
readonly buttonsEnabled = signal(true)
|
||||
readonly defaultName = signal('')
|
||||
readonly closeEnabled = signal(false)
|
||||
|
||||
@Output()
|
||||
public saveClicked = new EventEmitter()
|
||||
|
||||
@Input()
|
||||
get error() {
|
||||
return this.errorSignal()
|
||||
}
|
||||
|
||||
set error(error) {
|
||||
this.errorSignal.set(error)
|
||||
}
|
||||
|
||||
@Input()
|
||||
get buttonsEnabled(): boolean {
|
||||
return this.buttonsEnabledSignal()
|
||||
}
|
||||
|
||||
set buttonsEnabled(buttonsEnabled: boolean) {
|
||||
this.buttonsEnabledSignal.set(buttonsEnabled)
|
||||
}
|
||||
|
||||
get closeEnabled(): boolean {
|
||||
return this.closeEnabledSignal()
|
||||
}
|
||||
|
||||
set closeEnabled(closeEnabled: boolean) {
|
||||
this.closeEnabledSignal.set(closeEnabled)
|
||||
}
|
||||
|
||||
users: User[]
|
||||
|
||||
get defaultName() {
|
||||
return this.defaultNameSignal()
|
||||
}
|
||||
|
||||
@Input()
|
||||
set defaultName(value: string) {
|
||||
this.defaultNameSignal.set(value)
|
||||
setDefaultName(value: string) {
|
||||
this.defaultName.set(value)
|
||||
this.saveViewConfigForm.patchValue({ name: value })
|
||||
}
|
||||
|
||||
@@ -89,7 +57,7 @@ export class SaveViewConfigDialogComponent implements OnInit {
|
||||
ngOnInit(): void {
|
||||
// wait to enable close button so it doesn't steal focus from input since its the first clickable element in the DOM
|
||||
setTimeout(() => {
|
||||
this.closeEnabled = true
|
||||
this.closeEnabled.set(true)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@if (notes) {
|
||||
@if (notes()) {
|
||||
<div>
|
||||
<form [formGroup]="noteForm" class="needs-validation mt-3" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.Note }" novalidate>
|
||||
<div class="form-group">
|
||||
@@ -8,14 +8,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mt-2 d-flex justify-content-end align-items-center">
|
||||
@if (networkActive) {
|
||||
@if (networkActive()) {
|
||||
<div class="spinner-border spinner-border-sm fw-normal me-auto" role="status"></div>
|
||||
}
|
||||
<button type="button" class="btn btn-primary btn-sm" [disabled]="networkActive || addDisabled" (click)="addNote()" i18n>Add note</button>
|
||||
<button type="button" class="btn btn-primary btn-sm" [disabled]="networkActive() || addDisabled()" (click)="addNote()" i18n>Add note</button>
|
||||
</div>
|
||||
</form>
|
||||
<hr>
|
||||
@for (note of notes; track note) {
|
||||
@for (note of notes(); track note) {
|
||||
<div class="card border mb-3">
|
||||
<div class="card-body text-dark">
|
||||
<p class="card-text">{{note.note}}</p>
|
||||
|
||||
@@ -110,7 +110,7 @@ describe('DocumentNotesComponent', () => {
|
||||
})
|
||||
|
||||
it('should display notes with user name / username', () => {
|
||||
component.notes = notes
|
||||
component.notes.set(notes)
|
||||
fixture.detectChanges()
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain(
|
||||
notes[0].note
|
||||
@@ -154,7 +154,7 @@ describe('DocumentNotesComponent', () => {
|
||||
})
|
||||
|
||||
it('should support note entry, show error if fails', () => {
|
||||
component.documentId = 12
|
||||
component.documentId.set(12)
|
||||
const note = 'This is the new note.'
|
||||
const noteTextArea = fixture.debugElement.query(By.css('textarea'))
|
||||
noteTextArea.nativeElement.value = note
|
||||
@@ -177,7 +177,7 @@ describe('DocumentNotesComponent', () => {
|
||||
})
|
||||
|
||||
it('should support note save on ctrl+Enter', () => {
|
||||
component.documentId = 12
|
||||
component.documentId.set(12)
|
||||
const note = 'This is the new note.'
|
||||
const noteTextArea = fixture.debugElement.query(By.css('textarea'))
|
||||
noteTextArea.nativeElement.value = note
|
||||
@@ -189,8 +189,8 @@ describe('DocumentNotesComponent', () => {
|
||||
})
|
||||
|
||||
it('should support delete note, show error if fails', () => {
|
||||
component.documentId = 12
|
||||
component.notes = notes
|
||||
component.documentId.set(12)
|
||||
component.notes.set(notes)
|
||||
fixture.detectChanges()
|
||||
const deleteButton = fixture.debugElement.queryAll(By.css('button'))[1] // 0 is add button
|
||||
const deleteSpy = jest.spyOn(notesService, 'deleteNote')
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
inject,
|
||||
input,
|
||||
model,
|
||||
signal,
|
||||
} from '@angular/core'
|
||||
import {
|
||||
@@ -38,52 +39,17 @@ export class DocumentNotesComponent extends ComponentWithPermissions {
|
||||
private notesService = inject(DocumentNotesService)
|
||||
private toastService = inject(ToastService)
|
||||
private usersService = inject(UserService)
|
||||
private documentIdSignal = signal<number>(undefined)
|
||||
private notesSignal = signal<DocumentNote[]>([])
|
||||
private addDisabledSignal = signal(false)
|
||||
private networkActiveSignal = signal(false)
|
||||
readonly documentId = model<number>(undefined)
|
||||
readonly notes = model<DocumentNote[]>([])
|
||||
readonly addDisabled = input(false)
|
||||
readonly networkActive = signal(false)
|
||||
|
||||
noteForm: FormGroup = new FormGroup({
|
||||
newNote: new FormControl(''),
|
||||
})
|
||||
|
||||
get networkActive(): boolean {
|
||||
return this.networkActiveSignal()
|
||||
}
|
||||
|
||||
set networkActive(networkActive: boolean) {
|
||||
this.networkActiveSignal.set(networkActive)
|
||||
}
|
||||
|
||||
newNoteError: boolean = false
|
||||
|
||||
@Input()
|
||||
get documentId(): number {
|
||||
return this.documentIdSignal()
|
||||
}
|
||||
|
||||
set documentId(documentId: number) {
|
||||
this.documentIdSignal.set(documentId)
|
||||
}
|
||||
|
||||
@Input()
|
||||
get notes(): DocumentNote[] {
|
||||
return this.notesSignal()
|
||||
}
|
||||
|
||||
set notes(notes: DocumentNote[]) {
|
||||
this.notesSignal.set(notes)
|
||||
}
|
||||
|
||||
@Input()
|
||||
get addDisabled(): boolean {
|
||||
return this.addDisabledSignal()
|
||||
}
|
||||
|
||||
set addDisabled(addDisabled: boolean) {
|
||||
this.addDisabledSignal.set(addDisabled)
|
||||
}
|
||||
|
||||
@Output()
|
||||
updated: EventEmitter<DocumentNote[]> = new EventEmitter()
|
||||
users: User[]
|
||||
@@ -104,30 +70,30 @@ export class DocumentNotesComponent extends ComponentWithPermissions {
|
||||
return
|
||||
}
|
||||
this.newNoteError = false
|
||||
this.networkActive = true
|
||||
this.notesService.addNote(this.documentId, note).subscribe({
|
||||
this.networkActive.set(true)
|
||||
this.notesService.addNote(this.documentId(), note).subscribe({
|
||||
next: (result) => {
|
||||
this.notes = result
|
||||
this.notes.set(result)
|
||||
this.noteForm.get('newNote').reset()
|
||||
this.networkActive = false
|
||||
this.updated.emit(this.notes)
|
||||
this.networkActive.set(false)
|
||||
this.updated.emit(this.notes())
|
||||
},
|
||||
error: (e) => {
|
||||
this.networkActive = false
|
||||
this.networkActive.set(false)
|
||||
this.toastService.showError($localize`Error saving note`, e)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
deleteNote(noteId: number) {
|
||||
this.notesService.deleteNote(this.documentId, noteId).subscribe({
|
||||
this.notesService.deleteNote(this.documentId(), noteId).subscribe({
|
||||
next: (result) => {
|
||||
this.notes = result
|
||||
this.networkActive = false
|
||||
this.updated.emit(this.notes)
|
||||
this.notes.set(result)
|
||||
this.networkActive.set(false)
|
||||
this.updated.emit(this.notes())
|
||||
},
|
||||
error: (e) => {
|
||||
this.networkActive = false
|
||||
this.networkActive.set(false)
|
||||
this.toastService.showError($localize`Error deleting note`, e)
|
||||
},
|
||||
})
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@
|
||||
}
|
||||
</pngx-page-header>
|
||||
|
||||
<ul ngbNav #nav="ngbNav" (navChange)="onNavChange($event)" [(activeId)]="activeNavID" class="nav-underline">
|
||||
<ul ngbNav #nav="ngbNav" (navChange)="onNavChange($event)" [activeId]="activeNavID()" (activeIdChange)="activeNavID.set($event)" class="nav-underline">
|
||||
@for (section of visibleSections; track section.id) {
|
||||
<li [ngbNavItem]="section.id">
|
||||
<a ngbNavLink >
|
||||
|
||||
+6
-6
@@ -100,7 +100,7 @@ describe('DocumentAttributesComponent', () => {
|
||||
expect(router.navigate).toHaveBeenCalledWith(['attributes', 'tags'], {
|
||||
replaceUrl: true,
|
||||
})
|
||||
expect(component.activeNavID).toBe(1)
|
||||
expect(component.activeNavID()).toBe(1)
|
||||
})
|
||||
|
||||
it('should set active section from route param when valid', () => {
|
||||
@@ -116,7 +116,7 @@ describe('DocumentAttributesComponent', () => {
|
||||
fixture.detectChanges()
|
||||
paramMapSubject.next(convertToParamMap({ section: 'customfields' }))
|
||||
|
||||
expect(component.activeNavID).toBe(2)
|
||||
expect(component.activeNavID()).toBe(2)
|
||||
expect(router.navigate).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -124,10 +124,10 @@ describe('DocumentAttributesComponent', () => {
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||
|
||||
fixture.detectChanges()
|
||||
component.activeNavID = 1
|
||||
component.activeNavID.set(1)
|
||||
paramMapSubject.next(convertToParamMap({ section: 'customfields' }))
|
||||
|
||||
expect(component.activeNavID).toBe(2)
|
||||
expect(component.activeNavID()).toBe(2)
|
||||
})
|
||||
|
||||
it('should redirect to dashboard when no sections are visible', () => {
|
||||
@@ -169,7 +169,7 @@ describe('DocumentAttributesComponent', () => {
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||
expect(component.activeManagementList).toBeNull()
|
||||
|
||||
component.activeNavID = 1
|
||||
component.activeNavID.set(1)
|
||||
expect(component.activeSection.kind).toBe(
|
||||
DocumentAttributesSectionKind.ManagementList
|
||||
)
|
||||
@@ -180,7 +180,7 @@ describe('DocumentAttributesComponent', () => {
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||
expect(component.activeCustomFields).toBeNull()
|
||||
|
||||
component.activeNavID = 2
|
||||
component.activeNavID.set(2)
|
||||
expect(component.activeSection.kind).toBe(
|
||||
DocumentAttributesSectionKind.CustomFields
|
||||
)
|
||||
|
||||
+11
-18
@@ -133,19 +133,11 @@ export class DocumentAttributesComponent implements OnInit, OnDestroy {
|
||||
|
||||
@ViewChild('activeOutlet', { read: NgComponentOutlet })
|
||||
set activeOutlet(outlet: NgComponentOutlet | undefined) {
|
||||
this.activeComponentSignal.set(outlet?.componentInstance ?? null)
|
||||
this.activeComponent.set(outlet?.componentInstance ?? null)
|
||||
}
|
||||
|
||||
private activeComponentSignal = signal<unknown>(null)
|
||||
private activeNavIDSignal = signal<number>(null)
|
||||
|
||||
get activeNavID(): number {
|
||||
return this.activeNavIDSignal()
|
||||
}
|
||||
|
||||
set activeNavID(activeNavID: number) {
|
||||
this.activeNavIDSignal.set(activeNavID)
|
||||
}
|
||||
readonly activeComponent = signal<unknown>(null)
|
||||
readonly activeNavID = signal<number>(null)
|
||||
|
||||
get visibleSections(): DocumentAttributesSection[] {
|
||||
return this.sections.filter((section) =>
|
||||
@@ -158,8 +150,9 @@ export class DocumentAttributesComponent implements OnInit, OnDestroy {
|
||||
|
||||
get activeSection(): DocumentAttributesSection | null {
|
||||
return (
|
||||
this.visibleSections.find((section) => section.id === this.activeNavID) ??
|
||||
null
|
||||
this.visibleSections.find(
|
||||
(section) => section.id === this.activeNavID()
|
||||
) ?? null
|
||||
)
|
||||
}
|
||||
|
||||
@@ -168,14 +161,14 @@ export class DocumentAttributesComponent implements OnInit, OnDestroy {
|
||||
this.activeSection?.kind !== DocumentAttributesSectionKind.ManagementList
|
||||
)
|
||||
return null
|
||||
const instance = this.activeComponentSignal()
|
||||
const instance = this.activeComponent()
|
||||
return instance instanceof ManagementListComponent ? instance : null
|
||||
}
|
||||
|
||||
get activeCustomFields(): CustomFieldsComponent | null {
|
||||
if (this.activeSection?.kind !== DocumentAttributesSectionKind.CustomFields)
|
||||
return null
|
||||
const instance = this.activeComponentSignal()
|
||||
const instance = this.activeComponent()
|
||||
return instance instanceof CustomFieldsComponent ? instance : null
|
||||
}
|
||||
|
||||
@@ -208,13 +201,13 @@ export class DocumentAttributesComponent implements OnInit, OnDestroy {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.activeNavID !== navIDFromSection) {
|
||||
this.activeNavID = navIDFromSection
|
||||
if (this.activeNavID() !== navIDFromSection) {
|
||||
this.activeNavID.set(navIDFromSection)
|
||||
}
|
||||
|
||||
if (!section || this.getNavIDForSection(section) == null) {
|
||||
this.router.navigate(
|
||||
['attributes', this.getSectionForNavID(this.activeNavID)],
|
||||
['attributes', this.getSectionForNavID(this.activeNavID())],
|
||||
{ replaceUrl: true }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</li>
|
||||
}
|
||||
|
||||
@for (account of mailAccounts; track account) {
|
||||
@for (account of mailAccounts(); track account) {
|
||||
<li class="list-group-item">
|
||||
<div class="row fade" [class.show]="showAccounts()">
|
||||
<div class="col d-flex align-items-center">
|
||||
@@ -91,7 +91,7 @@
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
@if (!loadingAccounts() && mailAccounts.length === 0) {
|
||||
@if (!loadingAccounts() && mailAccounts().length === 0) {
|
||||
<li class="list-group-item" i18n>No mail accounts defined.</li>
|
||||
}
|
||||
</ul>
|
||||
@@ -186,7 +186,7 @@
|
||||
|
||||
</ng-container>
|
||||
|
||||
@if (!mailAccounts || !mailRules) {
|
||||
@if (!mailAccounts() || !mailRules()) {
|
||||
<div>
|
||||
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
||||
<div class="visually-hidden" i18n>Loading...</div>
|
||||
|
||||
@@ -56,13 +56,10 @@ export class MailComponent
|
||||
|
||||
public MailAccountType = MailAccountType
|
||||
|
||||
private mailAccountsSignal = signal<MailAccount[]>([])
|
||||
readonly mailAccounts = signal<MailAccount[]>([])
|
||||
|
||||
public get mailAccounts() {
|
||||
return this.mailAccountsSignal()
|
||||
}
|
||||
private set mailAccounts(accounts: MailAccount[]) {
|
||||
this.mailAccountsSignal.set(accounts)
|
||||
private setMailAccounts(accounts: MailAccount[]) {
|
||||
this.mailAccounts.set(accounts)
|
||||
this.mailAccountsById.set(
|
||||
new Map(accounts.map((account) => [account.id, account]))
|
||||
)
|
||||
@@ -94,12 +91,12 @@ export class MailComponent
|
||||
first(),
|
||||
takeUntil(this.unsubscribeNotifier),
|
||||
tap((r) => {
|
||||
this.mailAccounts = r.results
|
||||
this.setMailAccounts(r.results)
|
||||
this.loadingAccounts.set(false)
|
||||
this.showAccounts.set(true)
|
||||
if (this.oAuthAccountId) {
|
||||
this.editMailAccount(
|
||||
this.mailAccounts.find(
|
||||
this.mailAccounts().find(
|
||||
(account) => account.id === this.oAuthAccountId
|
||||
)
|
||||
)
|
||||
@@ -140,9 +137,9 @@ export class MailComponent
|
||||
if (success) {
|
||||
this.toastService.showInfo($localize`OAuth2 authentication success`)
|
||||
this.oAuthAccountId = parseInt(params.get('account_id'))
|
||||
if (this.mailAccounts.length > 0) {
|
||||
if (this.mailAccounts().length > 0) {
|
||||
this.editMailAccount(
|
||||
this.mailAccounts.find(
|
||||
this.mailAccounts().find(
|
||||
(account) => account.id === this.oAuthAccountId
|
||||
)
|
||||
)
|
||||
@@ -179,7 +176,7 @@ export class MailComponent
|
||||
this.mailAccountService
|
||||
.listAll(null, null, { full_perms: true })
|
||||
.subscribe((r) => {
|
||||
this.mailAccounts = r.results
|
||||
this.setMailAccounts(r.results)
|
||||
})
|
||||
})
|
||||
modal.componentInstance.failed
|
||||
@@ -210,7 +207,7 @@ export class MailComponent
|
||||
this.mailAccountService
|
||||
.listAll(null, null, { full_perms: true })
|
||||
.subscribe((r) => {
|
||||
this.mailAccounts = r.results
|
||||
this.setMailAccounts(r.results)
|
||||
})
|
||||
},
|
||||
error: (e) => {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</pngx-page-header>
|
||||
<form [formGroup]="savedViewsForm" (ngSubmit)="save()">
|
||||
<ul class="list-group mb-3" formGroupName="savedViews">
|
||||
@for (view of savedViews; track view) {
|
||||
@for (view of savedViews(); track view) {
|
||||
<li class="list-group-item py-3">
|
||||
<div [formGroupName]="view.id">
|
||||
<div class="row">
|
||||
@@ -65,7 +65,7 @@
|
||||
</li>
|
||||
}
|
||||
|
||||
@if (savedViews && savedViews.length === 0) {
|
||||
@if (savedViews() && savedViews().length === 0) {
|
||||
<li class="list-group-item">
|
||||
<div i18n>No saved views defined.</div>
|
||||
</li>
|
||||
|
||||
@@ -56,7 +56,7 @@ export class SavedViewsComponent
|
||||
|
||||
DisplayMode = DisplayMode
|
||||
|
||||
private savedViewsSignal = signal<SavedView[]>(undefined)
|
||||
readonly savedViews = signal<SavedView[]>(undefined)
|
||||
private savedViewsGroup = new FormGroup({})
|
||||
public savedViewsForm: FormGroup = new FormGroup({
|
||||
savedViews: this.savedViewsGroup,
|
||||
@@ -69,14 +69,6 @@ export class SavedViewsComponent
|
||||
return this.settings.allDisplayFields()
|
||||
}
|
||||
|
||||
public get savedViews(): SavedView[] {
|
||||
return this.savedViewsSignal()
|
||||
}
|
||||
|
||||
public set savedViews(savedViews: SavedView[]) {
|
||||
this.savedViewsSignal.set(savedViews)
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.settings.organizingSidebarSavedViews.set(true)
|
||||
@@ -91,7 +83,7 @@ export class SavedViewsComponent
|
||||
this.savedViewService
|
||||
.list(null, null, null, false, { full_perms: true })
|
||||
.subscribe((r) => {
|
||||
this.savedViews = r.results
|
||||
this.savedViews.set(r.results)
|
||||
this.initialize()
|
||||
})
|
||||
}
|
||||
@@ -109,7 +101,7 @@ export class SavedViewsComponent
|
||||
savedViews: {},
|
||||
}
|
||||
|
||||
for (let view of this.savedViews) {
|
||||
for (let view of this.savedViews()) {
|
||||
storeData.savedViews[view.id.toString()] = {
|
||||
id: view.id,
|
||||
name: view.name,
|
||||
@@ -156,7 +148,9 @@ export class SavedViewsComponent
|
||||
public deleteSavedView(savedView: SavedView) {
|
||||
this.savedViewService.delete(savedView).subscribe(() => {
|
||||
this.savedViewsGroup.removeControl(savedView.id.toString())
|
||||
this.savedViews.splice(this.savedViews.indexOf(savedView), 1)
|
||||
this.savedViews.update((savedViews) =>
|
||||
savedViews.filter((view) => view !== savedView)
|
||||
)
|
||||
this.toastService.showInfo(
|
||||
$localize`Saved view "${savedView.name}" deleted.`
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user