]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Enhancement: improve doc details close behavior (#8937)
authorshamoon <4887959+shamoon@users.noreply.github.com>
Wed, 29 Jan 2025 06:26:30 +0000 (22:26 -0800)
committerGitHub <noreply@github.com>
Wed, 29 Jan 2025 06:26:30 +0000 (22:26 -0800)
src-ui/src/app/app.component.ts
src-ui/src/app/components/document-detail/document-detail.component.spec.ts
src-ui/src/app/components/document-detail/document-detail.component.ts
src-ui/src/app/services/component-router.service.spec.ts [new file with mode: 0644]
src-ui/src/app/services/component-router.service.ts [new file with mode: 0644]

index d22b4ca38338b98ffa78557b6e5a0b920d2803b1..c89f5d4c2a284c04d5b2acb44f7ca16da0e0ec6b 100644 (file)
@@ -5,6 +5,7 @@ import { first, Subscription } from 'rxjs'
 import { ToastsComponent } from './components/common/toasts/toasts.component'
 import { FileDropComponent } from './components/file-drop/file-drop.component'
 import { SETTINGS_KEYS } from './data/ui-settings'
+import { ComponentRouterService } from './services/component-router.service'
 import { ConsumerStatusService } from './services/consumer-status.service'
 import { HotKeyService } from './services/hot-key.service'
 import {
@@ -41,7 +42,8 @@ export class AppComponent implements OnInit, OnDestroy {
     public tourService: TourService,
     private renderer: Renderer2,
     private permissionsService: PermissionsService,
-    private hotKeyService: HotKeyService
+    private hotKeyService: HotKeyService,
+    private componentRouterService: ComponentRouterService
   ) {
     let anyWindow = window as any
     anyWindow.pdfWorkerSrc = 'assets/js/pdf.worker.min.mjs'
index fb2596d4a1503b4215791db54589eaf1a9ba5782..0a2e5605fbbf77d2dbec301024d062b7fa6f14d4 100644 (file)
@@ -45,6 +45,7 @@ import { Tag } from 'src/app/data/tag'
 import { PermissionsGuard } from 'src/app/guards/permissions.guard'
 import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
 import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
+import { ComponentRouterService } from 'src/app/services/component-router.service'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 import { OpenDocumentsService } from 'src/app/services/open-documents.service'
 import { PermissionsService } from 'src/app/services/permissions.service'
@@ -127,6 +128,7 @@ describe('DocumentDetailComponent', () => {
   let settingsService: SettingsService
   let customFieldsService: CustomFieldsService
   let httpTestingController: HttpTestingController
+  let componentRouterService: ComponentRouterService
 
   let currentUserCan = true
   let currentUserHasObjectPermissions = true
@@ -264,6 +266,7 @@ describe('DocumentDetailComponent', () => {
     customFieldsService = TestBed.inject(CustomFieldsService)
     fixture = TestBed.createComponent(DocumentDetailComponent)
     httpTestingController = TestBed.inject(HttpTestingController)
+    componentRouterService = TestBed.inject(ComponentRouterService)
     component = fixture.componentInstance
   })
 
@@ -568,6 +571,16 @@ describe('DocumentDetailComponent', () => {
     expect(navigateSpy).toHaveBeenCalledWith(['documents'])
   })
 
+  it('should allow close and navigate to the last view if available', () => {
+    initNormally()
+    jest
+      .spyOn(componentRouterService, 'getComponentURLBefore')
+      .mockReturnValue('dashboard')
+    const navigateSpy = jest.spyOn(router, 'navigate')
+    component.close()
+    expect(navigateSpy).toHaveBeenCalledWith(['dashboard'])
+  })
+
   it('should allow close and navigate to documents by default', () => {
     initNormally()
     jest
index 6b65ad335b30afbcc282c2cbaf79af9a4e246696..8d1b350718a42d3d9759b08218c9cbd51c8f02da 100644 (file)
@@ -59,6 +59,7 @@ import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
 import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
 import { FileSizePipe } from 'src/app/pipes/file-size.pipe'
 import { SafeUrlPipe } from 'src/app/pipes/safeurl.pipe'
+import { ComponentRouterService } from 'src/app/services/component-router.service'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 import { HotKeyService } from 'src/app/services/hot-key.service'
 import { OpenDocumentsService } from 'src/app/services/open-documents.service'
@@ -272,7 +273,8 @@ export class DocumentDetailComponent
     private userService: UserService,
     private customFieldsService: CustomFieldsService,
     private http: HttpClient,
-    private hotKeyService: HotKeyService
+    private hotKeyService: HotKeyService,
+    private componentRouterService: ComponentRouterService
   ) {
     super()
   }
@@ -888,6 +890,10 @@ export class DocumentDetailComponent
             'view',
             this.documentListViewService.activeSavedViewId,
           ])
+        } else if (this.componentRouterService.getComponentURLBefore()) {
+          this.router.navigate([
+            this.componentRouterService.getComponentURLBefore(),
+          ])
         } else {
           this.router.navigate(['documents'])
         }
diff --git a/src-ui/src/app/services/component-router.service.spec.ts b/src-ui/src/app/services/component-router.service.spec.ts
new file mode 100644 (file)
index 0000000..b11fc81
--- /dev/null
@@ -0,0 +1,102 @@
+import { TestBed } from '@angular/core/testing'
+import { ActivationStart, Router } from '@angular/router'
+import { Subject } from 'rxjs'
+import { ComponentRouterService } from './component-router.service'
+
+describe('ComponentRouterService', () => {
+  let service: ComponentRouterService
+  let router: Router
+  let eventsSubject: Subject<any>
+
+  beforeEach(() => {
+    eventsSubject = new Subject<any>()
+    TestBed.configureTestingModule({
+      providers: [
+        ComponentRouterService,
+        {
+          provide: Router,
+          useValue: {
+            events: eventsSubject.asObservable(),
+          },
+        },
+      ],
+    })
+    service = TestBed.inject(ComponentRouterService)
+    router = TestBed.inject(Router)
+  })
+
+  it('should add to history and componentHistory on ActivationStart event', () => {
+    eventsSubject.next(
+      new ActivationStart({
+        url: 'test-url',
+        component: { name: 'TestComponent' },
+      } as any)
+    )
+
+    expect((service as any).history).toEqual(['test-url'])
+    expect((service as any).componentHistory).toEqual(['TestComponent'])
+  })
+
+  it('should not add duplicate component names to componentHistory', () => {
+    eventsSubject.next(
+      new ActivationStart({
+        url: 'test-url-1',
+        component: { name: 'TestComponent' },
+      } as any)
+    )
+    eventsSubject.next(
+      new ActivationStart({
+        url: 'test-url-2',
+        component: { name: 'TestComponent' },
+      } as any)
+    )
+
+    expect((service as any).componentHistory.length).toBe(1)
+    expect((service as any).componentHistory).toEqual(['TestComponent'])
+  })
+
+  it('should return the URL of the component before the current one', () => {
+    eventsSubject.next(
+      new ActivationStart({
+        url: 'test-url-1',
+        component: { name: 'TestComponent1' },
+      } as any)
+    )
+    eventsSubject.next(
+      new ActivationStart({
+        url: 'test-url-2',
+        component: { name: 'TestComponent2' },
+      } as any)
+    )
+
+    expect(service.getComponentURLBefore()).toBe('test-url-1')
+  })
+
+  it('should update the URL of the current component if the same component is loaded via a different URL', () => {
+    eventsSubject.next(
+      new ActivationStart({
+        url: 'test-url-1',
+        component: { name: 'TestComponent' },
+      } as any)
+    )
+    eventsSubject.next(
+      new ActivationStart({
+        url: 'test-url-2',
+        component: { name: 'TestComponent' },
+      } as any)
+    )
+
+    expect((service as any).history).toEqual(['test-url-2'])
+  })
+
+  it('should return null if there is no previous component', () => {
+    eventsSubject.next(
+      new ActivationStart({
+        url: 'test-url',
+        component: { name: 'TestComponent' },
+      } as any)
+    )
+
+    expect(service.getComponentURLBefore()).toBeNull()
+  })
+})
diff --git a/src-ui/src/app/services/component-router.service.ts b/src-ui/src/app/services/component-router.service.ts
new file mode 100644 (file)
index 0000000..3f97584
--- /dev/null
@@ -0,0 +1,35 @@
+import { Injectable } from '@angular/core'
+import { ActivationStart, Event, Router } from '@angular/router'
+import { filter } from 'rxjs'
+
+@Injectable({
+  providedIn: 'root',
+})
+export class ComponentRouterService {
+  private history: string[] = []
+  private componentHistory: any[] = []
+
+  constructor(private router: Router) {
+    this.router.events
+      .pipe(filter((event: Event) => event instanceof ActivationStart))
+      .subscribe((event: ActivationStart) => {
+        if (
+          this.componentHistory[this.componentHistory.length - 1] !==
+          event.snapshot.component.name
+        ) {
+          this.history.push(event.snapshot.url.toString())
+          this.componentHistory.push(event.snapshot.component.name)
+        } else {
+          // Update the URL of the current component in case the same component was loaded via a different URL
+          this.history[this.history.length - 1] = event.snapshot.url.toString()
+        }
+      })
+  }
+
+  public getComponentURLBefore(): any {
+    if (this.componentHistory.length > 1) {
+      return this.history[this.history.length - 2]
+    }
+    return null
+  }
+}