diff --git a/src-ui/src/app/app-routing.module.ts b/src-ui/src/app/app-routing.module.ts index 57ca258769..12a4463879 100644 --- a/src-ui/src/app/app-routing.module.ts +++ b/src-ui/src/app/app-routing.module.ts @@ -37,7 +37,7 @@ export const routes: Routes = [ path: 'dashboard', component: DashboardComponent, data: { - componentName: 'AppFrameComponent', + componentName: 'DashboardComponent', }, }, { 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 fb8f8c95ba..823e4f2aff 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 @@ -62,13 +62,13 @@ } @else { @switch (field) { @case (DisplayField.ADDED) { - {{doc.added | customDate}} + {{doc.added | customDate}} } @case (DisplayField.CREATED) { - {{doc.created | customDate}} + {{doc.created | customDate}} } @case (DisplayField.TITLE) { - {{doc.title | documentTitle}} + {{doc.title | documentTitle}} } @case (DisplayField.CORRESPONDENT) { @if (doc.correspondent) { @@ -106,7 +106,7 @@ } @case (DisplayField.NOTES) { @if (doc.notes.length) { - + {{doc.notes.length}} @@ -150,6 +150,7 @@ { it('should navigate to document', () => { const routerSpy = jest.spyOn(router, 'navigate') + const activateSpy = jest.spyOn( + documentListViewService, + 'activateSavedViewWithDocuments' + ) component.openDocumentDetail(documentResults[0]) + expect(activateSpy).toHaveBeenCalled() expect(routerSpy).toHaveBeenCalledWith(['documents', documentResults[0].id]) }) + it('should hand its documents to the list service when opening a document', () => { + jest.spyOn(documentService, 'listFiltered').mockReturnValue( + of({ + all: [2, 3], + count: 25, + results: documentResults, + }) + ) + jest.spyOn(router, 'navigateByUrl').mockResolvedValue(true) + const activateSpy = jest + .spyOn(documentListViewService, 'activateSavedViewWithDocuments') + .mockImplementation(() => {}) + component.ngOnInit() + fixture.detectChanges() + fixture.debugElement + .query(By.css('a[title="Open document"]')) + .nativeElement.click() + expect(activateSpy).toHaveBeenCalledWith(savedView, documentResults, 25, 20) + }) + + it('should fall back to the default dashboard page size', () => { + component.savedView = { ...savedView, page_size: undefined } + expect(component.pageSize).toEqual(DEFAULT_DASHBOARD_VIEW_PAGE_SIZE) + }) + it('should navigate via quickfilter on click tag', () => { const qfSpy = jest.spyOn(documentListViewService, 'quickFilter') component.clickTag(11, new MouseEvent('click')) diff --git a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts index 09988c8992..5daf99fb22 100644 --- a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts +++ b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts @@ -132,7 +132,7 @@ export class SavedViewWidgetComponent ngOnInit(): void { this.placeholderRows = Array.from( { - length: this.savedView?.page_size ?? DEFAULT_DASHBOARD_VIEW_PAGE_SIZE, + length: this.pageSize, }, (_, index) => index ) @@ -187,7 +187,7 @@ export class SavedViewWidgetComponent this.documentService .listFiltered( 1, - this.savedView?.page_size ?? DEFAULT_DASHBOARD_VIEW_PAGE_SIZE, + this.pageSize, this.savedView.sort_field, this.savedView.sort_reverse, this.savedView.filter_rules, @@ -292,7 +292,24 @@ export class SavedViewWidgetComponent ]) } + get pageSize(): number { + return this.savedView?.page_size ?? DEFAULT_DASHBOARD_VIEW_PAGE_SIZE + } + + /** + * Pass the widget's documents to the list service to enable next / previous. + */ + activateViewForNavigation() { + this.documentListViewService.activateSavedViewWithDocuments( + this.savedView, + this.documents(), + this.count(), + this.pageSize + ) + } + openDocumentDetail(document: Document) { + this.activateViewForNavigation() this.router.navigate(['documents', document.id]) } diff --git a/src-ui/src/app/components/document-detail/document-detail.component.spec.ts b/src-ui/src/app/components/document-detail/document-detail.component.spec.ts index f5a94bfd04..26a42336a2 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.spec.ts +++ b/src-ui/src/app/components/document-detail/document-detail.component.spec.ts @@ -940,6 +940,20 @@ describe('DocumentDetailComponent', () => { expect(navigateSpy).toHaveBeenCalledWith(['view', 77]) }) + it('should close back to the dashboard even if a saved view is active', () => { + initNormally() + jest + .spyOn(componentRouterService, 'getComponentURLBefore') + .mockReturnValue('dashboard') + jest + .spyOn(documentListViewService, 'activeSavedViewId', 'get') + .mockReturnValue(77) + const navigateSpy = jest.spyOn(router, 'navigate') + component.close() + expect(navigateSpy).toHaveBeenCalledWith(['dashboard']) + expect(navigateSpy).not.toHaveBeenCalledWith(['view', 77]) + }) + it('should not close if e.g. user-cancelled', () => { initNormally() jest.spyOn(openDocumentsService, 'closeDocument').mockReturnValue(of(false)) diff --git a/src-ui/src/app/components/document-detail/document-detail.component.ts b/src-ui/src/app/components/document-detail/document-detail.component.ts index b47937eef9..561fc6b681 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.ts +++ b/src-ui/src/app/components/document-detail/document-detail.component.ts @@ -1454,7 +1454,12 @@ export class DocumentDetailComponent .subscribe((closed) => { if (!closed) return if (closedCallback) closedCallback() - if (this.documentListViewService.activeSavedViewId) { + if ( + this.componentRouterService.getComponentURLBefore() === 'dashboard' + ) { + // e.g. opened from a dashboard widget + this.router.navigate(['dashboard']) + } else if (this.documentListViewService.activeSavedViewId) { this.router.navigate([ 'view', this.documentListViewService.activeSavedViewId, diff --git a/src-ui/src/app/services/document-list-view.service.spec.ts b/src-ui/src/app/services/document-list-view.service.spec.ts index 772c851e06..d5ef7e7b4b 100644 --- a/src-ui/src/app/services/document-list-view.service.spec.ts +++ b/src-ui/src/app/services/document-list-view.service.spec.ts @@ -386,6 +386,46 @@ describe('DocumentListViewService', () => { documentListViewService.activateSavedView(null) }) + it('should support activating saved view with pre-fetched documents', () => { + const routerSpy = jest.spyOn(router, 'navigate') + routerSpy.mockClear() + documentListViewService.activateSavedViewWithDocuments( + view, + documents.slice(0, 2), + documents.length, + 2 + ) + // no navigation, no reload + expect(routerSpy).not.toHaveBeenCalled() + httpTestingController.expectNone( + (req) => req.url === `${environment.apiBaseUrl}documents/` + ) + expect(documentListViewService.activeSavedViewId).toEqual(view.id) + expect(documentListViewService.activeSavedViewTitle).toEqual(view.name) + expect(documentListViewService.documents).toEqual(documents.slice(0, 2)) + expect(documentListViewService.collectionSize).toEqual(documents.length) + expect(documentListViewService.currentPage).toEqual(1) + expect(documentListViewService.pageSize).toEqual(2) + expect(documentListViewService.hasNext(documents[0].id)).toBeTruthy() + + let nextDocId: number + documentListViewService + .getNext(documents[1].id) + .subscribe((docId) => (nextDocId = docId)) + // crossing the page uses the view's sort + filters and the given page size + // (the currentPage setter reloads too, so the first request is cancelled) + const reqs = httpTestingController.match( + `${environment.apiBaseUrl}documents/?page=2&page_size=2&ordering=-added&truncate_content=true&include_selection_data=true&tags__id__all=${tags__id__all}` + ) + expect(reqs.length).toBeGreaterThan(0) + reqs[reqs.length - 1].flush({ + count: documents.length, + results: documents.slice(2, 4), + }) + expect(nextDocId).toEqual(documents[2].id) + documentListViewService.activateSavedView(null) + }) + it('should support navigating next / previous', () => { documentListViewService.setFilterRules([]) let req = httpTestingController.expectOne( diff --git a/src-ui/src/app/services/document-list-view.service.ts b/src-ui/src/app/services/document-list-view.service.ts index 429cde2c09..5a3a60c5b0 100644 --- a/src-ui/src/app/services/document-list-view.service.ts +++ b/src-ui/src/app/services/document-list-view.service.ts @@ -270,11 +270,28 @@ export class DocumentListViewService { this.currentPage = viewState.currentPage } - loadSavedView(view: SavedView, closeCurrentView: boolean = false) { - if (closeCurrentView) { - this._activeSavedViewId = null - } + /** + * Activate a saved view using documents that were already fetched, e.g. by a + * dashboard widget. Does not navigate or reload. + */ + activateSavedViewWithDocuments( + view: SavedView, + documents: Document[], + collectionSize: number, + pageSize: number + ) { + this.cancelPending() + this.rangeSelectionAnchorIndex = this.lastRangeSelectionToIndex = null + this._activeSavedViewId = view.id + this.applySavedViewConfig(view) + this.activeListViewState.pageSize = pageSize // match the page size the documents were fetched with + this.activeListViewState.currentPage = 1 + this.activeListViewState.documents = documents + this.activeListViewState.collectionSize = collectionSize + this.markChanged() + } + private applySavedViewConfig(view: SavedView) { this.activeListViewState.filterRules = cloneFilterRules(view.filter_rules) this.activeListViewState.sortField = view.sort_field this.activeListViewState.sortReverse = view.sort_reverse @@ -284,6 +301,14 @@ export class DocumentListViewService { this.activeListViewState.displayMode = view.display_mode this.activeListViewState.pageSize = view.page_size this.activeListViewState.displayFields = view.display_fields + } + + loadSavedView(view: SavedView, closeCurrentView: boolean = false) { + if (closeCurrentView) { + this._activeSavedViewId = null + } + + this.applySavedViewConfig(view) this.reduceSelectionToFilter() this.markChanged()