Pull request #161: RED-1307

Merge in RED/ui from RED-1307 to master

* commit '0913e12cfa16036115ff041f022e297a45d30cfb':
  resolve conflicts
  fix fileId and projectId
  add route reuse for projects listing and project overview
  throws undefined error
  activate current file in appStateService
  loading component works, wip
This commit is contained in:
Timo Bejan 2021-04-20 09:58:34 +02:00
commit ff1c79f1a4
9 changed files with 129 additions and 57 deletions

View File

@ -79,7 +79,7 @@ const routes = [
]; ];
@NgModule({ @NgModule({
imports: [RouterModule.forRoot(routes)], imports: [RouterModule.forRoot(routes, { scrollPositionRestoration: 'enabled' })],
exports: [RouterModule] exports: [RouterModule]
}) })
export class AppRoutingModule {} export class AppRoutingModule {}

View File

@ -1,7 +1,7 @@
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { APP_INITIALIZER, NgModule } from '@angular/core'; import { APP_INITIALIZER, NgModule } from '@angular/core';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router, RouteReuseStrategy } from '@angular/router';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from '@angular/common/http'; import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from '@angular/common/http';
import { BaseScreenComponent } from './components/base-screen/base-screen.component'; import { BaseScreenComponent } from './components/base-screen/base-screen.component';
@ -30,6 +30,7 @@ import { AppRoutingModule } from './app-routing.module';
import { SharedModule } from './modules/shared/shared.module'; import { SharedModule } from './modules/shared/shared.module';
import { FileUploadDownloadModule } from './modules/upload-download/file-upload-download.module'; import { FileUploadDownloadModule } from './modules/upload-download/file-upload-download.module';
import { UserProfileScreenComponent } from './components/user-profile/user-profile-screen.component'; import { UserProfileScreenComponent } from './components/user-profile/user-profile-screen.component';
import { CustomRouteReuseStrategy } from './utils/custom-route-reuse.strategy';
export function HttpLoaderFactory(httpClient: HttpClient) { export function HttpLoaderFactory(httpClient: HttpClient) {
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json'); return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
@ -92,7 +93,8 @@ const components = [AppComponent, LogoComponent, AuthErrorComponent, ToastCompon
monthYearA11yLabel: 'YYYY' monthYearA11yLabel: 'YYYY'
} }
} }
} },
{ provide: RouteReuseStrategy, useClass: CustomRouteReuseStrategy }
], ],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })

View File

@ -1,5 +1,4 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { ProjectListingScreenComponent } from './screens/project-listing-screen/project-listing-screen.component'; import { ProjectListingScreenComponent } from './screens/project-listing-screen/project-listing-screen.component';
import { CompositeRouteGuard } from '../../guards/composite-route.guard'; import { CompositeRouteGuard } from '../../guards/composite-route.guard';
@ -15,7 +14,8 @@ const routes = [
component: ProjectListingScreenComponent, component: ProjectListingScreenComponent,
canActivate: [CompositeRouteGuard], canActivate: [CompositeRouteGuard],
data: { data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
reuse: true
} }
}, },
{ {
@ -23,7 +23,8 @@ const routes = [
component: ProjectOverviewScreenComponent, component: ProjectOverviewScreenComponent,
canActivate: [CompositeRouteGuard], canActivate: [CompositeRouteGuard],
data: { data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
reuse: true
} }
}, },
{ {
@ -32,6 +33,7 @@ const routes = [
canActivate: [CompositeRouteGuard], canActivate: [CompositeRouteGuard],
data: { data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
// reuse: true
} }
} }
]; ];

View File

@ -7,7 +7,6 @@ import { debounce } from '../../../../utils/debounce';
import { MatDialogRef, MatDialogState } from '@angular/material/dialog'; import { MatDialogRef, MatDialogState } from '@angular/material/dialog';
import { ManualRedactionEntryWrapper } from '../../../../models/file/manual-redaction-entry.wrapper'; import { ManualRedactionEntryWrapper } from '../../../../models/file/manual-redaction-entry.wrapper';
import { AnnotationWrapper } from '../../../../models/file/annotation.wrapper'; import { AnnotationWrapper } from '../../../../models/file/annotation.wrapper';
import { ManualAnnotationService } from '../../services/manual-annotation.service';
import { ManualAnnotationResponse } from '../../../../models/file/manual-annotation-response'; import { ManualAnnotationResponse } from '../../../../models/file/manual-annotation-response';
import { AnnotationData, FileDataModel } from '../../../../models/file/file-data.model'; import { AnnotationData, FileDataModel } from '../../../../models/file/file-data.model';
import { FileActionService } from '../../services/file-action.service'; import { FileActionService } from '../../services/file-action.service';
@ -16,7 +15,6 @@ import { AnnotationProcessingService } from '../../services/annotation-processin
import { FilterModel } from '../../../shared/components/filter/model/filter.model'; import { FilterModel } from '../../../shared/components/filter/model/filter.model';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { NotificationService } from '../../../../services/notification.service'; import { NotificationService } from '../../../../services/notification.service';
import { TranslateService } from '@ngx-translate/core';
import { FileStatusWrapper } from '../../../../models/file/file-status.wrapper'; import { FileStatusWrapper } from '../../../../models/file/file-status.wrapper';
import { PermissionsService } from '../../../../services/permissions.service'; import { PermissionsService } from '../../../../services/permissions.service';
import { Subscription, timer } from 'rxjs'; import { Subscription, timer } from 'rxjs';
@ -49,7 +47,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
public analysisProgress: number; public analysisProgress: number;
public analysisInterval: number; public analysisInterval: number;
fileData: FileDataModel; fileData: FileDataModel;
fileId: string;
annotationData: AnnotationData; annotationData: AnnotationData;
selectedAnnotations: AnnotationWrapper[]; selectedAnnotations: AnnotationWrapper[];
viewReady = false; viewReady = false;
@ -60,7 +57,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
fileReanalysedSubscription: Subscription; fileReanalysedSubscription: Subscription;
hideSkipped = false; hideSkipped = false;
public viewDocumentInfo = false; public viewDocumentInfo = false;
private projectId: string;
private _instance: WebViewerInstance; private _instance: WebViewerInstance;
@ViewChild('fileWorkloadComponent') private _workloadComponent: FileWorkloadComponent; @ViewChild('fileWorkloadComponent') private _workloadComponent: FileWorkloadComponent;
@ -77,25 +73,17 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
private readonly _dialogService: ProjectsDialogService, private readonly _dialogService: ProjectsDialogService,
private readonly _router: Router, private readonly _router: Router,
private readonly _notificationService: NotificationService, private readonly _notificationService: NotificationService,
private readonly _translateService: TranslateService,
private readonly _annotationProcessingService: AnnotationProcessingService, private readonly _annotationProcessingService: AnnotationProcessingService,
private readonly _annotationDrawService: AnnotationDrawService, private readonly _annotationDrawService: AnnotationDrawService,
private readonly _fileActionService: FileActionService, private readonly _fileActionService: FileActionService,
private readonly _manualAnnotationService: ManualAnnotationService,
private readonly _fileDownloadService: PdfViewerDataService, private readonly _fileDownloadService: PdfViewerDataService,
private readonly _formBuilder: FormBuilder, private readonly _formBuilder: FormBuilder,
private readonly _statusControllerService: StatusControllerService, private readonly _statusControllerService: StatusControllerService,
private readonly ngZone: NgZone, private readonly _ngZone: NgZone,
private readonly _fileManagementControllerService: FileManagementControllerService private readonly _fileManagementControllerService: FileManagementControllerService
) { ) {
this._activatedRoute.params.subscribe((params) => { this.reviewerForm = this._formBuilder.group({
this.projectId = params.projectId; reviewer: [this.appStateService.activeFile.currentReviewer]
this.fileId = params.fileId;
this.appStateService.activateFile(this.projectId, this.fileId);
this.reviewerForm = this._formBuilder.group({
reviewer: [this.appStateService.activeFile.currentReviewer]
});
}); });
} }
@ -129,6 +117,14 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
); // on less than 3 seconds show indeterminate ); // on less than 3 seconds show indeterminate
} }
get projectId() {
return this.appStateService.activeProjectId;
}
get fileId() {
return this.appStateService.activeFileId;
}
updateViewMode() { updateViewMode() {
const allAnnotations = this._instance.annotManager.getAnnotationsList(); const allAnnotations = this._instance.annotManager.getAnnotationsList();
@ -262,7 +258,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
} }
openManualAnnotationDialog($event: ManualRedactionEntryWrapper) { openManualAnnotationDialog($event: ManualRedactionEntryWrapper) {
this.ngZone.run(() => { this._ngZone.run(() => {
this.dialogRef = this._dialogService.openManualAnnotationDialog($event, async (response: ManualAnnotationResponse) => { this.dialogRef = this._dialogService.openManualAnnotationDialog($event, async (response: ManualAnnotationResponse) => {
if (response?.annotationId) { if (response?.annotationId) {
const annotation = this.activeViewer.annotManager.getAnnotationById(response.manualRedactionEntryWrapper.rectId); const annotation = this.activeViewer.annotManager.getAnnotationById(response.manualRedactionEntryWrapper.rectId);
@ -545,7 +541,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
private _openFullScreen() { private _openFullScreen() {
const documentElement = document.documentElement; const documentElement = document.documentElement;
if (documentElement.requestFullscreen) { if (documentElement.requestFullscreen) {
documentElement.requestFullscreen(); documentElement.requestFullscreen().then();
} }
} }

View File

@ -1,5 +1,5 @@
import { Component, Injector, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { Component, Injector, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { FileManagementControllerService, Project, RuleSetModel } from '@redaction/red-ui-http'; import { Project, RuleSetModel } from '@redaction/red-ui-http';
import { AppStateService } from '../../../../state/app-state.service'; import { AppStateService } from '../../../../state/app-state.service';
import { UserService } from '../../../../services/user.service'; import { UserService } from '../../../../services/user.service';
import { DoughnutChartConfig } from '../../../shared/components/simple-doughnut-chart/simple-doughnut-chart.component'; import { DoughnutChartConfig } from '../../../shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
@ -16,13 +16,14 @@ import { TranslateService } from '@ngx-translate/core';
import { PermissionsService } from '../../../../services/permissions.service'; import { PermissionsService } from '../../../../services/permissions.service';
import { ProjectWrapper } from '../../../../state/model/project.wrapper'; import { ProjectWrapper } from '../../../../state/model/project.wrapper';
import { Subscription, timer } from 'rxjs'; import { Subscription, timer } from 'rxjs';
import { tap } from 'rxjs/operators'; import { filter, tap } from 'rxjs/operators';
import { TranslateChartService } from '../../../../services/translate-chart.service'; import { TranslateChartService } from '../../../../services/translate-chart.service';
import { RedactionFilterSorter } from '../../../../utils/sorters/redaction-filter-sorter'; import { RedactionFilterSorter } from '../../../../utils/sorters/redaction-filter-sorter';
import { StatusSorter } from '../../../../utils/sorters/status-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 { FilterComponent } from '../../../shared/components/filter/filter.component';
import { ProjectsDialogService } from '../../services/projects-dialog.service'; import { ProjectsDialogService } from '../../services/projects-dialog.service';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { BaseListingComponent } from '../../../shared/base/base-listing.component'; import { BaseListingComponent } from '../../../shared/base/base-listing.component';
@Component({ @Component({
@ -37,8 +38,6 @@ export class ProjectListingScreenComponent extends BaseListingComponent<ProjectW
public projectsChartData: DoughnutChartConfig[] = []; public projectsChartData: DoughnutChartConfig[] = [];
public documentsChartData: DoughnutChartConfig[] = []; public documentsChartData: DoughnutChartConfig[] = [];
public actionMenuOpen: boolean;
public statusFilters: FilterModel[]; public statusFilters: FilterModel[];
public peopleFilters: FilterModel[]; public peopleFilters: FilterModel[];
public needsWorkFilters: FilterModel[]; public needsWorkFilters: FilterModel[];
@ -50,7 +49,10 @@ export class ProjectListingScreenComponent extends BaseListingComponent<ProjectW
statusFilters: [] statusFilters: []
}; };
private projectAutoUpdateTimer: Subscription; private _projectAutoUpdateTimer: Subscription;
private _lastScrollPosition: number;
@ViewChild(CdkVirtualScrollViewport) private _scrollBar: CdkVirtualScrollViewport;
@ViewChild('statusFilter') private _statusFilterComponent: FilterComponent; @ViewChild('statusFilter') private _statusFilterComponent: FilterComponent;
@ViewChild('peopleFilter') private _peopleFilterComponent: FilterComponent; @ViewChild('peopleFilter') private _peopleFilterComponent: FilterComponent;
@ -58,14 +60,13 @@ export class ProjectListingScreenComponent extends BaseListingComponent<ProjectW
@ViewChild('ruleSetFilter') private _ruleSetFilterComponent: FilterComponent; @ViewChild('ruleSetFilter') private _ruleSetFilterComponent: FilterComponent;
constructor( constructor(
private readonly _appStateService: AppStateService,
public readonly userService: UserService, public readonly userService: UserService,
public readonly translateChartService: TranslateChartService,
public readonly permissionsService: PermissionsService, public readonly permissionsService: PermissionsService,
private readonly _dialogService: ProjectsDialogService, private readonly _dialogService: ProjectsDialogService,
private readonly _translateService: TranslateService, private readonly _translateService: TranslateService,
private readonly _router: Router, private readonly _router: Router,
public readonly translateChartService: TranslateChartService, private readonly _appStateService: AppStateService,
private readonly _fileManagementControllerService: FileManagementControllerService,
protected readonly _injector: Injector protected readonly _injector: Injector
) { ) {
super(_injector); super(_injector);
@ -74,7 +75,7 @@ export class ProjectListingScreenComponent extends BaseListingComponent<ProjectW
} }
public ngOnInit(): void { public ngOnInit(): void {
this.projectAutoUpdateTimer = timer(0, 10000) this._projectAutoUpdateTimer = timer(0, 10000)
.pipe( .pipe(
tap(async () => { tap(async () => {
await this._appStateService.loadAllProjects(); await this._appStateService.loadAllProjects();
@ -86,10 +87,19 @@ export class ProjectListingScreenComponent extends BaseListingComponent<ProjectW
this._appStateService.fileChanged.subscribe(() => { this._appStateService.fileChanged.subscribe(() => {
this._calculateData(); 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 { ngOnDestroy(): void {
this.projectAutoUpdateTimer.unsubscribe(); this._projectAutoUpdateTimer.unsubscribe();
} }
private _loadEntitiesFromState() { private _loadEntitiesFromState() {

View File

@ -1,5 +1,5 @@
import { Component, HostListener, Injector, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { Component, HostListener, Injector, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { NavigationEnd, NavigationStart, Router } from '@angular/router';
import { NotificationService, NotificationType } from '../../../../services/notification.service'; import { NotificationService, NotificationType } from '../../../../services/notification.service';
import { AppStateService } from '../../../../state/app-state.service'; import { AppStateService } from '../../../../state/app-state.service';
import { FileDropOverlayService } from '../../../upload-download/services/file-drop-overlay.service'; import { FileDropOverlayService } from '../../../upload-download/services/file-drop-overlay.service';
@ -7,7 +7,6 @@ import { FileUploadModel } from '../../../upload-download/model/file-upload.mode
import { FileUploadService } from '../../../upload-download/services/file-upload.service'; import { FileUploadService } from '../../../upload-download/services/file-upload.service';
import { StatusOverlayService } from '../../../upload-download/services/status-overlay.service'; import { StatusOverlayService } from '../../../upload-download/services/status-overlay.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { FileActionService } from '../../services/file-action.service';
import { FilterModel } from '../../../shared/components/filter/model/filter.model'; import { FilterModel } from '../../../shared/components/filter/model/filter.model';
import * as moment from 'moment'; import * as moment from 'moment';
import { ProjectDetailsComponent } from '../../components/project-details/project-details.component'; import { ProjectDetailsComponent } from '../../components/project-details/project-details.component';
@ -15,15 +14,16 @@ import { FileStatusWrapper } from '../../../../models/file/file-status.wrapper';
import { annotationFilterChecker, keyChecker, processFilters } from '../../../shared/components/filter/utils/filter-utils'; import { annotationFilterChecker, keyChecker, processFilters } from '../../../shared/components/filter/utils/filter-utils';
import { PermissionsService } from '../../../../services/permissions.service'; import { PermissionsService } from '../../../../services/permissions.service';
import { UserService } from '../../../../services/user.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 { Subscription, timer } from 'rxjs';
import { tap } from 'rxjs/operators'; import { filter, tap } from 'rxjs/operators';
import { RedactionFilterSorter } from '../../../../utils/sorters/redaction-filter-sorter'; import { RedactionFilterSorter } from '../../../../utils/sorters/redaction-filter-sorter';
import { StatusSorter } from '../../../../utils/sorters/status-sorter'; import { StatusSorter } from '../../../../utils/sorters/status-sorter';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { convertFiles, handleFileDrop } from '../../../../utils/file-drop-utils'; import { convertFiles, handleFileDrop } from '../../../../utils/file-drop-utils';
import { FilterComponent } from '../../../shared/components/filter/filter.component'; import { FilterComponent } from '../../../shared/components/filter/filter.component';
import { ProjectsDialogService } from '../../services/projects-dialog.service'; import { ProjectsDialogService } from '../../services/projects-dialog.service';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { BaseListingComponent } from '../../../shared/base/base-listing.component'; import { BaseListingComponent } from '../../../shared/base/base-listing.component';
import { ProjectWrapper } from '../../../../state/model/project.wrapper'; import { ProjectWrapper } from '../../../../state/model/project.wrapper';
@ -50,34 +50,30 @@ export class ProjectOverviewScreenComponent extends BaseListingComponent<FileSta
@ViewChild('projectDetailsComponent', { static: false }) @ViewChild('projectDetailsComponent', { static: false })
private _projectDetailsComponent: ProjectDetailsComponent; private _projectDetailsComponent: ProjectDetailsComponent;
private filesAutoUpdateTimer: Subscription; private _filesAutoUpdateTimer: Subscription;
private _lastScrollPosition: number;
@ViewChild(CdkVirtualScrollViewport) private _scrollBar: CdkVirtualScrollViewport;
@ViewChild('statusFilter') private _statusFilterComponent: FilterComponent; @ViewChild('statusFilter') private _statusFilterComponent: FilterComponent;
@ViewChild('peopleFilter') private _peopleFilterComponent: FilterComponent; @ViewChild('peopleFilter') private _peopleFilterComponent: FilterComponent;
@ViewChild('needsWorkFilter') private _needsWorkFilterComponent: FilterComponent; @ViewChild('needsWorkFilter') private _needsWorkFilterComponent: FilterComponent;
constructor( constructor(
private readonly _appStateService: AppStateService,
public readonly userService: UserService, public readonly userService: UserService,
public readonly permissionsService: PermissionsService, public readonly permissionsService: PermissionsService,
private readonly _activatedRoute: ActivatedRoute,
private readonly _notificationService: NotificationService, private readonly _notificationService: NotificationService,
private readonly _dialogService: ProjectsDialogService, private readonly _dialogService: ProjectsDialogService,
private readonly _fileActionService: FileActionService,
private readonly _fileUploadService: FileUploadService, private readonly _fileUploadService: FileUploadService,
private readonly _statusOverlayService: StatusOverlayService, private readonly _statusOverlayService: StatusOverlayService,
private readonly _router: Router, private readonly _router: Router,
private readonly _translateService: TranslateService, private readonly _translateService: TranslateService,
private readonly _fileDropOverlayService: FileDropOverlayService, private readonly _fileDropOverlayService: FileDropOverlayService,
private readonly _fileManagementControllerService: FileManagementControllerService, private readonly _appStateService: AppStateService,
protected readonly _injector: Injector protected readonly _injector: Injector
) { ) {
super(_injector); super(_injector);
this._loadEntitiesFromState();
this._activatedRoute.params.subscribe((params) => {
this._appStateService.activateProject(params.projectId);
this._loadEntitiesFromState();
});
this._appStateService.fileChanged.subscribe(() => { this._appStateService.fileChanged.subscribe(() => {
this.calculateData(); this.calculateData();
@ -85,7 +81,7 @@ export class ProjectOverviewScreenComponent extends BaseListingComponent<FileSta
} }
ngOnInit(): void { ngOnInit(): void {
this.filesAutoUpdateTimer = timer(0, 7500) this._filesAutoUpdateTimer = timer(0, 7500)
.pipe( .pipe(
tap(async () => { tap(async () => {
await this._appStateService.reloadActiveProjectFilesIfNecessary(); await this._appStateService.reloadActiveProjectFilesIfNecessary();
@ -95,11 +91,21 @@ export class ProjectOverviewScreenComponent extends BaseListingComponent<FileSta
.subscribe(); .subscribe();
this._fileDropOverlayService.initFileDropHandling(); this._fileDropOverlayService.initFileDropHandling();
this.calculateData(); 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 { ngOnDestroy(): void {
this._fileDropOverlayService.cleanupFileDropHandling(); this._fileDropOverlayService.cleanupFileDropHandling();
this.filesAutoUpdateTimer.unsubscribe(); this._filesAutoUpdateTimer.unsubscribe();
} }
public get activeProject(): ProjectWrapper { public get activeProject(): ProjectWrapper {
@ -143,7 +149,7 @@ export class ProjectOverviewScreenComponent extends BaseListingComponent<FileSta
} }
private _loadEntitiesFromState() { private _loadEntitiesFromState() {
this.allEntities = this._appStateService.activeProject.files; if (this.activeProject) this.allEntities = this.activeProject.files;
} }
reloadProjects() { reloadProjects() {

View File

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

@ -3,7 +3,6 @@ import {
DictionaryControllerService, DictionaryControllerService,
FileAttributeConfig, FileAttributeConfig,
FileAttributesControllerService, FileAttributesControllerService,
FileManagementControllerService,
FileStatus, FileStatus,
Project, Project,
ProjectControllerService, ProjectControllerService,
@ -16,7 +15,7 @@ import {
} from '@redaction/red-ui-http'; } from '@redaction/red-ui-http';
import { NotificationService, NotificationType } from '../services/notification.service'; import { NotificationService, NotificationType } from '../services/notification.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Router } from '@angular/router'; import { Event, NavigationEnd, ResolveStart, Router } from '@angular/router';
import { UserService } from '../services/user.service'; import { UserService } from '../services/user.service';
import { forkJoin, Observable } from 'rxjs'; import { forkJoin, Observable } from 'rxjs';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
@ -50,7 +49,6 @@ export class AppStateService {
constructor( constructor(
private readonly _router: Router, private readonly _router: Router,
private readonly _userService: UserService, private readonly _userService: UserService,
private readonly _fileManagementControllerService: FileManagementControllerService,
private readonly _projectControllerService: ProjectControllerService, private readonly _projectControllerService: ProjectControllerService,
private readonly _notificationService: NotificationService, private readonly _notificationService: NotificationService,
private readonly _reanalysisControllerService: ReanalysisControllerService, private readonly _reanalysisControllerService: ReanalysisControllerService,
@ -71,6 +69,33 @@ export class AppStateService {
activeDictionaryType: null, activeDictionaryType: null,
versions: {} versions: {}
}; };
this._router.events.subscribe((event: Event) => {
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() { async reloadActiveProjectFilesIfNecessary() {
@ -327,6 +352,7 @@ export class AppStateService {
} }
activateFile(projectId: string, fileId: string) { activateFile(projectId: string, fileId: string) {
if (this._appState.activeProjectId === projectId && this._appState.activeFileId === fileId) return;
this.activateProject(projectId); this.activateProject(projectId);
if (this.activeProject) { if (this.activeProject) {
this._appState.activeFileId = fileId; this._appState.activeFileId = fileId;

View File

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