extract view switch component

This commit is contained in:
Dan Percic 2021-11-25 01:02:10 +02:00
parent ed69b8c340
commit d0f96990c7
9 changed files with 148 additions and 102 deletions

View File

@ -11,7 +11,7 @@ import {
SimpleChanges,
ViewChild,
} from '@angular/core';
import { Dossier, File, IManualRedactionEntry, ViewMode } from '@red/domain';
import { Dossier, File, IManualRedactionEntry } from '@red/domain';
import WebViewer, { Core, WebViewerInstance } from '@pdftron/webviewer';
import { TranslateService } from '@ngx-translate/core';
import {
@ -35,6 +35,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { ActivatedRoute } from '@angular/router';
import { toPosition } from '../../../../utils/pdf-calculation.utils';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { ViewModeService } from '../../services/view-mode.service';
import Tools = Core.Tools;
import TextTool = Tools.TextTool;
import Annotation = Core.Annotations.Annotation;
@ -94,19 +95,9 @@ export class PdfViewerComponent implements OnInit, OnChanges {
private readonly _configService: ConfigService,
private readonly _loadingService: LoadingService,
private readonly _dossiersService: DossiersService,
readonly viewModeService: ViewModeService,
) {}
private _viewMode: ViewMode = 'STANDARD';
get viewMode(): ViewMode {
return this._viewMode;
}
set viewMode(value: ViewMode) {
this._viewMode = value;
this.utils.viewMode = value;
}
private get _dossier(): Dossier {
return this._dossiersService.find(this.file.dossierId);
}
@ -163,7 +154,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
this.instance,
this.file,
() => {
this.viewMode = 'COMPARE';
this.viewModeService.set('COMPARE');
},
() => {
this.utils.navigateToPage(1);
@ -200,7 +191,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
}
async closeCompareMode() {
this.viewMode = 'STANDARD';
this.viewModeService.set('STANDARD');
const pdfNet = this.instance.Core.PDFNet;
await pdfNet.initialize(environment.licenseKey ? atob(environment.licenseKey) : null);
const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await this.fileData.arrayBuffer());
@ -215,7 +206,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
private _setInitialDisplayMode() {
this.instance.UI.setFitMode('FitPage');
const instanceDisplayMode = this.documentViewer.getDisplayModeManager().getDisplayMode();
instanceDisplayMode.mode = this.viewMode === 'STANDARD' ? 'Single' : 'Facing';
instanceDisplayMode.mode = this.viewModeService.isStandard ? 'Single' : 'Facing';
this.documentViewer.getDisplayModeManager().setDisplayMode(instanceDisplayMode);
}
@ -237,7 +228,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
this.documentViewer = this.instance.Core.documentViewer;
this.annotationManager = this.instance.Core.annotationManager;
this.utils = new PdfViewerUtils(this.instance, this.viewMode, this.multiSelectActive);
this.utils = new PdfViewerUtils(this.instance, this.viewModeService, this.multiSelectActive);
this._setSelectionMode();
this._disableElements();

View File

@ -0,0 +1,33 @@
<ng-container *ngIf="viewModeService.viewMode$ | async as viewMode">
<div
(click)="switchView.emit('STANDARD')"
[class.active]="viewModeService.isStandard"
[matTooltip]="'file-preview.standard-tooltip' | translate"
class="red-tab"
iqserHelpMode="standard-view"
>
{{ 'file-preview.standard' | translate }}
</div>
<div
(click)="canSwitchToDeltaView && switchView.emit('DELTA')"
[class.active]="viewModeService.isDelta"
[class.disabled]="!canSwitchToDeltaView"
[matTooltip]="'file-preview.delta-tooltip' | translate"
class="red-tab"
iqserHelpMode="delta-view"
>
{{ 'file-preview.delta' | translate }}
</div>
<div
(click)="canSwitchToRedactedView && switchView.emit('REDACTED')"
[class.active]="viewModeService.isRedacted"
[class.disabled]="!canSwitchToRedactedView"
[matTooltip]="'file-preview.redacted-tooltip' | translate"
class="red-tab"
iqserHelpMode="preview-view"
>
{{ 'file-preview.redacted' | translate }}
</div>
</ng-container>

View File

@ -0,0 +1,33 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { File, ViewMode } from '@red/domain';
import { ViewModeService } from '../../services/view-mode.service';
import { FileDataModel } from '@models/file/file-data.model';
@Component({
selector: 'redaction-view-switch [file] [fileData]',
templateUrl: './view-switch.component.html',
styleUrls: ['./view-switch.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ViewSwitchComponent implements OnChanges {
@Output() readonly switchView = new EventEmitter<ViewMode>();
@Input() file: File;
@Input() fileData: FileDataModel;
canSwitchToDeltaView = false;
canSwitchToRedactedView = false;
constructor(readonly viewModeService: ViewModeService) {}
ngOnChanges(changes: SimpleChanges) {
if (changes.fileData) {
const fileData = changes.fileData.currentValue as FileDataModel;
this.canSwitchToDeltaView = fileData?.hasChangeLog;
}
if (changes.file) {
const file = changes?.file.currentValue as File;
this.canSwitchToRedactedView = !file.analysisRequired && !file.excluded;
}
}
}

View File

@ -3,37 +3,7 @@
<section [class.fullscreen]="fullScreen">
<div class="page-header">
<div class="flex flex-1">
<div
(click)="switchView('STANDARD')"
[class.active]="viewMode === 'STANDARD'"
[matTooltip]="'file-preview.standard-tooltip' | translate"
class="red-tab"
iqserHelpMode="standard-view"
>
{{ 'file-preview.standard' | translate }}
</div>
<div
(click)="canSwitchToDeltaView && switchView('DELTA')"
[class.active]="viewMode === 'DELTA'"
[class.disabled]="!canSwitchToDeltaView"
[matTooltip]="'file-preview.delta-tooltip' | translate"
class="red-tab"
iqserHelpMode="delta-view"
>
{{ 'file-preview.delta' | translate }}
</div>
<div
(click)="canSwitchToRedactedView(file) && switchView('REDACTED')"
[class.active]="viewMode === 'REDACTED'"
[class.disabled]="!canSwitchToRedactedView(file)"
[matTooltip]="'file-preview.redacted-tooltip' | translate"
class="red-tab"
iqserHelpMode="preview-view"
>
{{ 'file-preview.redacted' | translate }}
</div>
<redaction-view-switch (switchView)="switchView($event)" [fileData]="fileData" [file]="file"></redaction-view-switch>
</div>
<div class="flex-1 actions-container">
@ -75,7 +45,7 @@
></iqser-circle-button>
<iqser-circle-button
(action)="assignToMe(file)"
(action)="fileAssignService.assignToMe([file])"
*ngIf="permissionsService.canAssignToSelf(file)"
[tooltip]="'file-preview.assign-me' | translate"
icon="red:assign-me"
@ -145,7 +115,7 @@
(viewerReady)="viewerReady($event)"
*ngIf="displayPDFViewer"
[annotations]="annotations"
[canPerformActions]="canPerformAnnotationActions"
[canPerformActions]="canPerformAnnotationActions$ | async"
[fileData]="displayData"
[file]="file"
[multiSelectActive]="multiSelectActive"
@ -194,7 +164,7 @@
<redaction-annotation-actions
(annotationsChanged)="annotationsChangedByReviewAction($event)"
[annotations]="[annotation]"
[canPerformAnnotationActions]="canPerformAnnotationActions"
[canPerformAnnotationActions]="canPerformAnnotationActions$ | async"
[file]="file"
[viewer]="activeViewer"
></redaction-annotation-actions>

View File

@ -23,6 +23,7 @@ import {
OnAttach,
OnDetach,
processFilters,
shareDistinctLast,
Toaster,
} from '@iqser/common-ui';
import { MatDialogRef, MatDialogState } from '@angular/material/dialog';
@ -35,7 +36,7 @@ import { AnnotationDrawService } from '../../services/annotation-draw.service';
import { AnnotationProcessingService } from '../../services/annotation-processing.service';
import { Dossier, File, User, ViewMode, WorkflowFileStatus } from '@red/domain';
import { PermissionsService } from '@services/permissions.service';
import { Observable, timer } from 'rxjs';
import { combineLatest, Observable, timer } from 'rxjs';
import { UserPreferenceService } from '@services/user-preference.service';
import { UserService } from '@services/user.service';
import { PdfViewerDataService } from '../../services/pdf-viewer-data.service';
@ -51,10 +52,11 @@ import { FileActionsComponent } from '../../shared/components/file-actions/file-
import { FilesService } from '@services/entity-services/files.service';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { FileManagementService } from '@services/entity-services/file-management.service';
import { filter, switchMap, switchMapTo, tap } from 'rxjs/operators';
import { filter, map, switchMap, switchMapTo, tap } from 'rxjs/operators';
import { FilesMapService } from '@services/entity-services/files-map.service';
import { WatermarkService } from '@shared/services/watermark.service';
import { ExcludedPagesService } from './services/excluded-pages.service';
import { ViewModeService } from './services/view-mode.service';
import Annotation = Core.Annotations.Annotation;
import PDFNet = Core.PDFNet;
@ -71,20 +73,19 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
readonly translations = workflowFileStatusTranslations;
dialogRef: MatDialogRef<unknown>;
viewMode: ViewMode = 'STANDARD';
fullScreen = false;
editingReviewer = false;
shouldDeselectAnnotationsOnPageChange = true;
fileData: FileDataModel;
annotationData: AnnotationData;
selectedAnnotations: AnnotationWrapper[];
canPerformAnnotationActions: boolean;
hideSkipped = false;
displayPDFViewer = false;
viewDocumentInfo = false;
@ViewChild(PdfViewerComponent) readonly viewerComponent: PdfViewerComponent;
@ViewChild('fileActions') fileActions: FileActionsComponent;
readonly dossierId: string;
readonly canPerformAnnotationActions$: Observable<boolean>;
readonly dossier$: Observable<Dossier>;
readonly showExcludedPages$: Observable<boolean>;
readonly file$: Observable<File>;
@ -112,7 +113,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
private readonly _toaster: Toaster,
private readonly _annotationProcessingService: AnnotationProcessingService,
private readonly _annotationDrawService: AnnotationDrawService,
private readonly _fileAssignService: FileAssignService,
readonly fileAssignService: FileAssignService,
private readonly _fileDownloadService: PdfViewerDataService,
private readonly _filesService: FilesService,
private readonly _ngZone: NgZone,
@ -123,6 +124,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
private readonly _filesMapService: FilesMapService,
private readonly _dossiersService: DossiersService,
readonly excludedPagesService: ExcludedPagesService,
readonly viewModeService: ViewModeService,
) {
super();
this.dossierId = _activatedRoute.snapshot.paramMap.get('dossierId');
@ -134,6 +136,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}),
);
this.showExcludedPages$ = this._showExcludedPages$;
this.canPerformAnnotationActions$ = this._canPerformAnnotationActions$;
document.documentElement.addEventListener('fullscreenchange', () => {
if (!document.fullscreenElement) {
@ -155,15 +158,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
if (!currentPage) {
return 0;
}
return this.viewerComponent?.viewMode === 'STANDARD'
? currentPage
: currentPage % 2 === 0
? currentPage / 2
: (currentPage + 1) / 2;
}
get canSwitchToDeltaView(): boolean {
return this.fileData?.hasChangeLog;
return this.viewModeService.isStandard ? currentPage : currentPage % 2 === 0 ? currentPage / 2 : (currentPage + 1) / 2;
}
get displayData(): Blob {
@ -178,16 +173,19 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
return this.excludedPagesService.show$.pipe(tap(() => this._disableMultiSelectAndDocumentInfo()));
}
private get _canPerformAnnotationActions$() {
return combineLatest([this.file$, this.viewModeService.viewMode$]).pipe(
map(([file, viewMode]) => this.permissionsService.canPerformAnnotationActions(file) && viewMode === 'STANDARD'),
shareDistinctLast(),
);
}
assignTooltip(file: File): string {
return file.isUnderApproval
? this._translateService.instant('dossier-overview.assign-approver')
: this.assignOrChangeReviewerTooltip(file);
}
canSwitchToRedactedView(file: File): boolean {
return this.fileData && !file.analysisRequired && !file.excluded;
}
assignOrChangeReviewerTooltip(file: File): string {
return file.assignee
? this._translateService.instant('file-preview.change-reviewer')
@ -224,7 +222,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
const annotations = this._getAnnotations(a => a.getCustomData('redacto-manager'));
const redactions = annotations.filter(a => a.getCustomData('redaction'));
switch (this.viewMode) {
switch (this.viewModeService.viewMode) {
case 'STANDARD': {
this._setAnnotationsColor(redactions, 'annotationColor');
const standardEntries = annotations.filter(a => a.getCustomData('changeLogRemoved') === 'false');
@ -252,7 +250,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
await this._stampPDF();
this.rebuildFilters();
this._updateCanPerformActions();
}
ngOnDetach(): void {
@ -274,7 +271,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
async ngOnInit(): Promise<void> {
this._loadingService.start();
await this.userPreferenceService.saveLastOpenedFileForDossier(this.dossierId, this.fileId);
this._updateCanPerformActions();
this._subscribeToFileUpdates();
const file = this._filesMapService.get(this.dossierId, this.fileId);
@ -304,7 +300,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
const newAnnotationsData = this.fileData.getAnnotations(
this._appStateService.dictionaryData[dossier.dossierTemplateId],
this._userService.currentUser,
this.viewMode,
this.viewModeService.viewMode,
this.userPreferenceService.areDevFeaturesEnabled,
);
if (this.annotationData) {
@ -448,7 +444,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
this._instance = $event;
await this._stampPDF();
await this._cleanupAndRedrawManualAnnotations$().toPromise();
this._updateCanPerformActions();
// Go to initial page from query params
const pageNumber = this._lastPage || this._activatedRoute.snapshot.queryParams.page;
@ -468,7 +463,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
ocredFile(): void {
this._updateCanPerformActions();
this._reloadFileOnReanalysis = true;
}
@ -478,10 +472,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
this.excludedPagesService.hide();
}
async assignToMe(file: File) {
await this._fileAssignService.assignToMe([file]);
}
async assignReviewer(file: File, user: User | string) {
const assigneeId = typeof user === 'string' ? user : user?.id;
const reviewerName = this._userService.getNameForId(assigneeId);
@ -508,7 +498,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
async switchView(viewMode: ViewMode) {
this.viewMode = viewMode;
this.viewModeService.set(viewMode);
await this.updateViewMode();
this._scrollViews();
}
@ -562,7 +552,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
const allPages = [...Array(file.numberOfPages).keys()].map(page => page + 1);
await clearStamps(document, pdfNet, allPages);
if (this.viewMode === 'REDACTED') {
if (this.viewModeService.viewMode === 'REDACTED') {
const dossier = this._dossiersService.find(this.dossierId);
if (dossier.watermarkPreviewEnabled) {
await this._stampPreview(document, dossier.dossierTemplateId);
@ -610,9 +600,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
this.addSubscription = timer(0, 5000)
.pipe(switchMapTo(this._filesService.reload(this.dossierId, this.fileId)))
.subscribe();
this.addSubscription = this.file$.subscribe(() => {
this._updateCanPerformActions();
});
this.addSubscription = this._filesMapService.fileReanalysed$
.pipe(filter(file => file.fileId === this.fileId))
.subscribe(async file => {
@ -623,14 +610,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
});
}
private _updateCanPerformActions() {
const file = this._filesMapService.get(this.dossierId, this.fileId);
this.canPerformAnnotationActions =
this.permissionsService.canPerformAnnotationActions(file) &&
this.viewMode === 'STANDARD' &&
!this.viewerComponent?.utils.isCompareMode;
}
private async _loadFileData(file: File, performUpdate = false): Promise<void> {
const fileData = await this._fileDownloadService.loadDataFor(file).toPromise();
@ -676,7 +655,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
this.rebuildFilters();
if (this.viewMode === 'STANDARD') {
if (this.viewModeService.viewMode === 'STANDARD') {
currentPageAnnotationIds.forEach(id => {
this._findAndDeleteAnnotation(id);
});
@ -693,7 +672,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
this.fileId,
this.dossierId,
this.hideSkipped,
!!this.viewerComponent?.utils?.isCompareMode,
!!this.viewModeService.isCompare,
);
}

View File

@ -17,6 +17,8 @@ import { CommentsComponent } from './components/comments/comments.component';
import { DocumentInfoComponent } from './components/document-info/document-info.component';
import { TypeAnnotationIconComponent } from './components/type-annotation-icon/type-annotation-icon.component';
import { OverlayModule } from '@angular/cdk/overlay';
import { ViewSwitchComponent } from './components/view-switch/view-switch.component';
import { ViewModeService } from './services/view-mode.service';
const routes: Routes = [
{
@ -40,6 +42,7 @@ const routes: Routes = [
CommentsComponent,
DocumentInfoComponent,
TypeAnnotationIconComponent,
ViewSwitchComponent,
],
imports: [
RouterModule.forChild(routes),
@ -50,5 +53,6 @@ const routes: Routes = [
TranslateModule,
OverlayModule,
],
providers: [ViewModeService],
})
export class FilePreviewModule {}

View File

@ -0,0 +1,37 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { ViewMode } from '@red/domain';
@Injectable()
export class ViewModeService {
readonly viewMode$: Observable<ViewMode>;
private readonly _viewMode$ = new BehaviorSubject<ViewMode>('STANDARD');
constructor() {
this.viewMode$ = this._viewMode$.asObservable();
}
get viewMode() {
return this._viewMode$.value;
}
get isStandard() {
return this._viewMode$.value === 'STANDARD';
}
get isDelta() {
return this._viewMode$.value === 'DELTA';
}
get isCompare() {
return this._viewMode$.value === 'COMPARE';
}
get isRedacted() {
return this._viewMode$.value === 'REDACTED';
}
set(mode: ViewMode) {
this._viewMode$.next(mode);
}
}

View File

@ -1,7 +1,7 @@
import { ViewMode } from '@red/domain';
import { translateQuads } from '@utils/pdf-coordinates';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { Core, WebViewerInstance } from '@pdftron/webviewer';
import { ViewModeService } from '../screens/file-preview-screen/services/view-mode.service';
import Annotation = Core.Annotations.Annotation;
const DISABLED_HOTKEYS = [
@ -41,14 +41,10 @@ export class PdfViewerUtils {
ready = false;
excludedPages: number[] = [];
constructor(readonly instance: WebViewerInstance, public viewMode: ViewMode, public multiSelectActive: boolean) {}
get isCompareMode() {
return this.viewMode === 'COMPARE';
}
constructor(readonly instance: WebViewerInstance, readonly viewModeService: ViewModeService, public multiSelectActive: boolean) {}
get paginationOffset() {
return this.isCompareMode ? 2 : 1;
return this.viewModeService.isCompare ? 2 : 1;
}
get isCurrentPageExcluded() {
@ -58,7 +54,7 @@ export class PdfViewerUtils {
get currentPage() {
try {
return this.isCompareMode ? Math.ceil(this._currentInternalPage / 2) : this._currentInternalPage;
return this.viewModeService.isCompare ? Math.ceil(this._currentInternalPage / 2) : this._currentInternalPage;
} catch (e) {
return null;
}
@ -70,7 +66,7 @@ export class PdfViewerUtils {
}
try {
return this.isCompareMode ? Math.ceil(this._totalInternalPages / 2) : this._totalInternalPages;
return this.viewModeService.isCompare ? Math.ceil(this._totalInternalPages / 2) : this._totalInternalPages;
} catch (e) {
return null;
}