mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-07-09 05:25:10 +00:00
Direct signals in lots more components
This commit is contained in:
@@ -97,11 +97,11 @@ export class ConfigComponent
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe({
|
||||
next: (config) => {
|
||||
this.loading = false
|
||||
this.loading.set(false)
|
||||
this.initialize(config)
|
||||
},
|
||||
error: (e) => {
|
||||
this.loading = false
|
||||
this.loading.set(false)
|
||||
this.toastService.showError($localize`Error retrieving config`, e)
|
||||
},
|
||||
})
|
||||
@@ -162,20 +162,20 @@ export class ConfigComponent
|
||||
}
|
||||
|
||||
public saveConfig() {
|
||||
this.loading = true
|
||||
this.loading.set(true)
|
||||
this.configService
|
||||
.saveConfig(this.configForm.value as PaperlessConfig)
|
||||
.pipe(takeUntil(this.unsubscribeNotifier), first())
|
||||
.subscribe({
|
||||
next: (config) => {
|
||||
this.loading = false
|
||||
this.loading.set(false)
|
||||
this.initialize(config)
|
||||
this.store.next(config)
|
||||
this.settingsService.initializeSettings().subscribe()
|
||||
this.toastService.showInfo($localize`Configuration updated`)
|
||||
},
|
||||
error: (e) => {
|
||||
this.loading = false
|
||||
this.loading.set(false)
|
||||
this.toastService.showError(
|
||||
$localize`An error occurred updating configuration`,
|
||||
e
|
||||
@@ -189,20 +189,20 @@ export class ConfigComponent
|
||||
}
|
||||
|
||||
public uploadFile(file: File, key: string) {
|
||||
this.loading = true
|
||||
this.loading.set(true)
|
||||
this.configService
|
||||
.uploadFile(file, this.configForm.value['id'], key)
|
||||
.pipe(takeUntil(this.unsubscribeNotifier), first())
|
||||
.subscribe({
|
||||
next: (config) => {
|
||||
this.loading = false
|
||||
this.loading.set(false)
|
||||
this.initialize(config)
|
||||
this.store.next(config)
|
||||
this.settingsService.initializeSettings().subscribe()
|
||||
this.toastService.showInfo($localize`File successfully updated`)
|
||||
},
|
||||
error: (e) => {
|
||||
this.loading = false
|
||||
this.loading.set(false)
|
||||
this.toastService.showError(
|
||||
$localize`An error occurred uploading file`,
|
||||
e
|
||||
|
||||
@@ -61,7 +61,7 @@ export class LogsComponent
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe((result) => {
|
||||
this.logFiles.set(result)
|
||||
this.loading = false
|
||||
this.loading.set(false)
|
||||
if (this.logFiles().length > 0) {
|
||||
this.activeLog.set(this.logFiles()[0])
|
||||
this.reloadLogs()
|
||||
@@ -86,14 +86,14 @@ export class LogsComponent
|
||||
}
|
||||
|
||||
reloadLogs() {
|
||||
this.loading = true
|
||||
this.loading.set(true)
|
||||
const shouldStickToBottom = this.isNearBottom()
|
||||
this.logService
|
||||
.get(this.activeLog(), this.limit())
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe({
|
||||
next: (result) => {
|
||||
this.loading = false
|
||||
this.loading.set(false)
|
||||
const parsed = this.parseLogsWithLevel(result)
|
||||
const hasChanges =
|
||||
parsed.length !== this.logs().length ||
|
||||
@@ -115,7 +115,7 @@ export class LogsComponent
|
||||
},
|
||||
error: () => {
|
||||
this.logs.set([])
|
||||
this.loading = false
|
||||
this.loading.set(false)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ describe('SettingsComponent', () => {
|
||||
viewportScroller = TestBed.inject(ViewportScroller)
|
||||
toastService = TestBed.inject(ToastService)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
settingsService.currentUser = users[0]
|
||||
settingsService.currentUser.set(users[0])
|
||||
userService = TestBed.inject(UserService)
|
||||
permissionsService = TestBed.inject(PermissionsService)
|
||||
modalService = TestBed.inject(NgbModal)
|
||||
|
||||
@@ -732,7 +732,7 @@ export class TasksComponent
|
||||
|
||||
this.reloadSectionCounts()
|
||||
|
||||
this.loading = true
|
||||
this.loading.set(true)
|
||||
this.tasksService
|
||||
.list(
|
||||
this.page,
|
||||
@@ -748,7 +748,7 @@ export class TasksComponent
|
||||
if (this.selectedSection !== TaskSection.All) {
|
||||
this.setSectionCount(this.selectedSection, result.count)
|
||||
}
|
||||
this.loading = false
|
||||
this.loading.set(false)
|
||||
if (
|
||||
this.page > 1 &&
|
||||
this.pagedTasks().length === 0 &&
|
||||
@@ -759,7 +759,7 @@ export class TasksComponent
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
this.loading = false
|
||||
this.loading.set(false)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ export class TrashComponent
|
||||
}
|
||||
|
||||
reload() {
|
||||
this.loading = true
|
||||
this.loading.set(true)
|
||||
this.trashService
|
||||
.getTrash(this.page())
|
||||
.pipe(
|
||||
@@ -62,11 +62,11 @@ export class TrashComponent
|
||||
this.documentsInTrash.set(r.results)
|
||||
this.totalDocuments.set(r.count)
|
||||
this.selectedDocuments.set(new Set())
|
||||
this.loading = false
|
||||
this.loading.set(false)
|
||||
})
|
||||
)
|
||||
.subscribe(() => {
|
||||
this.show = true
|
||||
this.show.set(true)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ describe('UsersAndGroupsComponent', () => {
|
||||
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||
editDialog.failed.emit()
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
settingsService.currentUser = users[1] // simulate logged in as different user
|
||||
settingsService.currentUser.set(users[1]) // simulate logged in as different user
|
||||
editDialog.succeeded.emit(users[0])
|
||||
expect(toastInfoSpy).toHaveBeenCalledWith(
|
||||
`Saved user "${users[0].username}".`
|
||||
@@ -148,7 +148,7 @@ describe('UsersAndGroupsComponent', () => {
|
||||
.mockImplementation(() => {})
|
||||
const editDialog = modal.componentInstance as UserEditDialogComponent
|
||||
editDialog.passwordIsSet = true
|
||||
settingsService.currentUser = users[0] // simulate logged in as same user
|
||||
settingsService.currentUser.set(users[0]) // simulate logged in as same user
|
||||
editDialog.succeeded.emit(users[0])
|
||||
fixture.detectChanges()
|
||||
tick(2600)
|
||||
|
||||
@@ -109,7 +109,7 @@ export class UsersAndGroupsComponent
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe((newUser: User) => {
|
||||
if (
|
||||
newUser.id === this.settings.currentUser.id &&
|
||||
newUser.id === this.settings.currentUser().id &&
|
||||
(modal.componentInstance as UserEditDialogComponent).passwordIsSet
|
||||
) {
|
||||
this.toastService.showInfo(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<nav class="navbar navbar-dark fixed-top bg-primary flex-md-nowrap p-0 shadow-sm">
|
||||
<button class="navbar-toggler d-md-none collapsed border-0" type="button" data-toggle="collapse"
|
||||
data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation"
|
||||
(click)="mobileSearchHidden = false; isMenuCollapsed = !isMenuCollapsed">
|
||||
(click)="closeMobileSearch(); toggleMenuCollapsed()">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<a class="navbar-brand d-flex align-items-center me-0 px-3 py-3 order-sm-0"
|
||||
@@ -23,7 +23,7 @@
|
||||
</div>
|
||||
</a>
|
||||
<div class="search-container flex-grow-1 py-2 pb-3 pb-sm-2 px-3 ps-md-4 me-sm-auto order-3 order-sm-1"
|
||||
[class.mobile-hidden]="mobileSearchHidden">
|
||||
[class.mobile-hidden]="mobileSearchHidden()">
|
||||
<div class="col-12 col-md-7">
|
||||
<pngx-global-search></pngx-global-search>
|
||||
</div>
|
||||
@@ -68,8 +68,8 @@
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<nav id="sidebarMenu" class="d-md-block bg-light sidebar collapse"
|
||||
[ngClass]="slimSidebarEnabled ? 'slim' : 'col-md-3 col-lg-2 col-xxxl-1'" [class.animating]="slimSidebarAnimating"
|
||||
[ngbCollapse]="isMenuCollapsed">
|
||||
[ngClass]="slimSidebarEnabled ? 'slim' : 'col-md-3 col-lg-2 col-xxxl-1'" [class.animating]="slimSidebarAnimating()"
|
||||
[ngbCollapse]="isMenuCollapsed()">
|
||||
@if (canSaveSettings) {
|
||||
<button class="btn btn-sm btn-dark sidebar-slim-toggler" (click)="toggleSlimSidebar()">
|
||||
@if (slimSidebarEnabled) {
|
||||
@@ -104,7 +104,7 @@
|
||||
</h6>
|
||||
<ul class="nav flex-column mb-2" cdkDropList (cdkDropListDropped)="onDrop($event)">
|
||||
@for (view of savedViewService.sidebarViews; track view.id) {
|
||||
<li class="nav-item w-100 app-link" cdkDrag [cdkDragDisabled]="!settingsService.organizingSidebarSavedViews"
|
||||
<li class="nav-item w-100 app-link" cdkDrag [cdkDragDisabled]="!settingsService.organizingSidebarSavedViews()"
|
||||
cdkDragPreviewContainer="parent" cdkDragPreviewClass="navItemDrag" (cdkDragStarted)="onDragStart($event)"
|
||||
(cdkDragEnded)="onDragEnd($event)">
|
||||
<a class="nav-link" routerLink="view/{{view.id}}"
|
||||
@@ -120,7 +120,7 @@
|
||||
<span class="badge bg-info text-dark position-absolute top-0 end-0 d-none d-md-block">{{ savedViewService.getDocumentCount(view) }}</span>
|
||||
}
|
||||
</a>
|
||||
@if (settingsService.organizingSidebarSavedViews) {
|
||||
@if (settingsService.organizingSidebarSavedViews()) {
|
||||
<div class="position-absolute end-0 top-0 px-3 py-2" [class.me-n3]="slimSidebarEnabled" cdkDragHandle>
|
||||
<i-bs name="grip-vertical"></i-bs>
|
||||
</div>
|
||||
@@ -329,10 +329,10 @@
|
||||
{{ versionString }}
|
||||
</a>
|
||||
</div>
|
||||
@if (!settingsService.updateCheckingIsSet || appRemoteVersion) {
|
||||
@if (!settingsService.updateCheckingIsSet || appRemoteVersion()) {
|
||||
<div class="version-check">
|
||||
<ng-template #updateAvailablePopContent>
|
||||
<span class="small">Paperless-ngx {{ appRemoteVersion.version }} <ng-container i18n>is
|
||||
<span class="small">Paperless-ngx {{ appRemoteVersion().version }} <ng-container i18n>is
|
||||
available.</ng-container><br /><ng-container i18n>Click to view.</ng-container></span>
|
||||
</ng-template>
|
||||
<ng-template #updateCheckingNotEnabledPopContent>
|
||||
@@ -350,13 +350,13 @@
|
||||
</p>
|
||||
</ng-template>
|
||||
@if (settingsService.updateCheckingIsSet) {
|
||||
@if (appRemoteVersion.update_available) {
|
||||
@if (appRemoteVersion().update_available) {
|
||||
<a class="small text-decoration-none" target="_blank" rel="noopener noreferrer"
|
||||
href="https://github.com/paperless-ngx/paperless-ngx/releases"
|
||||
[ngbPopover]="updateAvailablePopContent" popoverClass="shadow" triggers="mouseenter:mouseleave"
|
||||
container="body">
|
||||
<i-bs width="1.2em" height="1.2em" name="info-circle" class="me-1"></i-bs>
|
||||
@if (appRemoteVersion?.update_available) {
|
||||
@if (appRemoteVersion()?.update_available) {
|
||||
<ng-container i18n>Update available</ng-container>
|
||||
}
|
||||
</a>
|
||||
@@ -377,7 +377,7 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main role="main" class="ms-sm-auto px-md-4" [class.mobile-search-hidden]="mobileSearchHidden"
|
||||
<main role="main" class="ms-sm-auto px-md-4" [class.mobile-search-hidden]="mobileSearchHidden()"
|
||||
[ngClass]="slimSidebarEnabled ? 'col-slim' : 'col-md-9 col-lg-10 col-xxxl-11'">
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
|
||||
@@ -90,48 +90,12 @@ export class AppFrameComponent
|
||||
permissionsService = inject(PermissionsService)
|
||||
private djangoMessagesService = inject(DjangoMessagesService)
|
||||
|
||||
private appRemoteVersionSignal = signal<AppRemoteVersion>(null)
|
||||
|
||||
private isMenuCollapsedSignal = signal(true)
|
||||
|
||||
private slimSidebarAnimatingSignal = signal(false)
|
||||
|
||||
private mobileSearchHiddenSignal = signal(false)
|
||||
|
||||
readonly appRemoteVersion = signal<AppRemoteVersion>(null)
|
||||
readonly isMenuCollapsed = signal(true)
|
||||
readonly slimSidebarAnimating = signal(false)
|
||||
readonly mobileSearchHidden = signal(false)
|
||||
private lastScrollY: number = 0
|
||||
|
||||
get appRemoteVersion(): AppRemoteVersion {
|
||||
return this.appRemoteVersionSignal()
|
||||
}
|
||||
|
||||
set appRemoteVersion(value: AppRemoteVersion) {
|
||||
this.appRemoteVersionSignal.set(value)
|
||||
}
|
||||
|
||||
get isMenuCollapsed(): boolean {
|
||||
return this.isMenuCollapsedSignal()
|
||||
}
|
||||
|
||||
set isMenuCollapsed(value: boolean) {
|
||||
this.isMenuCollapsedSignal.set(value)
|
||||
}
|
||||
|
||||
get slimSidebarAnimating(): boolean {
|
||||
return this.slimSidebarAnimatingSignal()
|
||||
}
|
||||
|
||||
set slimSidebarAnimating(value: boolean) {
|
||||
this.slimSidebarAnimatingSignal.set(value)
|
||||
}
|
||||
|
||||
get mobileSearchHidden(): boolean {
|
||||
return this.mobileSearchHiddenSignal()
|
||||
}
|
||||
|
||||
set mobileSearchHidden(value: boolean) {
|
||||
this.mobileSearchHiddenSignal.set(value)
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
const permissionsService = this.permissionsService
|
||||
@@ -179,7 +143,7 @@ export class AppFrameComponent
|
||||
}
|
||||
|
||||
toggleSlimSidebar(): void {
|
||||
this.slimSidebarAnimating = true
|
||||
this.slimSidebarAnimating.set(true)
|
||||
const slimSidebarEnabled = !this.slimSidebarEnabled
|
||||
this.settingsService.set(SETTINGS_KEYS.SLIM_SIDEBAR, slimSidebarEnabled)
|
||||
if (slimSidebarEnabled) {
|
||||
@@ -199,7 +163,7 @@ export class AppFrameComponent
|
||||
},
|
||||
})
|
||||
setTimeout(() => {
|
||||
this.slimSidebarAnimating = false
|
||||
this.slimSidebarAnimating.set(false)
|
||||
}, 200) // slightly longer than css animation for slim sidebar
|
||||
}
|
||||
|
||||
@@ -209,6 +173,18 @@ export class AppFrameComponent
|
||||
this.attributesSectionsCollapsed = !this.attributesSectionsCollapsed
|
||||
}
|
||||
|
||||
toggleMenuCollapsed(): void {
|
||||
this.isMenuCollapsed.set(!this.isMenuCollapsed())
|
||||
}
|
||||
|
||||
closeMobileSearch(): void {
|
||||
this.mobileSearchHidden.set(false)
|
||||
}
|
||||
|
||||
setMobileSearchHidden(hidden: boolean): void {
|
||||
this.mobileSearchHidden.set(hidden)
|
||||
}
|
||||
|
||||
get versionString(): string {
|
||||
this.settingsService.trackChanges()
|
||||
return `${environment.appTitle} v${this.settingsService.get(SETTINGS_KEYS.VERSION)}${environment.tag === 'prod' ? '' : ` #${environment.tag}`}`
|
||||
@@ -311,7 +287,7 @@ export class AppFrameComponent
|
||||
@HostListener('window:resize')
|
||||
onWindowResize(): void {
|
||||
if (!this.isMobileViewport()) {
|
||||
this.mobileSearchHidden = false
|
||||
this.mobileSearchHidden.set(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,8 +295,8 @@ export class AppFrameComponent
|
||||
onWindowScroll(): void {
|
||||
const currentScrollY = window.scrollY
|
||||
|
||||
if (!this.isMobileViewport() || this.isMenuCollapsed === false) {
|
||||
this.mobileSearchHidden = false
|
||||
if (!this.isMobileViewport() || this.isMenuCollapsed() === false) {
|
||||
this.mobileSearchHidden.set(false)
|
||||
this.lastScrollY = currentScrollY
|
||||
return
|
||||
}
|
||||
@@ -328,9 +304,9 @@ export class AppFrameComponent
|
||||
const delta = currentScrollY - this.lastScrollY
|
||||
|
||||
if (currentScrollY <= 0 || delta < -SCROLL_THRESHOLD) {
|
||||
this.mobileSearchHidden = false
|
||||
this.mobileSearchHidden.set(false)
|
||||
} else if (currentScrollY > SCROLL_THRESHOLD && delta > SCROLL_THRESHOLD) {
|
||||
this.mobileSearchHidden = true
|
||||
this.mobileSearchHidden.set(true)
|
||||
}
|
||||
|
||||
this.lastScrollY = currentScrollY
|
||||
@@ -341,7 +317,7 @@ export class AppFrameComponent
|
||||
}
|
||||
|
||||
closeMenu() {
|
||||
this.isMenuCollapsed = true
|
||||
this.isMenuCollapsed.set(true)
|
||||
}
|
||||
|
||||
editProfile() {
|
||||
@@ -404,11 +380,11 @@ export class AppFrameComponent
|
||||
}
|
||||
|
||||
onDragStart(event: CdkDragStart) {
|
||||
this.settingsService.globalDropzoneEnabled = false
|
||||
this.settingsService.globalDropzoneEnabled.set(false)
|
||||
}
|
||||
|
||||
onDragEnd(event: CdkDragEnd) {
|
||||
this.settingsService.globalDropzoneEnabled = true
|
||||
this.settingsService.globalDropzoneEnabled.set(true)
|
||||
}
|
||||
|
||||
onDrop(event: CdkDragDrop<SavedView[]>) {
|
||||
@@ -429,7 +405,7 @@ export class AppFrameComponent
|
||||
this.remoteVersionService
|
||||
.checkForUpdates()
|
||||
.subscribe((appRemoteVersion: AppRemoteVersion) => {
|
||||
this.appRemoteVersion = appRemoteVersion
|
||||
this.appRemoteVersion.set(appRemoteVersion)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -459,7 +435,7 @@ export class AppFrameComponent
|
||||
this.settingsService.trackChanges()
|
||||
return (
|
||||
this.settingsService.get(SETTINGS_KEYS.SIDEBAR_VIEWS_SHOW_COUNT) &&
|
||||
!this.settingsService.organizingSidebarSavedViews
|
||||
!this.settingsService.organizingSidebarSavedViews()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ describe('CustomFieldsDropdownComponent', () => {
|
||||
})
|
||||
)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
settingsService.currentUser = { id: 1, username: 'test' }
|
||||
settingsService.currentUser.set({ id: 1, username: 'test' })
|
||||
fixture = TestBed.createComponent(CustomFieldsDropdownComponent)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ describe('CorrespondentEditDialogComponent', () => {
|
||||
|
||||
fixture = TestBed.createComponent(CorrespondentEditDialogComponent)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
settingsService.currentUser = { id: 99, username: 'user99' }
|
||||
settingsService.currentUser.set({ id: 99, username: 'user99' })
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ describe('CustomFieldEditDialogComponent', () => {
|
||||
|
||||
fixture = TestBed.createComponent(CustomFieldEditDialogComponent)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
settingsService.currentUser = { id: 99, username: 'user99' }
|
||||
settingsService.currentUser.set({ id: 99, username: 'user99' })
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ describe('DocumentTypeEditDialogComponent', () => {
|
||||
|
||||
fixture = TestBed.createComponent(DocumentTypeEditDialogComponent)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
settingsService.currentUser = { id: 99, username: 'user99' }
|
||||
settingsService.currentUser.set({ id: 99, username: 'user99' })
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ describe('GroupEditDialogComponent', () => {
|
||||
|
||||
fixture = TestBed.createComponent(GroupEditDialogComponent)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
settingsService.currentUser = { id: 99, username: 'user99' }
|
||||
settingsService.currentUser.set({ id: 99, username: 'user99' })
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ describe('MailAccountEditDialogComponent', () => {
|
||||
|
||||
fixture = TestBed.createComponent(MailAccountEditDialogComponent)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
settingsService.currentUser = { id: 99, username: 'user99' }
|
||||
settingsService.currentUser.set({ id: 99, username: 'user99' })
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
|
||||
+1
-1
@@ -75,7 +75,7 @@ describe('MailRuleEditDialogComponent', () => {
|
||||
|
||||
fixture = TestBed.createComponent(MailRuleEditDialogComponent)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
settingsService.currentUser = { id: 99, username: 'user99' }
|
||||
settingsService.currentUser.set({ id: 99, username: 'user99' })
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ describe('StoragePathEditDialogComponent', () => {
|
||||
documentService = TestBed.inject(DocumentService)
|
||||
fixture = TestBed.createComponent(StoragePathEditDialogComponent)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
settingsService.currentUser = { id: 99, username: 'user99' }
|
||||
settingsService.currentUser.set({ id: 99, username: 'user99' })
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ describe('TagEditDialogComponent', () => {
|
||||
|
||||
fixture = TestBed.createComponent(TagEditDialogComponent)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
settingsService.currentUser = { id: 99, username: 'user99' }
|
||||
settingsService.currentUser.set({ id: 99, username: 'user99' })
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ describe('UserEditDialogComponent', () => {
|
||||
|
||||
fixture = TestBed.createComponent(UserEditDialogComponent)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
settingsService.currentUser = { id: 99, username: 'user99' }
|
||||
settingsService.currentUser.set({ id: 99, username: 'user99' })
|
||||
permissionsService = TestBed.inject(PermissionsService)
|
||||
toastService = TestBed.inject(ToastService)
|
||||
component = fixture.componentInstance
|
||||
|
||||
+1
-1
@@ -187,7 +187,7 @@ describe('WorkflowEditDialogComponent', () => {
|
||||
|
||||
fixture = TestBed.createComponent(WorkflowEditDialogComponent)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
settingsService.currentUser = { id: 99, username: 'user99' }
|
||||
settingsService.currentUser.set({ id: 99, username: 'user99' })
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
|
||||
@@ -136,7 +136,7 @@ describe('TagsComponent', () => {
|
||||
})
|
||||
|
||||
it('should support create new using last search term and open a modal', () => {
|
||||
settingsService.currentUser = { id: 1 }
|
||||
settingsService.currentUser.set({ id: 1 })
|
||||
let activeInstances: NgbModalRef[]
|
||||
modalService.activeInstances.subscribe((v) => (activeInstances = v))
|
||||
component.select.filter('foobar')
|
||||
|
||||
+2
-2
@@ -117,12 +117,12 @@ export class PermissionsFilterDropdownComponent extends ComponentWithPermissions
|
||||
if (this.selectionModel.ownerFilter === OwnerFilterType.SELF) {
|
||||
this.selectionModel.includeUsers = []
|
||||
this.selectionModel.excludeUsers = []
|
||||
this.selectionModel.userID = this.settingsService.currentUser.id
|
||||
this.selectionModel.userID = this.settingsService.currentUser().id
|
||||
this.selectionModel.hideUnowned = false
|
||||
} else if (this.selectionModel.ownerFilter === OwnerFilterType.NOT_SELF) {
|
||||
this.selectionModel.userID = null
|
||||
this.selectionModel.includeUsers = []
|
||||
this.selectionModel.excludeUsers = [this.settingsService.currentUser.id]
|
||||
this.selectionModel.excludeUsers = [this.settingsService.currentUser().id]
|
||||
this.selectionModel.hideUnowned = false
|
||||
} else if (this.selectionModel.ownerFilter === OwnerFilterType.NONE) {
|
||||
this.selectionModel.userID = null
|
||||
|
||||
+13
-13
@@ -7,38 +7,38 @@
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6">
|
||||
<pngx-input-text i18n-title title="Email" formControlName="email" (keyup)="onEmailKeyUp($event)" [error]="error?.email"></pngx-input-text>
|
||||
<pngx-input-text i18n-title title="Email" formControlName="email" (keyup)="onEmailKeyUp($event)" [error]="error()?.email"></pngx-input-text>
|
||||
<div ngbAccordion>
|
||||
<div ngbAccordionItem="first" [collapsed]="!showEmailConfirm" class="border-0 bg-transparent">
|
||||
<div ngbAccordionItem="first" [collapsed]="!showEmailConfirm()" class="border-0 bg-transparent">
|
||||
<div ngbAccordionCollapse>
|
||||
<div ngbAccordionBody class="p-0 pb-3">
|
||||
<pngx-input-text i18n-title title="Confirm Email" formControlName="email_confirm" (keyup)="onEmailConfirmKeyUp($event)" autocomplete="email" [error]="error?.email_confirm"></pngx-input-text>
|
||||
<pngx-input-text i18n-title title="Confirm Email" formControlName="email_confirm" (keyup)="onEmailConfirmKeyUp($event)" autocomplete="email" [error]="error()?.email_confirm"></pngx-input-text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<pngx-input-password i18n-title title="Password" formControlName="password" (keyup)="onPasswordKeyUp($event)" [showReveal]="true" autocomplete="current-password" [error]="error?.password"></pngx-input-password>
|
||||
<pngx-input-password i18n-title title="Password" formControlName="password" (keyup)="onPasswordKeyUp($event)" [showReveal]="true" autocomplete="current-password" [error]="error()?.password"></pngx-input-password>
|
||||
<div ngbAccordion>
|
||||
<div ngbAccordionItem="first" [collapsed]="!showPasswordConfirm" class="border-0 bg-transparent">
|
||||
<div ngbAccordionItem="first" [collapsed]="!showPasswordConfirm()" class="border-0 bg-transparent">
|
||||
<div ngbAccordionCollapse>
|
||||
<div ngbAccordionBody class="p-0 pb-3">
|
||||
<pngx-input-password i18n-title title="Confirm Password" formControlName="password_confirm" (keyup)="onPasswordConfirmKeyUp($event)" autocomplete="new-password" [error]="error?.password_confirm"></pngx-input-password>
|
||||
<pngx-input-password i18n-title title="Confirm Password" formControlName="password_confirm" (keyup)="onPasswordConfirmKeyUp($event)" autocomplete="new-password" [error]="error()?.password_confirm"></pngx-input-password>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<pngx-input-text i18n-title title="First name" formControlName="first_name" [error]="error?.first_name"></pngx-input-text>
|
||||
<pngx-input-text i18n-title title="Last name" formControlName="last_name" [error]="error?.first_name"></pngx-input-text>
|
||||
<pngx-input-text i18n-title title="First name" formControlName="first_name" [error]="error()?.first_name"></pngx-input-text>
|
||||
<pngx-input-text i18n-title title="Last name" formControlName="last_name" [error]="error()?.first_name"></pngx-input-text>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" i18n>API Auth Token</label>
|
||||
<div class="position-relative">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" formControlName="auth_token" readonly>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="copyAuthToken()" i18n-title title="Copy">
|
||||
@if (!copied) {
|
||||
@if (!copied()) {
|
||||
<i-bs width="1em" height="1em" name="clipboard-fill"></i-bs>
|
||||
}
|
||||
@if (copied) {
|
||||
@if (copied()) {
|
||||
<i-bs width="1em" height="1em" name="clipboard-check-fill"></i-bs>
|
||||
}
|
||||
<span class="visually-hidden" i18n>Copy</span>
|
||||
@@ -51,7 +51,7 @@
|
||||
(confirm)="generateAuthToken()">
|
||||
</pngx-confirm-button>
|
||||
</div>
|
||||
<span class="badge copied-badge bg-primary small fade ms-4 position-absolute top-50 translate-middle-y pe-none z-3" [class.show]="copied" i18n>Copied!</span>
|
||||
<span class="badge copied-badge bg-primary small fade ms-4 position-absolute top-50 translate-middle-y pe-none z-3" [class.show]="copied()" i18n>Copied!</span>
|
||||
</div>
|
||||
<div class="form-text text-muted text-end fst-italic" i18n>Warning: changing the token cannot be undone</div>
|
||||
</div>
|
||||
@@ -155,10 +155,10 @@
|
||||
}
|
||||
</ul>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary ms-2" (click)="copyRecoveryCodes()" i18n-title title="Copy">
|
||||
@if (!codesCopied) {
|
||||
@if (!codesCopied()) {
|
||||
<i-bs width="1em" height="1em" name="clipboard-fill" class="me-1"></i-bs><span i18n>Copy codes</span>
|
||||
}
|
||||
@if (codesCopied) {
|
||||
@if (codesCopied()) {
|
||||
<i-bs width="1em" height="1em" name="clipboard-check-fill" class="text-primary me-1"></i-bs><span class="text-primary" i18n>Copied!</span>
|
||||
}
|
||||
</button>
|
||||
|
||||
+16
-63
@@ -50,28 +50,12 @@ export class ProfileEditDialogComponent
|
||||
private toastService = inject(ToastService)
|
||||
private clipboard = inject(Clipboard)
|
||||
|
||||
private networkActiveSignal = signal(false)
|
||||
private errorSignal = signal<any>(undefined)
|
||||
private showPasswordConfirmSignal = signal(false)
|
||||
private showEmailConfirmSignal = signal(false)
|
||||
private copiedSignal = signal(false)
|
||||
private codesCopiedSignal = signal(false)
|
||||
|
||||
public get networkActive(): boolean {
|
||||
return this.networkActiveSignal()
|
||||
}
|
||||
|
||||
public set networkActive(networkActive: boolean) {
|
||||
this.networkActiveSignal.set(networkActive)
|
||||
}
|
||||
|
||||
public get error(): any {
|
||||
return this.errorSignal()
|
||||
}
|
||||
|
||||
public set error(error: any) {
|
||||
this.errorSignal.set(error)
|
||||
}
|
||||
readonly networkActive = signal(false)
|
||||
readonly error = signal<any>(undefined)
|
||||
readonly showPasswordConfirm = signal(false)
|
||||
readonly showEmailConfirm = signal(false)
|
||||
readonly copied = signal(false)
|
||||
readonly codesCopied = signal(false)
|
||||
|
||||
public form = new FormGroup({
|
||||
email: new FormControl(''),
|
||||
@@ -87,49 +71,18 @@ export class ProfileEditDialogComponent
|
||||
private currentPassword: string
|
||||
private newPassword: string
|
||||
private passwordConfirm: string
|
||||
public get showPasswordConfirm(): boolean {
|
||||
return this.showPasswordConfirmSignal()
|
||||
}
|
||||
|
||||
public set showPasswordConfirm(showPasswordConfirm: boolean) {
|
||||
this.showPasswordConfirmSignal.set(showPasswordConfirm)
|
||||
}
|
||||
|
||||
public hasUsablePassword: boolean = false
|
||||
|
||||
private currentEmail: string
|
||||
private newEmail: string
|
||||
private emailConfirm: string
|
||||
public get showEmailConfirm(): boolean {
|
||||
return this.showEmailConfirmSignal()
|
||||
}
|
||||
|
||||
public set showEmailConfirm(showEmailConfirm: boolean) {
|
||||
this.showEmailConfirmSignal.set(showEmailConfirm)
|
||||
}
|
||||
|
||||
public isTotpEnabled: boolean = false
|
||||
public totpSettings: TotpSettings
|
||||
public totpSettingsLoading: boolean = false
|
||||
public totpLoading: boolean = false
|
||||
public recoveryCodes: string[]
|
||||
|
||||
public get copied(): boolean {
|
||||
return this.copiedSignal()
|
||||
}
|
||||
|
||||
public set copied(copied: boolean) {
|
||||
this.copiedSignal.set(copied)
|
||||
}
|
||||
|
||||
public get codesCopied(): boolean {
|
||||
return this.codesCopiedSignal()
|
||||
}
|
||||
|
||||
public set codesCopied(codesCopied: boolean) {
|
||||
this.codesCopiedSignal.set(codesCopied)
|
||||
}
|
||||
|
||||
public socialAccounts: SocialAccount[] = []
|
||||
public socialAccountProviders: SocialAccountProvider[] = []
|
||||
|
||||
@@ -141,12 +94,12 @@ export class ProfileEditDialogComponent
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.networkActive = true
|
||||
this.networkActive.set(true)
|
||||
this.profileService
|
||||
.get()
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe((profile) => {
|
||||
this.networkActive = false
|
||||
this.networkActive.set(false)
|
||||
this.form.patchValue(profile)
|
||||
this.currentEmail = profile.email
|
||||
this.form.get('email').valueChanges.subscribe((newEmail) => {
|
||||
@@ -186,7 +139,7 @@ export class ProfileEditDialogComponent
|
||||
}
|
||||
|
||||
onEmailChange(): void {
|
||||
this.showEmailConfirm = this.currentEmail !== this.newEmail
|
||||
this.showEmailConfirm.set(this.currentEmail !== this.newEmail)
|
||||
if (this.showEmailConfirm) {
|
||||
this.form.get('email_confirm').enable()
|
||||
if (this.newEmail !== this.emailConfirm) {
|
||||
@@ -212,7 +165,7 @@ export class ProfileEditDialogComponent
|
||||
}
|
||||
|
||||
onPasswordChange(): void {
|
||||
this.showPasswordConfirm = this.currentPassword !== this.newPassword
|
||||
this.showPasswordConfirm.set(this.currentPassword !== this.newPassword)
|
||||
|
||||
if (this.showPasswordConfirm) {
|
||||
this.form.get('password_confirm').enable()
|
||||
@@ -228,17 +181,17 @@ export class ProfileEditDialogComponent
|
||||
}
|
||||
|
||||
private setFieldError(fieldName: string, message: string): void {
|
||||
this.error = {
|
||||
...(this.error ?? {}),
|
||||
this.error.set({
|
||||
...(this.error() ?? {}),
|
||||
[fieldName]: message,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private clearFieldError(fieldName: string): void {
|
||||
if (!this.error) return
|
||||
const error = { ...this.error }
|
||||
if (!this.error()) return
|
||||
const error = { ...this.error() }
|
||||
delete error[fieldName]
|
||||
this.error = Object.keys(error).length ? error : undefined
|
||||
this.error.set(Object.keys(error).length ? error : undefined)
|
||||
}
|
||||
|
||||
save(): void {
|
||||
|
||||
+27
-53
@@ -57,40 +57,13 @@ export class SystemStatusDialogComponent implements OnInit, OnDestroy {
|
||||
|
||||
public SystemStatusItemStatus = SystemStatusItemStatus
|
||||
public PaperlessTaskType = PaperlessTaskType
|
||||
private statusSignal = signal<SystemStatus>(undefined)
|
||||
@Input() status = signal<SystemStatus>(undefined)
|
||||
public frontendVersion: string = environment.version
|
||||
private versionMismatchSignal = signal(false)
|
||||
|
||||
private copiedSignal = signal(false)
|
||||
|
||||
private runningTasksSignal = signal<Set<PaperlessTaskType>>(new Set())
|
||||
readonly versionMismatch = signal(false)
|
||||
readonly copied = signal(false)
|
||||
readonly runningTasks = signal<Set<PaperlessTaskType>>(new Set())
|
||||
private unsubscribeNotifier: Subject<any> = new Subject()
|
||||
|
||||
@Input()
|
||||
get status(): SystemStatus {
|
||||
return this.statusSignal()
|
||||
}
|
||||
|
||||
set status(status: SystemStatus) {
|
||||
this.statusSignal.set(status)
|
||||
}
|
||||
|
||||
get versionMismatch(): boolean {
|
||||
return this.versionMismatchSignal()
|
||||
}
|
||||
|
||||
set versionMismatch(versionMismatch: boolean) {
|
||||
this.versionMismatchSignal.set(versionMismatch)
|
||||
}
|
||||
|
||||
get copied(): boolean {
|
||||
return this.copiedSignal()
|
||||
}
|
||||
|
||||
set copied(copied: boolean) {
|
||||
this.copiedSignal.set(copied)
|
||||
}
|
||||
|
||||
get currentUserIsSuperUser(): boolean {
|
||||
return this.permissionsService.isSuperUser()
|
||||
}
|
||||
@@ -100,17 +73,18 @@ export class SystemStatusDialogComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public ngOnInit() {
|
||||
const status = this.status
|
||||
this.versionMismatch =
|
||||
const status = this.status()
|
||||
this.versionMismatch.set(
|
||||
environment.production &&
|
||||
status.pngx_version &&
|
||||
this.frontendVersion &&
|
||||
status.pngx_version !== this.frontendVersion
|
||||
if (this.versionMismatch) {
|
||||
this.status = {
|
||||
status.pngx_version &&
|
||||
this.frontendVersion &&
|
||||
status.pngx_version !== this.frontendVersion
|
||||
)
|
||||
if (this.versionMismatch()) {
|
||||
this.status.set({
|
||||
...status,
|
||||
pngx_version: `${status.pngx_version} (frontend: ${this.frontendVersion})`,
|
||||
}
|
||||
})
|
||||
}
|
||||
this.updateWebsocketStatus(this.websocketStatusService.isConnected())
|
||||
this.websocketStatusService
|
||||
@@ -124,10 +98,10 @@ export class SystemStatusDialogComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public copy() {
|
||||
this.clipboard.copy(JSON.stringify(this.status, null, 4))
|
||||
this.copied = true
|
||||
this.clipboard.copy(JSON.stringify(this.status(), null, 4))
|
||||
this.copied.set(true)
|
||||
setTimeout(() => {
|
||||
this.copied = false
|
||||
this.copied.set(false)
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
@@ -138,7 +112,7 @@ export class SystemStatusDialogComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public isRunning(taskName: PaperlessTaskType): boolean {
|
||||
return this.runningTasksSignal().has(taskName)
|
||||
return this.runningTasks().has(taskName)
|
||||
}
|
||||
|
||||
public runTask(taskName: PaperlessTaskType) {
|
||||
@@ -148,11 +122,11 @@ export class SystemStatusDialogComponent implements OnInit, OnDestroy {
|
||||
next: () => {
|
||||
this.setTaskRunning(taskName, false)
|
||||
this.systemStatusService.get().subscribe({
|
||||
next: (status) => {
|
||||
this.status = {
|
||||
...this.status,
|
||||
...status,
|
||||
}
|
||||
next: (statusUpdate) => {
|
||||
this.status.set({
|
||||
...this.status(),
|
||||
...statusUpdate,
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
@@ -167,22 +141,22 @@ export class SystemStatusDialogComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private updateWebsocketStatus(connected: boolean): void {
|
||||
this.status = {
|
||||
...this.status,
|
||||
this.status.set({
|
||||
...this.status(),
|
||||
websocket_connected: connected
|
||||
? SystemStatusItemStatus.OK
|
||||
: SystemStatusItemStatus.ERROR,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private setTaskRunning(taskName: PaperlessTaskType, running: boolean): void {
|
||||
const runningTasks = new Set(this.runningTasksSignal())
|
||||
const runningTasks = new Set(this.runningTasks())
|
||||
if (running) {
|
||||
runningTasks.add(taskName)
|
||||
} else {
|
||||
runningTasks.delete(taskName)
|
||||
}
|
||||
this.runningTasksSignal.set(runningTasks)
|
||||
this.runningTasks.set(runningTasks)
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
|
||||
@@ -120,10 +120,10 @@ describe('DashboardComponent', () => {
|
||||
}).compileComponents()
|
||||
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
settingsService.currentUser = {
|
||||
settingsService.currentUser.set({
|
||||
first_name: 'Foo',
|
||||
last_name: 'Bar',
|
||||
}
|
||||
})
|
||||
jest.spyOn(settingsService, 'get').mockImplementation((key) => {
|
||||
if (key === SETTINGS_KEYS.DASHBOARD_VIEWS_SORT_ORDER) return [0, 2, 3]
|
||||
})
|
||||
@@ -137,9 +137,9 @@ describe('DashboardComponent', () => {
|
||||
|
||||
it('should show a welcome message', () => {
|
||||
expect(component.subtitle).toEqual(`Hello Foo, welcome to Paperless-ngx`)
|
||||
settingsService.currentUser = {
|
||||
settingsService.currentUser.set({
|
||||
id: 1,
|
||||
}
|
||||
})
|
||||
expect(component.subtitle).toEqual(`Welcome to Paperless-ngx`)
|
||||
})
|
||||
|
||||
|
||||
+12
-12
@@ -5,21 +5,21 @@
|
||||
[draggable]="savedView"
|
||||
>
|
||||
|
||||
@if (count !== null) {
|
||||
<span title-badge class="badge bg-info text-dark ms-2">{{count}}</span>
|
||||
@if (count() !== null) {
|
||||
<span title-badge class="badge bg-info text-dark ms-2">{{count()}}</span>
|
||||
}
|
||||
|
||||
@if (documents.length) {
|
||||
@if (documents().length) {
|
||||
<a class="btn-link text-decoration-none" header-buttons [routerLink]="[]" (click)="showAll()" i18n>Show all</a>
|
||||
}
|
||||
|
||||
<div content class="wrapper fade" [class.show]="show">
|
||||
@if (displayMode === DisplayMode.TABLE) {
|
||||
@if (displayMode() === DisplayMode.TABLE) {
|
||||
<table class="table table-hover mb-0 mt-n2 align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
@for (field of displayFields; track field; let i = $index) {
|
||||
@if (displayFields.includes(field)) {
|
||||
@for (field of displayFields(); track field; let i = $index) {
|
||||
@if (displayFields().includes(field)) {
|
||||
<th
|
||||
scope="col"
|
||||
[ngClass]="{
|
||||
@@ -46,13 +46,13 @@
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
@for (doc of documents; track doc.id; let i = $index) {
|
||||
@for (doc of documents(); track doc.id; let i = $index) {
|
||||
<tr>
|
||||
@for (field of displayFields; track field; let j = $index) {
|
||||
@for (field of displayFields(); track field; let j = $index) {
|
||||
<td class="py-2 py-md-3 position-relative" [ngClass]="{ 'd-none d-md-table-cell': j > 1 }">
|
||||
@if (loading && !documents.length) {
|
||||
@if (loading && !documents().length) {
|
||||
<div class="placeholder-glow text-start">
|
||||
<span class="placeholder bg-secondary w-50" [ngStyle]="{ opacity: 1 - (i * 1/documents.length) }"></span>
|
||||
<span class="placeholder bg-secondary w-50" [ngStyle]="{ opacity: 1 - (i * 1/documents().length) }\"></span>
|
||||
</div>
|
||||
} @else {
|
||||
@switch (field) {
|
||||
@@ -112,7 +112,7 @@
|
||||
@if (field.startsWith(DisplayField.CUSTOM_FIELD)) {
|
||||
<pngx-custom-field-display [document]="doc" [fieldDisplayKey]="field"></pngx-custom-field-display>
|
||||
}
|
||||
@if (j === displayFields.length - 1) {
|
||||
@if (j === displayFields().length - 1) {
|
||||
<div class="btn-group position-absolute top-50 end-0 translate-middle-y" (mouseleave)="popupPreview.close()">
|
||||
<pngx-preview-popup [document]="doc" linkClasses="btn px-4 btn-dark border-dark-subtle" #popupPreview>
|
||||
<i-bs width="0.8em" height="0.8em" name="eye"></i-bs>
|
||||
@@ -129,7 +129,7 @@
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
} @else if (displayMode === DisplayMode.SMALL_CARDS) {
|
||||
} @else if (displayMode() === DisplayMode.SMALL_CARDS) {
|
||||
<div class="row row-cols-paperless-cards my-n2">
|
||||
@if (loading && !documents.length) {
|
||||
@for (row of placeholderRows; track row) {
|
||||
|
||||
+8
-40
@@ -107,7 +107,7 @@ export class SavedViewWidgetComponent
|
||||
@Input()
|
||||
savedView: SavedView
|
||||
|
||||
private documentsSignal = signal<Document[]>([])
|
||||
readonly documents = signal<Document[]>([])
|
||||
|
||||
unsubscribeNotifier: Subject<any> = new Subject()
|
||||
|
||||
@@ -117,48 +117,16 @@ export class SavedViewWidgetComponent
|
||||
mouseOnPreview = false
|
||||
popoverHidden = true
|
||||
|
||||
private displayModeSignal = signal<DisplayMode>(null)
|
||||
readonly displayMode = signal<DisplayMode>(null)
|
||||
|
||||
private displayFieldsSignal = signal<DisplayField[]>(
|
||||
readonly displayFields = signal<DisplayField[]>(
|
||||
DEFAULT_DASHBOARD_DISPLAY_FIELDS
|
||||
)
|
||||
|
||||
private countSignal = signal<number>(null)
|
||||
readonly count = signal<number>(null)
|
||||
|
||||
placeholderRows: number[] = []
|
||||
|
||||
get documents(): Document[] {
|
||||
return this.documentsSignal()
|
||||
}
|
||||
|
||||
set documents(value: Document[]) {
|
||||
this.documentsSignal.set(value)
|
||||
}
|
||||
|
||||
get displayMode(): DisplayMode {
|
||||
return this.displayModeSignal()
|
||||
}
|
||||
|
||||
set displayMode(value: DisplayMode) {
|
||||
this.displayModeSignal.set(value)
|
||||
}
|
||||
|
||||
get displayFields(): DisplayField[] {
|
||||
return this.displayFieldsSignal()
|
||||
}
|
||||
|
||||
set displayFields(value: DisplayField[]) {
|
||||
this.displayFieldsSignal.set(value)
|
||||
}
|
||||
|
||||
get count(): number {
|
||||
return this.countSignal()
|
||||
}
|
||||
|
||||
set count(value: number) {
|
||||
this.countSignal.set(value)
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.placeholderRows = Array.from(
|
||||
{
|
||||
@@ -166,7 +134,7 @@ export class SavedViewWidgetComponent
|
||||
},
|
||||
(_, index) => index
|
||||
)
|
||||
this.displayMode = this.savedView.display_mode ?? DisplayMode.TABLE
|
||||
this.displayMode.set(this.savedView.display_mode ?? DisplayMode.TABLE)
|
||||
|
||||
if (
|
||||
this.permissionsService.currentUserCan(
|
||||
@@ -192,7 +160,7 @@ export class SavedViewWidgetComponent
|
||||
// filter by perms etc
|
||||
this.displayFields = this.displayFields.filter(
|
||||
(field) =>
|
||||
this.settingsService.allDisplayFields.find((f) => f.id === field) !==
|
||||
this.settingsService.allDisplayFields().find((f) => f.id === field) !==
|
||||
undefined
|
||||
)
|
||||
|
||||
@@ -224,8 +192,8 @@ export class SavedViewWidgetComponent
|
||||
)
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe((result) => {
|
||||
this.documents = result.results
|
||||
this.count = result.count
|
||||
this.documents.set(result.results)
|
||||
this.count.set(result.count)
|
||||
this.savedViewService.setDocumentCount(this.savedView, result.count)
|
||||
this.loading = false
|
||||
this.show = true
|
||||
|
||||
@@ -286,7 +286,7 @@ describe('DocumentDetailComponent', () => {
|
||||
toastService = TestBed.inject(ToastService)
|
||||
documentListViewService = TestBed.inject(DocumentListViewService)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
settingsService.currentUser = { id: 1 }
|
||||
settingsService.currentUser.set({ id: 1 })
|
||||
customFieldsService = TestBed.inject(CustomFieldsService)
|
||||
deviceDetectorService = TestBed.inject(DeviceDetectorService)
|
||||
fixture = TestBed.createComponent(DocumentDetailComponent)
|
||||
|
||||
+1
-1
@@ -134,7 +134,7 @@
|
||||
<small i18n>{document.page_count, plural, =1 {1 page} other {{{document.page_count}} pages}}</small>
|
||||
</div>
|
||||
}
|
||||
@if (displayFields.includes(DisplayField.OWNER) && document.owner && document.owner !== settingsService.currentUser.id) {
|
||||
@if (displayFields.includes(DisplayField.OWNER) && document.owner && document.owner !== settingsService.currentUser().id) {
|
||||
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center">
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="person-fill-lock"></i-bs><small>{{document.owner | username | async}}</small>
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -113,7 +113,7 @@
|
||||
<small>#{{document.archive_serial_number}}</small>
|
||||
</div>
|
||||
}
|
||||
@if (displayFields.includes(DisplayField.OWNER) && document.owner && document.owner !== settingsService.currentUser.id) {
|
||||
@if (displayFields.includes(DisplayField.OWNER) && document.owner && document.owner !== settingsService.currentUser().id) {
|
||||
<div class="ps-0 p-1">
|
||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="person-fill-lock"></i-bs>
|
||||
<small>{{document.owner | username | async}}</small>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</button>
|
||||
<div ngbDropdownMenu aria-labelledby="dropdownDisplayFields" class="shadow">
|
||||
<div class="px-3">
|
||||
@for (field of settingsService.allDisplayFields; track field.id) {
|
||||
@for (field of settingsService.allDisplayFields(); track field.id) {
|
||||
<div class="form-check my-1">
|
||||
<input class="form-check-input mt-1" type="checkbox" id="displayField{{field.id}}" [checked]="activeDisplayFields.includes(field.id)" (change)="toggleDisplayField(field.id)">
|
||||
<label class="form-check-label" for="displayField{{field.id}}">{{field.name}}</label>
|
||||
|
||||
@@ -837,11 +837,10 @@ describe('DocumentListComponent', () => {
|
||||
|
||||
it('should get custom field title', () => {
|
||||
fixture.detectChanges()
|
||||
jest
|
||||
.spyOn(settingsService, 'allDisplayFields', 'get')
|
||||
.mockReturnValue([
|
||||
{ id: 'custom_field_1' as any, name: 'Custom Field 1' },
|
||||
])
|
||||
const mockDisplayFields = [
|
||||
{ id: 'custom_field_1' as any, name: 'Custom Field 1' },
|
||||
]
|
||||
settingsService.allDisplayFields = jest.fn(() => mockDisplayFields) as any
|
||||
expect(component.getDisplayCustomFieldTitle('custom_field_1')).toEqual(
|
||||
'Custom Field 1'
|
||||
)
|
||||
|
||||
@@ -255,7 +255,7 @@ export class DocumentListComponent
|
||||
}
|
||||
|
||||
public getDisplayCustomFieldTitle(field: string) {
|
||||
return this.settingsService.allDisplayFields.find((f) => f.id === field)
|
||||
return this.settingsService.allDisplayFields().find((f) => f.id === field)
|
||||
?.name
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -260,7 +260,7 @@ describe('FilterEditorComponent', () => {
|
||||
|
||||
documentService = TestBed.inject(DocumentService)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
settingsService.currentUser = users[0]
|
||||
settingsService.currentUser.set(users[0])
|
||||
permissionsService = TestBed.inject(PermissionsService)
|
||||
searchService = TestBed.inject(SearchService)
|
||||
jest
|
||||
|
||||
@@ -55,7 +55,7 @@ describe('FileDropComponent', () => {
|
||||
|
||||
it('should disable drag-drop if disabled in settings', fakeAsync(() => {
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||
settingsService.globalDropzoneEnabled = false
|
||||
settingsService.globalDropzoneEnabled.set(false)
|
||||
expect(component.dragDropEnabled).toBeFalsy()
|
||||
|
||||
component.onDragOver(new Event('dragover') as DragEvent)
|
||||
@@ -296,21 +296,21 @@ describe('FileDropComponent', () => {
|
||||
})
|
||||
|
||||
it('should ignore events if disabled', fakeAsync(() => {
|
||||
settingsService.globalDropzoneEnabled = false
|
||||
expect(settingsService.globalDropzoneActive).toBeFalsy()
|
||||
settingsService.globalDropzoneEnabled.set(false)
|
||||
expect(settingsService.globalDropzoneActive()).toBeFalsy()
|
||||
component.onDragOver(new Event('dragover') as DragEvent)
|
||||
expect(settingsService.globalDropzoneActive).toBeFalsy()
|
||||
settingsService.globalDropzoneActive = true
|
||||
expect(settingsService.globalDropzoneActive()).toBeFalsy()
|
||||
settingsService.globalDropzoneActive.set(true)
|
||||
component.onDragLeave(new Event('dragleave') as DragEvent)
|
||||
expect(settingsService.globalDropzoneActive).toBeTruthy()
|
||||
expect(settingsService.globalDropzoneActive()).toBeTruthy()
|
||||
component.onDrop(new Event('drop') as DragEvent)
|
||||
expect(settingsService.globalDropzoneActive).toBeTruthy()
|
||||
expect(settingsService.globalDropzoneActive()).toBeTruthy()
|
||||
}))
|
||||
|
||||
it('should hide if app loses focus', fakeAsync(() => {
|
||||
const leaveSpy = jest.spyOn(component, 'onDragLeave')
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||
settingsService.globalDropzoneEnabled = true
|
||||
settingsService.globalDropzoneEnabled.set(true)
|
||||
const overEvent = new Event('dragover') as DragEvent
|
||||
;(overEvent as any).dataTransfer = { types: ['Files'] }
|
||||
component.onDragOver(overEvent)
|
||||
@@ -326,7 +326,7 @@ describe('FileDropComponent', () => {
|
||||
it('should hide on window blur', fakeAsync(() => {
|
||||
const leaveSpy = jest.spyOn(component, 'onDragLeave')
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||
settingsService.globalDropzoneEnabled = true
|
||||
settingsService.globalDropzoneEnabled.set(true)
|
||||
const overEvent = new Event('dragover') as DragEvent
|
||||
;(overEvent as any).dataTransfer = { types: ['Files'] }
|
||||
component.onDragOver(overEvent)
|
||||
|
||||
@@ -7,24 +7,8 @@ export abstract class LoadingComponentWithPermissions
|
||||
extends ComponentWithPermissions
|
||||
implements OnDestroy
|
||||
{
|
||||
private loadingSignal = signal(true)
|
||||
private showSignal = signal(false)
|
||||
|
||||
public get loading(): boolean {
|
||||
return this.loadingSignal()
|
||||
}
|
||||
|
||||
public set loading(value: boolean) {
|
||||
this.loadingSignal.set(value)
|
||||
}
|
||||
|
||||
public get show(): boolean {
|
||||
return this.showSignal()
|
||||
}
|
||||
|
||||
public set show(value: boolean) {
|
||||
this.showSignal.set(value)
|
||||
}
|
||||
readonly loading = signal(true)
|
||||
readonly show = signal(false)
|
||||
|
||||
protected unsubscribeNotifier: Subject<any> = new Subject()
|
||||
|
||||
|
||||
+1
-1
@@ -94,7 +94,7 @@ describe('CustomFieldsComponent', () => {
|
||||
toastService = TestBed.inject(ToastService)
|
||||
listViewService = TestBed.inject(DocumentListViewService)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
settingsService.currentUser = { id: 0, username: 'test' }
|
||||
settingsService.currentUser.set({ id: 0, username: 'test' })
|
||||
|
||||
fixture = TestBed.createComponent(CustomFieldsComponent)
|
||||
component = fixture.componentInstance
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@if (loadingAccounts) {
|
||||
@if (loadingAccounts()) {
|
||||
<li class="list-group-item">
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<ng-container i18n>Loading...</ng-container>
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
@for (account of mailAccounts; track account) {
|
||||
<li class="list-group-item">
|
||||
<div class="row fade" [class.show]="showAccounts">
|
||||
<div class="row fade" [class.show]="showAccounts()">
|
||||
<div class="col d-flex align-items-center">
|
||||
<button class="btn btn-link p-0 text-start" type="button" (click)="editMailAccount(account)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.MailAccount) || !userCanEdit(account)">
|
||||
{{account.name}}@switch (account.account_type) {
|
||||
@@ -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>
|
||||
@@ -117,19 +117,19 @@
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@if (loadingRules) {
|
||||
@if (loadingRules()) {
|
||||
<li class="list-group-item">
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<ng-container i18n>Loading...</ng-container>
|
||||
</li>
|
||||
}
|
||||
|
||||
@for (rule of mailRules; track rule) {
|
||||
@for (rule of mailRules(); track rule) {
|
||||
<li class="list-group-item">
|
||||
<div class="row fade" [class.show]="showRules">
|
||||
<div class="row fade" [class.show]="showRules()">
|
||||
<div class="col d-flex align-items-center"><button class="btn btn-link p-0 text-start" type="button" (click)="editMailRule(rule)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.MailRule) || !userCanEdit(rule)">{{rule.name}}</button></div>
|
||||
<div class="col-1 d-flex align-items-center d-none d-sm-flex">{{rule.order}}</div>
|
||||
<div class="col-2 d-flex align-items-center">{{ mailAccountsById.get(rule.account)?.name }}</div>
|
||||
<div class="col-2 d-flex align-items-center">{{ mailAccountsById().get(rule.account)?.name }}</div>
|
||||
<div class="col-2 d-flex align-items-center d-none d-sm-flex">
|
||||
<div class="form-check form-switch mb-0">
|
||||
<input #inputField type="checkbox" class="form-check-input cursor-pointer" [id]="rule.id+'_enable'" [(ngModel)]="rule.enabled" (change)="onMailRuleEnableToggled(rule)" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailRule }">
|
||||
@@ -179,7 +179,7 @@
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
@if (!loadingRules && mailRules.length === 0) {
|
||||
@if (!loadingRules() && mailRules().length === 0) {
|
||||
<li class="list-group-item" i18n>No mail rules defined.</li>
|
||||
}
|
||||
</ul>
|
||||
|
||||
@@ -113,7 +113,7 @@ describe('MailComponent', () => {
|
||||
permissionsService = TestBed.inject(PermissionsService)
|
||||
activatedRoute = TestBed.inject(ActivatedRoute)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
settingsService.currentUser = { id: 1 }
|
||||
settingsService.currentUser.set({ id: 1 })
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||
jest
|
||||
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
|
||||
|
||||
@@ -63,25 +63,13 @@ export class MailComponent
|
||||
}
|
||||
private set mailAccounts(accounts: MailAccount[]) {
|
||||
this.mailAccountsSignal.set(accounts)
|
||||
this.mailAccountsById = new Map(
|
||||
accounts.map((account) => [account.id, account])
|
||||
this.mailAccountsById.set(
|
||||
new Map(accounts.map((account) => [account.id, account]))
|
||||
)
|
||||
}
|
||||
private mailAccountsByIdSignal = signal<Map<number, MailAccount>>(new Map())
|
||||
public get mailAccountsById(): Map<number, MailAccount> {
|
||||
return this.mailAccountsByIdSignal()
|
||||
}
|
||||
private set mailAccountsById(value: Map<number, MailAccount>) {
|
||||
this.mailAccountsByIdSignal.set(value)
|
||||
}
|
||||
readonly mailAccountsById = signal<Map<number, MailAccount>>(new Map())
|
||||
|
||||
private mailRulesSignal = signal<MailRule[]>([])
|
||||
public get mailRules(): MailRule[] {
|
||||
return this.mailRulesSignal()
|
||||
}
|
||||
public set mailRules(value: MailRule[]) {
|
||||
this.mailRulesSignal.set(value)
|
||||
}
|
||||
readonly mailRules = signal<MailRule[]>([])
|
||||
|
||||
unsubscribeNotifier: Subject<any> = new Subject()
|
||||
oAuthAccountId: number
|
||||
@@ -94,35 +82,10 @@ export class MailComponent
|
||||
return this.settingsService.get(SETTINGS_KEYS.OUTLOOK_OAUTH_URL)
|
||||
}
|
||||
|
||||
private loadingRulesSignal = signal(true)
|
||||
private showRulesSignal = signal(false)
|
||||
private loadingAccountsSignal = signal(true)
|
||||
private showAccountsSignal = signal(false)
|
||||
|
||||
public get loadingRules(): boolean {
|
||||
return this.loadingRulesSignal()
|
||||
}
|
||||
public set loadingRules(value: boolean) {
|
||||
this.loadingRulesSignal.set(value)
|
||||
}
|
||||
public get showRules(): boolean {
|
||||
return this.showRulesSignal()
|
||||
}
|
||||
public set showRules(value: boolean) {
|
||||
this.showRulesSignal.set(value)
|
||||
}
|
||||
public get loadingAccounts(): boolean {
|
||||
return this.loadingAccountsSignal()
|
||||
}
|
||||
public set loadingAccounts(value: boolean) {
|
||||
this.loadingAccountsSignal.set(value)
|
||||
}
|
||||
public get showAccounts(): boolean {
|
||||
return this.showAccountsSignal()
|
||||
}
|
||||
public set showAccounts(value: boolean) {
|
||||
this.showAccountsSignal.set(value)
|
||||
}
|
||||
readonly loadingRules = signal(true)
|
||||
readonly showRules = signal(false)
|
||||
readonly loadingAccounts = signal(true)
|
||||
readonly showAccounts = signal(false)
|
||||
|
||||
ngOnInit(): void {
|
||||
this.mailAccountService
|
||||
@@ -132,8 +95,8 @@ export class MailComponent
|
||||
takeUntil(this.unsubscribeNotifier),
|
||||
tap((r) => {
|
||||
this.mailAccounts = r.results
|
||||
this.loadingAccounts = false
|
||||
this.showAccounts = true
|
||||
this.loadingAccounts.set(false)
|
||||
this.showAccounts.set(true)
|
||||
if (this.oAuthAccountId) {
|
||||
this.editMailAccount(
|
||||
this.mailAccounts.find(
|
||||
@@ -145,7 +108,7 @@ export class MailComponent
|
||||
)
|
||||
.subscribe({
|
||||
error: (e) => {
|
||||
this.loadingAccounts = false
|
||||
this.loadingAccounts.set(false)
|
||||
this.toastService.showError(
|
||||
$localize`Error retrieving mail accounts`,
|
||||
e
|
||||
@@ -159,14 +122,14 @@ export class MailComponent
|
||||
first(),
|
||||
takeUntil(this.unsubscribeNotifier),
|
||||
tap((r) => {
|
||||
this.mailRules = r.results
|
||||
this.loadingRules = false
|
||||
this.showRules = true
|
||||
this.mailRules.set(r.results)
|
||||
this.loadingRules.set(false)
|
||||
this.showRules.set(true)
|
||||
})
|
||||
)
|
||||
.subscribe({
|
||||
error: (e) => {
|
||||
this.loadingRules = false
|
||||
this.loadingRules.set(false)
|
||||
this.toastService.showError($localize`Error retrieving mail rules`, e)
|
||||
},
|
||||
})
|
||||
@@ -292,7 +255,7 @@ export class MailComponent
|
||||
this.mailRuleService
|
||||
.listAll(null, null, { full_perms: true })
|
||||
.subscribe((r) => {
|
||||
this.mailRules = r.results
|
||||
this.mailRules.set(r.results)
|
||||
})
|
||||
})
|
||||
modal.componentInstance.failed
|
||||
@@ -348,7 +311,7 @@ export class MailComponent
|
||||
this.mailRuleService
|
||||
.listAll(null, null, { full_perms: true })
|
||||
.subscribe((r) => {
|
||||
this.mailRules = r.results
|
||||
this.mailRules.set(r.results)
|
||||
})
|
||||
},
|
||||
error: (e) => {
|
||||
|
||||
@@ -705,11 +705,16 @@ describe('DocumentListViewService', () => {
|
||||
const customFields = ['custom_field_1', 'custom_field_2']
|
||||
documentListViewService.displayFields = customFields as any
|
||||
expect(documentListViewService.displayFields).toEqual(customFields)
|
||||
jest.spyOn(settingsService, 'allDisplayFields', 'get').mockReturnValue([
|
||||
const mockDisplayFields = [
|
||||
{ id: DisplayField.ADDED, name: 'Added' },
|
||||
{ id: DisplayField.TITLE, name: 'Title' },
|
||||
{ id: 'custom_field_1', name: 'Custom Field 1' },
|
||||
] as any)
|
||||
] as any
|
||||
jest
|
||||
.spyOn(settingsService.allDisplayFields, 'toString')
|
||||
.mockReturnValue(mockDisplayFields.toString() as any)
|
||||
// Mock the signal directly by overriding its value
|
||||
settingsService.allDisplayFields = jest.fn(() => mockDisplayFields) as any
|
||||
settingsService.displayFieldsInit.emit(true)
|
||||
expect(documentListViewService.displayFields).toEqual(['custom_field_1'])
|
||||
|
||||
|
||||
@@ -17,8 +17,10 @@ export class SavedViewService extends AbstractPaperlessService<SavedView> {
|
||||
private settingsService = inject(SettingsService)
|
||||
private documentService = inject(DocumentService)
|
||||
|
||||
private savedViewsSignal = signal<SavedView[]>([])
|
||||
private savedViewDocumentCountsSignal = signal<Map<number, number>>(new Map())
|
||||
private readonly savedViews = signal<SavedView[]>([])
|
||||
private readonly savedViewDocumentCounts = signal<Map<number, number>>(
|
||||
new Map()
|
||||
)
|
||||
private unsubscribeNotifier: Subject<void> = new Subject<void>()
|
||||
|
||||
constructor() {
|
||||
@@ -26,22 +28,6 @@ export class SavedViewService extends AbstractPaperlessService<SavedView> {
|
||||
this.resourceName = 'saved_views'
|
||||
}
|
||||
|
||||
private get savedViews(): SavedView[] {
|
||||
return this.savedViewsSignal()
|
||||
}
|
||||
|
||||
private set savedViews(views: SavedView[]) {
|
||||
this.savedViewsSignal.set(views)
|
||||
}
|
||||
|
||||
private get savedViewDocumentCounts(): Map<number, number> {
|
||||
return this.savedViewDocumentCountsSignal()
|
||||
}
|
||||
|
||||
private set savedViewDocumentCounts(counts: Map<number, number>) {
|
||||
this.savedViewDocumentCountsSignal.set(counts)
|
||||
}
|
||||
|
||||
public list(
|
||||
page?: number,
|
||||
pageSize?: number,
|
||||
@@ -53,15 +39,16 @@ export class SavedViewService extends AbstractPaperlessService<SavedView> {
|
||||
tap({
|
||||
next: (r) => {
|
||||
const views = r.results.map((view) => this.withUserVisibility(view))
|
||||
this.savedViews = views
|
||||
this.savedViews.set(views)
|
||||
r.results = views
|
||||
this._loading = false
|
||||
this.settingsService.dashboardIsEmpty =
|
||||
this.settingsService.dashboardIsEmpty.set(
|
||||
this.dashboardViews.length === 0
|
||||
)
|
||||
},
|
||||
error: () => {
|
||||
this._loading = false
|
||||
this.settingsService.dashboardIsEmpty = true
|
||||
this.settingsService.dashboardIsEmpty.set(true)
|
||||
},
|
||||
})
|
||||
)
|
||||
@@ -79,8 +66,8 @@ export class SavedViewService extends AbstractPaperlessService<SavedView> {
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
get allViews() {
|
||||
return this.savedViews
|
||||
get allViews(): SavedView[] {
|
||||
return this.savedViews()
|
||||
}
|
||||
|
||||
private getVisibleViewIds(setting: string): number[] {
|
||||
@@ -111,7 +98,9 @@ export class SavedViewService extends AbstractPaperlessService<SavedView> {
|
||||
}
|
||||
|
||||
get sidebarViews(): SavedView[] {
|
||||
const sidebarViews = this.savedViews.filter((v) => this.isSidebarVisible(v))
|
||||
const sidebarViews = this.savedViews().filter((v) =>
|
||||
this.isSidebarVisible(v)
|
||||
)
|
||||
|
||||
const sorted: number[] = this.settingsService.get(
|
||||
SETTINGS_KEYS.SIDEBAR_VIEWS_SORT_ORDER
|
||||
@@ -126,7 +115,7 @@ export class SavedViewService extends AbstractPaperlessService<SavedView> {
|
||||
}
|
||||
|
||||
get dashboardViews(): SavedView[] {
|
||||
const dashboardViews = this.savedViews.filter((v) =>
|
||||
const dashboardViews = this.savedViews().filter((v) =>
|
||||
this.isDashboardVisible(v)
|
||||
)
|
||||
|
||||
@@ -192,12 +181,12 @@ export class SavedViewService extends AbstractPaperlessService<SavedView> {
|
||||
}
|
||||
|
||||
public setDocumentCount(view: SavedView, count: number) {
|
||||
const counts = new Map(this.savedViewDocumentCounts)
|
||||
const counts = new Map(this.savedViewDocumentCounts())
|
||||
counts.set(view.id, count)
|
||||
this.savedViewDocumentCounts = counts
|
||||
this.savedViewDocumentCounts.set(counts)
|
||||
}
|
||||
|
||||
public getDocumentCount(view: SavedView): number {
|
||||
return this.savedViewDocumentCounts.get(view.id)
|
||||
return this.savedViewDocumentCounts().get(view.id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,15 +296,7 @@ export class SettingsService {
|
||||
|
||||
private settings: Record<string, any> = {}
|
||||
private settingsVersion = signal(0)
|
||||
private currentUserSignal = signal<User>(undefined)
|
||||
|
||||
get currentUser(): User {
|
||||
return this.currentUserSignal()
|
||||
}
|
||||
|
||||
set currentUser(value: User) {
|
||||
this.currentUserSignal.set(value)
|
||||
}
|
||||
readonly currentUser = signal<User>(undefined)
|
||||
|
||||
public settingsSaved: EventEmitter<any> = new EventEmitter()
|
||||
|
||||
@@ -313,49 +305,14 @@ export class SettingsService {
|
||||
return this._renderer
|
||||
}
|
||||
|
||||
private dashboardIsEmptySignal = signal(false)
|
||||
private globalDropzoneEnabledSignal = signal(true)
|
||||
private globalDropzoneActiveSignal = signal(false)
|
||||
private organizingSidebarSavedViewsSignal = signal(false)
|
||||
readonly dashboardIsEmpty = signal(false)
|
||||
readonly globalDropzoneEnabled = signal(true)
|
||||
readonly globalDropzoneActive = signal(false)
|
||||
readonly organizingSidebarSavedViews = signal(false)
|
||||
|
||||
public get dashboardIsEmpty(): boolean {
|
||||
return this.dashboardIsEmptySignal()
|
||||
}
|
||||
|
||||
public set dashboardIsEmpty(value: boolean) {
|
||||
this.dashboardIsEmptySignal.set(value)
|
||||
}
|
||||
|
||||
public get globalDropzoneEnabled(): boolean {
|
||||
return this.globalDropzoneEnabledSignal()
|
||||
}
|
||||
|
||||
public set globalDropzoneEnabled(value: boolean) {
|
||||
this.globalDropzoneEnabledSignal.set(value)
|
||||
}
|
||||
|
||||
public get globalDropzoneActive(): boolean {
|
||||
return this.globalDropzoneActiveSignal()
|
||||
}
|
||||
|
||||
public set globalDropzoneActive(value: boolean) {
|
||||
this.globalDropzoneActiveSignal.set(value)
|
||||
}
|
||||
|
||||
public get organizingSidebarSavedViews(): boolean {
|
||||
return this.organizingSidebarSavedViewsSignal()
|
||||
}
|
||||
|
||||
public set organizingSidebarSavedViews(value: boolean) {
|
||||
this.organizingSidebarSavedViewsSignal.set(value)
|
||||
}
|
||||
|
||||
private allDisplayFieldsSignal = signal<
|
||||
Array<{ id: DisplayField; name: string }>
|
||||
>(DEFAULT_DISPLAY_FIELDS)
|
||||
public get allDisplayFields(): Array<{ id: DisplayField; name: string }> {
|
||||
return this.allDisplayFieldsSignal()
|
||||
}
|
||||
readonly allDisplayFields = signal<Array<{ id: DisplayField; name: string }>>(
|
||||
DEFAULT_DISPLAY_FIELDS
|
||||
)
|
||||
public displayFieldsInit: EventEmitter<boolean> = new EventEmitter()
|
||||
|
||||
constructor() {
|
||||
@@ -409,7 +366,7 @@ export class SettingsService {
|
||||
// to update lang cookie
|
||||
if (this.settings['language']?.length)
|
||||
this.setLanguage(this.settings['language'])
|
||||
this.currentUser = uisettings.user
|
||||
this.currentUser.set(uisettings.user)
|
||||
this.permissionsService.initialize(
|
||||
uisettings.permissions,
|
||||
this.currentUser
|
||||
@@ -453,7 +410,7 @@ export class SettingsService {
|
||||
: null
|
||||
}).filter((f) => f)
|
||||
|
||||
this.allDisplayFieldsSignal.set(displayFields)
|
||||
this.allDisplayFields.set(displayFields)
|
||||
|
||||
if (
|
||||
this.permissionsService.currentUserCan(
|
||||
@@ -481,8 +438,8 @@ export class SettingsService {
|
||||
|
||||
get displayName(): string {
|
||||
return (
|
||||
this.currentUser?.first_name ??
|
||||
this.currentUser?.username ??
|
||||
this.currentUser()?.first_name ??
|
||||
this.currentUser()?.username ??
|
||||
''
|
||||
).trim()
|
||||
}
|
||||
@@ -615,7 +572,7 @@ export class SettingsService {
|
||||
|
||||
// special case to fallback
|
||||
if (key === SETTINGS_KEYS.DEFAULT_PERMS_OWNER && value === undefined) {
|
||||
return this.currentUser?.id
|
||||
return this.currentUser()?.id
|
||||
}
|
||||
|
||||
if (value !== undefined) {
|
||||
|
||||
@@ -45,11 +45,11 @@ describe('ConsumerStatusService', () => {
|
||||
|
||||
httpTestingController = TestBed.inject(HttpTestingController)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
settingsService.currentUser = {
|
||||
settingsService.currentUser.set({
|
||||
id: 1,
|
||||
username: 'testuser',
|
||||
is_superuser: false,
|
||||
}
|
||||
})
|
||||
websocketStatusService = TestBed.inject(WebsocketStatusService)
|
||||
documentService = TestBed.inject(DocumentService)
|
||||
})
|
||||
@@ -356,12 +356,12 @@ describe('ConsumerStatusService', () => {
|
||||
})
|
||||
|
||||
it('should notify user if user can view or is in group', () => {
|
||||
settingsService.currentUser = {
|
||||
settingsService.currentUser.set({
|
||||
id: 1,
|
||||
username: 'testuser',
|
||||
is_superuser: false,
|
||||
groups: [1],
|
||||
}
|
||||
})
|
||||
websocketStatusService.connect()
|
||||
server.send({
|
||||
type: WebsocketStatusType.STATUS_UPDATE,
|
||||
|
||||
@@ -211,7 +211,7 @@ export class WebsocketStatusService {
|
||||
groups_can_view?: number[]
|
||||
}): boolean {
|
||||
// see paperless.consumers.StatusConsumer._can_view
|
||||
const user: User = this.settingsService.currentUser
|
||||
const user: User = this.settingsService.currentUser()
|
||||
return (
|
||||
!messageData.owner_id ||
|
||||
user.is_superuser ||
|
||||
|
||||
Reference in New Issue
Block a user