From 3755070e1fbf7e779abeff042e9bf8d7eecc5664 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Mon, 6 Jul 2026 20:09:58 -0700 Subject: [PATCH] Update some global components with loading signals --- .../app-frame/app-frame.component.ts | 42 ++++++++-- .../global-search/global-search.component.ts | 31 ++++++- .../saved-view-widget.component.html | 49 +++++++++-- .../saved-view-widget.component.ts | 84 ++++++++++++++----- .../widget-frame/widget-frame.component.html | 15 ++-- .../widget-frame.component.spec.ts | 6 +- .../widget-frame/widget-frame.component.ts | 42 ++++------ .../document-card-large.component.ts | 7 +- .../document-card-small.component.ts | 8 +- 9 files changed, 196 insertions(+), 88 deletions(-) diff --git a/src-ui/src/app/components/app-frame/app-frame.component.ts b/src-ui/src/app/components/app-frame/app-frame.component.ts index 7989eb3e1..33384065d 100644 --- a/src-ui/src/app/components/app-frame/app-frame.component.ts +++ b/src-ui/src/app/components/app-frame/app-frame.component.ts @@ -6,7 +6,7 @@ import { moveItemInArray, } from '@angular/cdk/drag-drop' import { NgClass } from '@angular/common' -import { Component, HostListener, inject, OnInit } from '@angular/core' +import { Component, HostListener, inject, OnInit, signal } from '@angular/core' import { ActivatedRoute, Router, RouterModule } from '@angular/router' import { NgbCollapseModule, @@ -90,16 +90,48 @@ export class AppFrameComponent permissionsService = inject(PermissionsService) private djangoMessagesService = inject(DjangoMessagesService) - appRemoteVersion: AppRemoteVersion + private appRemoteVersionSignal = signal(null) - isMenuCollapsed: boolean = true + private isMenuCollapsedSignal = signal(true) - slimSidebarAnimating: boolean = false + private slimSidebarAnimatingSignal = signal(false) - public mobileSearchHidden: boolean = false + private mobileSearchHiddenSignal = 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 diff --git a/src-ui/src/app/components/app-frame/global-search/global-search.component.ts b/src-ui/src/app/components/app-frame/global-search/global-search.component.ts index e95b52cfc..cda87c0d4 100644 --- a/src-ui/src/app/components/app-frame/global-search/global-search.component.ts +++ b/src-ui/src/app/components/app-frame/global-search/global-search.component.ts @@ -7,6 +7,7 @@ import { ViewChild, ViewChildren, inject, + signal, } from '@angular/core' import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { Router } from '@angular/router' @@ -82,12 +83,36 @@ export class GlobalSearchComponent implements OnInit { private locationStrategy = inject(LocationStrategy) public DataType = DataType - public query: string + private querySignal = signal(null) public queryDebounce: Subject - public searchResults: GlobalSearchResult + private searchResultsSignal = signal(null) private currentItemIndex: number = -1 private domIndex: number = -1 - public loading: boolean = false + private loadingSignal = signal(false) + + public get query(): string { + return this.querySignal() + } + + public set query(value: string) { + this.querySignal.set(value) + } + + public get searchResults(): GlobalSearchResult { + return this.searchResultsSignal() + } + + public set searchResults(value: GlobalSearchResult) { + this.searchResultsSignal.set(value) + } + + public get loading(): boolean { + return this.loadingSignal() + } + + public set loading(value: boolean) { + this.loadingSignal.set(value) + } @ViewChild('searchInput') searchInput: ElementRef @ViewChild('resultsDropdown') resultsDropdown: NgbDropdown diff --git a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html index ef82a96a3..71835525e 100644 --- a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html +++ b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html @@ -1,11 +1,14 @@ + @if (count !== null) { + {{count}} + } + @if (documents.length) { Show all } @@ -30,11 +33,24 @@ + @if (loading && !documents.length) { + @for (row of placeholderRows; track row) { + + @for (field of displayFields; track field; let j = $index) { + +
+ +
+ + } + + } + } @for (doc of documents; track doc.id; let i = $index) { @for (field of displayFields; track field; let j = $index) { - @if (loading && show) { + @if (loading && !documents.length) {
@@ -115,12 +131,22 @@ } @else if (displayMode === DisplayMode.SMALL_CARDS) {
+ @if (loading && !documents.length) { + @for (row of placeholderRows; track row) { + + + } + } @for (d of documents; track d.id; let i = $index) { } @else if (displayMode === DisplayMode.LARGE_CARDS) {
+ @if (loading && !documents.length) { + @for (row of placeholderRows; track row) { + + + } + } @for (d of documents; track d.id; let i = $index) { ([]) unsubscribeNotifier: Subject = new Subject() @@ -116,21 +117,56 @@ export class SavedViewWidgetComponent mouseOnPreview = false popoverHidden = true - displayMode: DisplayMode + private displayModeSignal = signal(null) - displayFields: DisplayField[] = DEFAULT_DASHBOARD_DISPLAY_FIELDS + private displayFieldsSignal = signal( + DEFAULT_DASHBOARD_DISPLAY_FIELDS + ) - count: number + private countSignal = signal(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.reload() + this.placeholderRows = Array.from( + { + length: this.savedView?.page_size ?? DEFAULT_DASHBOARD_VIEW_PAGE_SIZE, + }, + (_, index) => index + ) this.displayMode = this.savedView.display_mode ?? DisplayMode.TABLE - this.websocketStatusService - .onDocumentConsumptionFinished() - .pipe(takeUntil(this.unsubscribeNotifier)) - .subscribe(() => { - this.reload() - }) if ( this.permissionsService.currentUserCan( @@ -159,6 +195,14 @@ export class SavedViewWidgetComponent this.settingsService.allDisplayFields.find((f) => f.id === field) !== undefined ) + + this.reload() + this.websocketStatusService + .onDocumentConsumptionFinished() + .pipe(takeUntil(this.unsubscribeNotifier)) + .subscribe(() => { + this.reload() + }) } ngOnDestroy(): void { @@ -168,6 +212,7 @@ export class SavedViewWidgetComponent reload() { this.loading = this.documents.length == 0 + this.show = true this.documentService .listFiltered( 1, @@ -177,18 +222,13 @@ export class SavedViewWidgetComponent this.savedView.filter_rules, { truncate_content: true } ) - .pipe( - takeUntil(this.unsubscribeNotifier), - tap((result) => { - this.show = true - this.documents = result.results - this.count = result.count - this.savedViewService.setDocumentCount(this.savedView, result.count) - }), - delay(500) - ) + .pipe(takeUntil(this.unsubscribeNotifier)) .subscribe((result) => { + this.documents = result.results + this.count = result.count + this.savedViewService.setDocumentCount(this.savedView, result.count) this.loading = false + this.show = true }) } diff --git a/src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.html b/src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.html index 45dcdf7d8..ff13d1050 100644 --- a/src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.html +++ b/src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.html @@ -1,19 +1,20 @@ -@if (!cardless) { -
+@if (!cardless()) { +
- @if (draggable) { + @if (draggable()) {
} -
{{title}}
- @if (badge) { - {{badge}} +
{{title()}}
+ + @if (badge() !== null && badge() !== undefined) { + {{badge()}} }
- @if (loading) { + @if (loading()) {
Loading...
} diff --git a/src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.spec.ts b/src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.spec.ts index e61697587..169c362d3 100644 --- a/src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.spec.ts +++ b/src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.spec.ts @@ -45,21 +45,19 @@ describe('WidgetFrameComponent', () => { }) it('should show title', () => { - component.title = 'Foo' + fixture.componentRef.setInput('title', 'Foo') fixture.detectChanges() expect(fixture.debugElement.nativeElement.textContent).toContain('Foo') }) it('should show loading indicator', () => { expect(fixture.debugElement.query(By.css('.spinner-border'))).toBeNull() - component.loading = true + fixture.componentRef.setInput('loading', true) fixture.detectChanges() expect(fixture.debugElement.query(By.css('.spinner-border'))).not.toBeNull() }) it('should show', () => { - expect(component.show).toBeFalsy() - jest.advanceTimersByTime(100) expect(component.show).toBeTruthy() }) }) diff --git a/src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.ts b/src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.ts index 3b0b04529..750451275 100644 --- a/src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.ts +++ b/src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.ts @@ -1,8 +1,7 @@ import { DragDropModule } from '@angular/cdk/drag-drop' import { NgTemplateOutlet } from '@angular/common' -import { AfterViewInit, Component, Input } from '@angular/core' +import { AfterViewInit, Component, input, signal } from '@angular/core' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' -import { LoadingComponentWithPermissions } from 'src/app/components/loading-component/loading.component' @Component({ selector: 'pngx-widget-frame', @@ -10,39 +9,28 @@ import { LoadingComponentWithPermissions } from 'src/app/components/loading-comp styleUrls: ['./widget-frame.component.scss'], imports: [DragDropModule, NgxBootstrapIconsModule, NgTemplateOutlet], }) -export class WidgetFrameComponent - extends LoadingComponentWithPermissions - implements AfterViewInit -{ - constructor() { - super() - this.loading = false +export class WidgetFrameComponent implements AfterViewInit { + private showSignal = signal(false) + + loading = input(false) + + get show(): boolean { + return this.showSignal() } - @Input() - title: string - - @Input() - override set loading(value: boolean) { - super.loading = value + set show(value: boolean) { + this.showSignal.set(value) } - override get loading(): boolean { - return super.loading - } + title = input() - @Input() - draggable: any + draggable = input() - @Input() - cardless: boolean = false + cardless = input(false) - @Input() - badge: string + badge = input(null) ngAfterViewInit(): void { - setTimeout(() => { - this.show = true - }, 100) + this.show = true } } diff --git a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.ts b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.ts index 8f77489ec..4ac98e626 100644 --- a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.ts +++ b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.ts @@ -14,7 +14,6 @@ import { NgbTooltipModule, } from '@ng-bootstrap/ng-bootstrap' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' -import { delay, of } from 'rxjs' import { DEFAULT_DISPLAY_FIELDS, DisplayField, @@ -108,11 +107,7 @@ export class DocumentCardLargeComponent popoverHidden = true ngAfterViewInit(): void { - of(true) - .pipe(delay(50)) - .subscribe(() => { - this.show = true - }) + this.show = true } get searchScoreClass() { diff --git a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts index 05f84d752..eece61df7 100644 --- a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts +++ b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts @@ -14,8 +14,6 @@ import { NgbTooltipModule, } from '@ng-bootstrap/ng-bootstrap' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' -import { of } from 'rxjs' -import { delay } from 'rxjs/operators' import { DEFAULT_DISPLAY_FIELDS, DisplayField, @@ -101,11 +99,7 @@ export class DocumentCardSmallComponent @ViewChild('popupPreview') popupPreview: PreviewPopupComponent ngAfterViewInit(): void { - of(true) - .pipe(delay(50)) - .subscribe(() => { - this.show = true - }) + this.show = true } getIsThumbInverted() {