add route reuse for projects listing and project overview

This commit is contained in:
Dan Percic 2021-04-19 23:39:08 +03:00
parent 81e0498e48
commit 8767c051b4
10 changed files with 83 additions and 48 deletions

View File

@ -30,7 +30,7 @@ import { AppRoutingModule } from './app-routing.module';
import { SharedModule } from './modules/shared/shared.module';
import { FileUploadDownloadModule } from './modules/upload-download/file-upload-download.module';
import { UserProfileScreenComponent } from './components/user-profile/user-profile-screen.component';
import { CustomRouteReuseStrategy } from './modules/projects/services/custom-route-reuse.strategy';
import { CustomRouteReuseStrategy } from './utils/custom-route-reuse.strategy';
export function HttpLoaderFactory(httpClient: HttpClient) {
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');

View File

@ -418,7 +418,7 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
}
}
public _loadDocument() {
private _loadDocument() {
if (this.fileData) {
this.instance.loadDocument(this.fileData, {
filename: this.fileStatus ? this.fileStatus.filename : 'document.pdf'

View File

@ -14,7 +14,8 @@ const routes = [
component: ProjectListingScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
reuse: true
}
},
{
@ -22,7 +23,8 @@ const routes = [
component: ProjectOverviewScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
reuse: true
}
},
{
@ -30,8 +32,8 @@ const routes = [
component: FilePreviewScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
reuse: true
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
// reuse: true
}
}
];

View File

@ -1,4 +1,4 @@
<section *ngIf="activeFile" [class.fullscreen]="fullScreen">
<section *ngIf="appStateService.activeFile" [class.fullscreen]="fullScreen">
<div class="page-header">
<div class="flex flex-1">
<div

View File

@ -89,10 +89,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
});
}
get activeFile() {
return this.appStateService.activeFile;
}
get annotations(): AnnotationWrapper[] {
return this.annotationData ? this.annotationData.visibleAnnotations : [];
}
@ -191,15 +187,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
});
}
});
this._router.events.subscribe((event: Event) => {
if (event instanceof NavigationEnd) {
console.log(event);
if (!event.url.includes('?page=') && event.url.includes('/ui/projects/') && event.url.includes('/file/')) {
this._viewerComponent._loadDocument();
}
}
});
}
ngOnDestroy(): void {

View File

@ -16,13 +16,14 @@ import { TranslateService } from '@ngx-translate/core';
import { PermissionsService } from '../../../../services/permissions.service';
import { ProjectWrapper } from '../../../../state/model/project.wrapper';
import { Subscription, timer } from 'rxjs';
import { tap } from 'rxjs/operators';
import { filter, tap } from 'rxjs/operators';
import { TranslateChartService } from '../../../../services/translate-chart.service';
import { RedactionFilterSorter } from '../../../../utils/sorters/redaction-filter-sorter';
import { StatusSorter } from '../../../../utils/sorters/status-sorter';
import { Router } from '@angular/router';
import { NavigationEnd, NavigationStart, Router } from '@angular/router';
import { FilterComponent } from '../../../shared/components/filter/filter.component';
import { ProjectsDialogService } from '../../services/projects-dialog.service';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { BaseListingComponent } from '../../../shared/base/base-listing.component';
@Component({
@ -51,6 +52,9 @@ export class ProjectListingScreenComponent extends BaseListingComponent<ProjectW
};
private projectAutoUpdateTimer: Subscription;
private _lastScrollPosition: number;
@ViewChild(CdkVirtualScrollViewport) private _scrollBar: CdkVirtualScrollViewport;
@ViewChild('statusFilter') private _statusFilterComponent: FilterComponent;
@ViewChild('peopleFilter') private _peopleFilterComponent: FilterComponent;
@ -60,12 +64,13 @@ export class ProjectListingScreenComponent extends BaseListingComponent<ProjectW
constructor(
private readonly _appStateService: AppStateService,
public readonly userService: UserService,
public readonly sortingService: SortingService,
public readonly translateChartService: TranslateChartService,
public readonly permissionsService: PermissionsService,
private readonly _dialogService: ProjectsDialogService,
private readonly _translateService: TranslateService,
private readonly _router: Router,
public readonly translateChartService: TranslateChartService,
private readonly _fileManagementControllerService: FileManagementControllerService,
protected readonly _injector: Injector
) {
super(_injector);
@ -86,6 +91,15 @@ export class ProjectListingScreenComponent extends BaseListingComponent<ProjectW
this._appStateService.fileChanged.subscribe(() => {
this._calculateData();
});
this._router.events.pipe(filter((events) => events instanceof NavigationStart || events instanceof NavigationEnd)).subscribe((event) => {
if (event instanceof NavigationStart && event.url !== '/ui/projects') {
this._lastScrollPosition = this._scrollBar.getOffsetToRenderedContentStart() + this._scrollBar.getRenderedRange().end;
}
if (event instanceof NavigationEnd && event.url === '/ui/projects') {
this._scrollBar.scrollTo({ top: this._lastScrollPosition });
}
});
}
ngOnDestroy(): void {

View File

@ -1,5 +1,5 @@
import { Component, HostListener, Injector, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivatedRoute, NavigationEnd, NavigationStart, Router } from '@angular/router';
import { NotificationService, NotificationType } from '../../../../services/notification.service';
import { AppStateService } from '../../../../state/app-state.service';
import { FileDropOverlayService } from '../../../upload-download/services/file-drop-overlay.service';
@ -15,15 +15,16 @@ import { FileStatusWrapper } from '../../../../models/file/file-status.wrapper';
import { annotationFilterChecker, keyChecker, processFilters } from '../../../shared/components/filter/utils/filter-utils';
import { PermissionsService } from '../../../../services/permissions.service';
import { UserService } from '../../../../services/user.service';
import { FileManagementControllerService, FileStatus } from '@redaction/red-ui-http';
import { FileStatus } from '@redaction/red-ui-http';
import { Subscription, timer } from 'rxjs';
import { tap } from 'rxjs/operators';
import { filter, tap } from 'rxjs/operators';
import { RedactionFilterSorter } from '../../../../utils/sorters/redaction-filter-sorter';
import { StatusSorter } from '../../../../utils/sorters/status-sorter';
import { FormGroup } from '@angular/forms';
import { convertFiles, handleFileDrop } from '../../../../utils/file-drop-utils';
import { FilterComponent } from '../../../shared/components/filter/filter.component';
import { ProjectsDialogService } from '../../services/projects-dialog.service';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { BaseListingComponent } from '../../../shared/base/base-listing.component';
import { ProjectWrapper } from '../../../../state/model/project.wrapper';
@ -50,7 +51,10 @@ export class ProjectOverviewScreenComponent extends BaseListingComponent<FileSta
@ViewChild('projectDetailsComponent', { static: false })
private _projectDetailsComponent: ProjectDetailsComponent;
private filesAutoUpdateTimer: Subscription;
private _filesAutoUpdateTimer: Subscription;
private _lastScrollPosition: number;
@ViewChild(CdkVirtualScrollViewport) private _scrollBar: CdkVirtualScrollViewport;
@ViewChild('statusFilter') private _statusFilterComponent: FilterComponent;
@ViewChild('peopleFilter') private _peopleFilterComponent: FilterComponent;
@ -60,6 +64,7 @@ export class ProjectOverviewScreenComponent extends BaseListingComponent<FileSta
private readonly _appStateService: AppStateService,
public readonly userService: UserService,
public readonly permissionsService: PermissionsService,
private readonly _sortingService: SortingService,
private readonly _activatedRoute: ActivatedRoute,
private readonly _notificationService: NotificationService,
private readonly _dialogService: ProjectsDialogService,
@ -69,7 +74,6 @@ export class ProjectOverviewScreenComponent extends BaseListingComponent<FileSta
private readonly _router: Router,
private readonly _translateService: TranslateService,
private readonly _fileDropOverlayService: FileDropOverlayService,
private readonly _fileManagementControllerService: FileManagementControllerService,
protected readonly _injector: Injector
) {
super(_injector);
@ -85,7 +89,7 @@ export class ProjectOverviewScreenComponent extends BaseListingComponent<FileSta
}
ngOnInit(): void {
this.filesAutoUpdateTimer = timer(0, 7500)
this._filesAutoUpdateTimer = timer(0, 7500)
.pipe(
tap(async () => {
await this._appStateService.reloadActiveProjectFilesIfNecessary();
@ -95,11 +99,21 @@ export class ProjectOverviewScreenComponent extends BaseListingComponent<FileSta
.subscribe();
this._fileDropOverlayService.initFileDropHandling();
this.calculateData();
this._router.events.pipe(filter((events) => events instanceof NavigationStart || events instanceof NavigationEnd)).subscribe((event) => {
if (event instanceof NavigationStart && !event.url.endsWith(this.appStateService.activeProjectId)) {
this._lastScrollPosition = this._scrollBar.getOffsetToRenderedContentStart() + this._scrollBar.getRenderedRange().end;
}
if (event instanceof NavigationEnd && event.url.endsWith(this.appStateService.activeProjectId)) {
this._scrollBar.scrollTo({ top: this._lastScrollPosition });
}
});
}
ngOnDestroy(): void {
this._fileDropOverlayService.cleanupFileDropHandling();
this.filesAutoUpdateTimer.unsubscribe();
this._filesAutoUpdateTimer.unsubscribe();
}
public get activeProject(): ProjectWrapper {

View File

@ -38,12 +38,16 @@ export class SyncWidthDirective implements AfterViewInit, OnDestroy {
const hasExtraColumns = headerItems.length !== length ? 1 : 0;
for (let idx = 0; idx < length - hasExtraColumns - 1; ++idx) {
headerItems[idx].style.width = `${tableRow.children[idx].getBoundingClientRect().width}px`;
headerItems[idx].style.minWidth = `${tableRow.children[idx].getBoundingClientRect().width}px`;
if (headerItems[idx]) {
headerItems[idx].style.width = `${tableRow.children[idx].getBoundingClientRect().width}px`;
headerItems[idx].style.minWidth = `${tableRow.children[idx].getBoundingClientRect().width}px`;
}
}
for (let idx = length - hasExtraColumns - 1; idx < headerItems.length; ++idx) {
headerItems[idx].style.minWidth = `0`;
if (headerItems[idx]) {
headerItems[idx].style.minWidth = `0`;
}
}
}

View File

@ -15,7 +15,7 @@ import {
} from '@redaction/red-ui-http';
import { NotificationService, NotificationType } from '../services/notification.service';
import { TranslateService } from '@ngx-translate/core';
import { ActivatedRoute, Event, ResolveStart, Router } from '@angular/router';
import { Event, NavigationEnd, ResolveStart, Router } from '@angular/router';
import { UserService } from '../services/user.service';
import { forkJoin, Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@ -71,18 +71,33 @@ export class AppStateService {
};
this._router.events.subscribe((event: Event) => {
if (event instanceof ResolveStart) {
console.log(event);
if (event.url.includes('/ui/projects/') && event.url.includes('/file/')) {
const url = event.url.replace('/ui/projects/', '').split('/');
const projectId = url[0];
const fileId = url[2];
this.activateFile(projectId, fileId);
}
if (AppStateService._isFileOverviewRoute(event)) {
const url = (event as ResolveStart).url.replace('/ui/projects/', '');
const [projectId, , fileId] = url.split('/');
this.activateFile(projectId, fileId);
}
if (AppStateService._isProjectOverviewRoute(event)) {
const projectId = (event as ResolveStart).url.replace('/ui/projects/', '');
this.activateProject(projectId);
}
if (AppStateService._isRandomRoute(event)) {
this._appState.activeProjectId = null;
}
});
}
private static _isFileOverviewRoute(event: Event) {
return event instanceof ResolveStart && event.url.includes('/ui/projects/') && event.url.includes('/file/');
}
private static _isProjectOverviewRoute(event: Event) {
return event instanceof ResolveStart && event.url.includes('/ui/projects/') && !event.url.includes('/file/');
}
private static _isRandomRoute(event: Event) {
return event instanceof NavigationEnd && !event.url.includes('/ui/projects/') && !event.url.includes('/file/');
}
async reloadActiveProjectFilesIfNecessary() {
if (this.activeProject?.hasPendingOrProcessing) {
await this.reloadActiveProjectFiles();

View File

@ -1,24 +1,23 @@
import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router';
export class CustomRouteReuseStrategy implements RouteReuseStrategy {
handlers: { [key: string]: DetachedRouteHandle } = {};
private _handlers: { [key: string]: DetachedRouteHandle } = {};
shouldDetach(route: ActivatedRouteSnapshot): boolean {
return route.routeConfig.path === ':projectId/file/:fileId';
return !!route.routeConfig.data?.reuse;
}
store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
if (handle === null) return;
this.handlers[route.url.join('/')] = handle;
this._handlers[route.toString()] = handle;
}
shouldAttach(route: ActivatedRouteSnapshot): boolean {
return !!this.handlers[route.url.join('/')];
return !!this._handlers[route.toString()];
}
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
console.log(this.handlers[route.url.join('/')]);
return this.handlers[route.url.join('/')] as DetachedRouteHandle;
return this._handlers[route.toString()] as DetachedRouteHandle;
}
shouldReuseRoute(future: ActivatedRouteSnapshot, current: ActivatedRouteSnapshot): boolean {