PDF Viewer Sync

This commit is contained in:
Timo 2021-02-01 14:22:35 +02:00
parent 2fbbd1d480
commit ceaf97414a
11 changed files with 104 additions and 214 deletions

View File

@ -4,7 +4,6 @@ import { FileStatusWrapper } from '../../screens/file/model/file-status.wrapper'
import { DialogService } from '../../dialogs/dialog.service';
import { AppStateService } from '../../state/app-state.service';
import { FileActionService } from '../../screens/file/service/file-action.service';
import { SingleFileDownloadService } from '../../screens/file/service/single-file-download.service';
@Component({
selector: 'redaction-file-actions',
@ -22,8 +21,7 @@ export class FileActionsComponent implements OnInit {
public readonly permissionsService: PermissionsService,
public readonly appStateService: AppStateService,
private readonly _dialogService: DialogService,
private readonly _fileActionService: FileActionService,
private readonly _fileDownloadService: SingleFileDownloadService
private readonly _fileActionService: FileActionService
) {}
ngOnInit(): void {

View File

@ -5,7 +5,7 @@ import { FileStatusWrapper } from '../../../screens/file/model/file-status.wrapp
import { FileManagementControllerService } from '@redaction/red-ui-http';
import { StatusOverlayService } from '../../../upload-download/status-overlay.service';
import { FileDownloadService } from '../../../upload-download/file-download.service';
import { SingleFileDownloadService } from '../../../screens/file/service/single-file-download.service';
import { PdfViewerDataService } from '../../../screens/file/service/pdf-viewer-data.service';
import { saveAs } from 'file-saver';
import { UserPreferenceService } from '../../../common/service/user-preference.service';
@ -28,7 +28,6 @@ export class FileDownloadBtnComponent {
constructor(
private readonly _permissionsService: PermissionsService,
private readonly _fileDownloadService: FileDownloadService,
private readonly _singleFileDownloadService: SingleFileDownloadService,
private readonly _userPreferencesService: UserPreferenceService,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _statusOverlayService: StatusOverlayService,
@ -82,7 +81,7 @@ export class FileDownloadBtnComponent {
this._statusOverlayService.openDownloadStatusOverlay();
this._changeDetectorRef.detectChanges();
} else {
this._singleFileDownloadService.loadFile('REDACTED', this.file).subscribe((data) => saveAs(data, (<FileStatusWrapper>this.file).filename));
// this._singleFileDownloadService.loadFile('REDACTED', this.file).subscribe((data) => saveAs(data, (<FileStatusWrapper>this.file).filename));
}
}
@ -92,8 +91,8 @@ export class FileDownloadBtnComponent {
downloadFlatRedactedFiles($event: MouseEvent) {
$event.preventDefault();
this._singleFileDownloadService
.loadFile('FLAT_REDACTED', <FileStatusWrapper>this.file)
.subscribe((data) => saveAs(data, (<FileStatusWrapper>this.file).filename));
// this._singleFileDownloadService
// .loadFile('FLAT_REDACTED', <FileStatusWrapper>this.file)
// .subscribe((data) => saveAs(data, (<FileStatusWrapper>this.file).filename));
}
}

View File

@ -27,7 +27,7 @@ import { UserPreferenceService } from '../../../common/service/user-preference.s
import { UserService } from '../../../user/user.service';
import { FormBuilder, FormGroup } from '@angular/forms';
import { StatusControllerService } from '@redaction/red-ui-http';
import { SingleFileDownloadService } from '../service/single-file-download.service';
import { PdfViewerDataService } from '../service/pdf-viewer-data.service';
const KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'f', 'F'];
@ -78,7 +78,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
private readonly _annotationDrawService: AnnotationDrawService,
private readonly _fileActionService: FileActionService,
private readonly _manualAnnotationService: ManualAnnotationService,
private readonly _fileDownloadService: SingleFileDownloadService,
private readonly _fileDownloadService: PdfViewerDataService,
private readonly _formBuilder: FormBuilder,
private readonly _statusControllerService: StatusControllerService,
private readonly ngZone: NgZone
@ -100,6 +100,21 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
set redactedView(value: boolean) {
this._activeViewer = value ? 'REDACTED' : 'ANNOTATED';
const allAnnotations = this.instance.annotManager.getAnnotationsList();
const redactions = allAnnotations.filter((a) => a.getCustomData('redaction'));
if (this._activeViewer === 'ANNOTATED') {
redactions.forEach((redaction) => {
redaction['StrokeColor'] = redaction.getCustomData('annotationColor');
});
this.instance.annotManager.showAnnotations(allAnnotations);
} else {
const other = allAnnotations.filter((a) => !a.getCustomData('redaction'));
redactions.forEach((redaction) => {
redaction['StrokeColor'] = redaction.getCustomData('redactionColor');
});
this.instance.annotManager.hideAnnotations(other);
}
this._updateCanPerformActions();
}
@ -120,7 +135,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
}
get canNotSwitchToRedactedView() {
return this.permissionsService.fileRequiresReanalysis() || !this.fileData?.redactedFileData;
return this.permissionsService.fileRequiresReanalysis();
}
ngOnInit(): void {
@ -141,7 +156,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
this.loadingMessage = null;
this._updateCanPerformActions();
this._cleanupAndRedrawManualAnnotations();
this._fileDownloadService.loadRedactedView(this.fileData);
});
}
});
@ -159,7 +173,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
this.fileData.redactionLog = fileDataModel.redactionLog;
this.fileData.fileStatus = fileDataModel.fileStatus;
this.fileData.manualRedactions = fileDataModel.manualRedactions;
this.fileData.redactedFileData = null;
this._rebuildFilters(true);
} else {
this.fileData = fileDataModel;
@ -444,10 +457,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
this.instance = $event;
this.viewReady = true;
this._cleanupAndRedrawManualAnnotations();
if (!this.fileData.redactedFileData) {
this._fileDownloadService.loadRedactedView(this.fileData);
}
// Go to initial page from query params
const pageNumber = this._activatedRoute.snapshot.queryParams.page;
if (pageNumber) {
@ -470,10 +479,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
this._fileDownloadService.loadActiveFileManualAnnotations().subscribe((manualRedactions) => {
this.fileData.manualRedactions = manualRedactions;
this._rebuildFilters();
if (!this.redactedView) {
this._annotationDrawService.drawAnnotations(this.instance, this.annotations);
document.querySelectorAll('iframe')[0].click();
}
this._annotationDrawService.drawAnnotations(this.instance, this.annotations, this.redactedView);
});
}
@ -492,7 +498,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
const newPageAnnotations = this.annotations.filter((item) => item.pageNumber === page);
this._handleDeltaAnnotationFilters(currentPageAnnotations, newPageAnnotations);
this._annotationDrawService.drawAnnotations(this.instance, newPageAnnotations);
document.querySelectorAll('iframe')[0].click();
}
});
}
@ -537,7 +542,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy {
}
get displayData() {
return this.fileData ? (this.redactedView ? this.fileData.redactedFileData : this.fileData.annotatedFileData) : null;
return this.fileData?.fileData;
}
public async assignToMe() {

View File

@ -5,11 +5,9 @@ import { AnnotationWrapper } from './annotation.wrapper';
import { RedactionLogEntryWrapper } from './redaction-log-entry.wrapper';
export class FileDataModel {
redactedFileData: Blob;
constructor(
public fileStatus: FileStatusWrapper,
public annotatedFileData: Blob,
public fileData: Blob,
public redactionLog: RedactionLog,
public manualRedactions: ManualRedactions,
public viewedPages?: ViewedPages

View File

@ -27,17 +27,6 @@ import { AnnotationDrawService } from '../service/annotation-draw.service';
import { AnnotationActionsService } from '../../../common/service/annotation-actions.service';
import { UserPreferenceService } from '../../../common/service/user-preference.service';
import { translateQuads } from '../../../utils/pdf-coordinates';
import { SingleFileDownloadService } from '../service/single-file-download.service';
export interface ViewerState {
displayMode?: any;
layoutMode?: any;
pageNumber?: any;
scrollTop?: any;
scrollLeft?: any;
zoom?: any;
leftPanelState?: any;
}
@Component({
selector: 'redaction-pdf-viewer',
@ -45,7 +34,6 @@ export interface ViewerState {
styleUrls: ['./pdf-viewer.component.scss']
})
export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
private _viewerState: ViewerState = null; // no initial state
private _selectedText = '';
public searching = false;
@ -73,7 +61,6 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
private readonly kc: KeycloakService,
private readonly _appStateService: AppStateService,
private readonly _translateService: TranslateService,
private readonly _fileDownloadService: SingleFileDownloadService,
private readonly _appConfigService: AppConfigService,
private readonly _manualAnnotationService: ManualAnnotationService,
private readonly _ngZone: NgZone,
@ -389,10 +376,6 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
private _loadDocument() {
if (this.fileData) {
// if it's not first load - save viewer state
if (this._viewerState) {
this._viewerState = this._getCurrentViewerState();
}
this.instance.loadDocument(this.fileData, {
filename: this.fileStatus ? this.fileStatus.filename : 'document.pdf'
});
@ -402,56 +385,10 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
private _documentLoaded() {
this._ngZone.run(() => {
this.viewerReady.emit(this.instance);
if (!this._viewerState) {
this.setInitialViewerState();
this._viewerState = this._getCurrentViewerState();
} else {
this._restoreState(this._viewerState);
}
this.setInitialViewerState();
});
}
private _getCurrentViewerState() {
const instance = this.instance;
const docViewer = this.instance.docViewer;
const lastScrolledViewerScrollElement = docViewer.getScrollViewElement();
const viewerState: ViewerState = {
displayMode: docViewer.getDisplayModeManager().getDisplayMode().mode,
layoutMode: instance.getLayoutMode(),
pageNumber: instance.docViewer.getCurrentPage(),
scrollLeft: lastScrolledViewerScrollElement.scrollLeft,
scrollTop: lastScrolledViewerScrollElement.scrollTop,
zoom: docViewer.getZoom(),
leftPanelState: instance.isElementOpen('leftPanel')
};
return viewerState;
}
private _restoreState(viewerState: ViewerState) {
this.instance.docViewer.setCurrentPage(viewerState.pageNumber);
this.instance.setLayoutMode(viewerState.layoutMode);
const instanceDisplayMode = this.instance.docViewer.getDisplayModeManager().getDisplayMode();
instanceDisplayMode.mode = viewerState.displayMode;
this.instance.docViewer.getDisplayModeManager().setDisplayMode(instanceDisplayMode);
// Synchronize zoom - needs to be done before scrolling
if (viewerState.zoom === 0) {
this.instance.setFitMode('FitPage');
} else {
this.instance.docViewer.zoomTo(viewerState.zoom);
}
const viewerScrollElement = this.instance.docViewer.getScrollViewElement();
viewerScrollElement.scrollTo(viewerState.scrollLeft, viewerState.scrollTop);
if (viewerState.leftPanelState) {
this.instance.openElements(['leftPanel']);
} else {
this.instance.closeElements(['leftPanel']);
}
this._viewerState = viewerState;
}
setInitialViewerState() {
// viewer init
this.instance.setFitMode('FitPage');

View File

@ -16,10 +16,17 @@ export class AnnotationDrawService {
private readonly _userPreferenceService: UserPreferenceService
) {}
public drawAnnotations(activeViewer: WebViewerInstance, annotationWrappers: AnnotationWrapper[]) {
public drawAnnotations(activeViewer: WebViewerInstance, annotationWrappers: AnnotationWrapper[], redactedOnly: boolean = false) {
const annotations = [];
annotationWrappers.forEach((annotation) => {
annotations.push(this.computeAnnotation(activeViewer, annotation));
if (redactedOnly) {
if (annotation.isRedacted) {
const pdfViewerAnnotation = this.computeAnnotation(activeViewer, annotation);
annotations.push(pdfViewerAnnotation);
}
} else {
annotations.push(this.computeAnnotation(activeViewer, annotation));
}
});
const annotationManager = activeViewer.annotManager;
@ -81,6 +88,9 @@ export class AnnotationDrawService {
highlight.Quads = this._rectanglesToQuads(annotationWrapper.positions, activeViewer, pageNumber);
highlight.Id = annotationWrapper.id;
highlight.ReadOnly = true;
highlight.setCustomData('redaction', annotationWrapper.isRedacted);
highlight.setCustomData('redactionColor', this.getColor(activeViewer, 'ignore', 'ignore'));
highlight.setCustomData('annotationColor', this.getColor(activeViewer, annotationWrapper.superType, annotationWrapper.dictionary));
return highlight;
}

View File

@ -0,0 +1,59 @@
import { Injectable } from '@angular/core';
import { forkJoin, Observable, of } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import {
FileManagementControllerService,
ManualRedactionControllerService,
RedactionLogControllerService,
ViewedPagesControllerService
} from '@redaction/red-ui-http';
import { FileType } from '../model/file-type';
import { FileDataModel } from '../model/file-data.model';
import { AppStateService } from '../../../state/app-state.service';
import { PermissionsService } from '../../../common/service/permissions.service';
import { FileStatusWrapper } from '../model/file-status.wrapper';
@Injectable({
providedIn: 'root'
})
export class PdfViewerDataService {
constructor(
private readonly _appStateService: AppStateService,
private readonly _permissionsService: PermissionsService,
private readonly _fileManagementControllerService: FileManagementControllerService,
private readonly _manualRedactionControllerService: ManualRedactionControllerService,
private readonly _redactionLogControllerService: RedactionLogControllerService,
private readonly _viewedPagesControllerService: ViewedPagesControllerService
) {}
loadActiveFileManualAnnotations() {
return this._manualRedactionControllerService.getManualRedaction(this._appStateService.activeProjectId, this._appStateService.activeFileId);
}
loadActiveFileData(): Observable<FileDataModel> {
const fileObs = this.downloadOriginalFile(this._appStateService.activeFile);
const reactionLogObs = this._redactionLogControllerService.getRedactionLog(this._appStateService.activeProjectId, this._appStateService.activeFileId);
const manualRedactionsObs = this._manualRedactionControllerService.getManualRedaction(
this._appStateService.activeProjectId,
this._appStateService.activeFileId
);
const viewedPagesObs = this.getViewedPagesForActiveFile();
return forkJoin([fileObs, reactionLogObs, manualRedactionsObs, viewedPagesObs]).pipe(
map((data) => {
return new FileDataModel(this._appStateService.activeFile, ...data);
})
);
}
getViewedPagesForActiveFile() {
if (this._permissionsService.canMarkPagesAsViewed()) {
return this._viewedPagesControllerService.getViewedPages(this._appStateService.activeProjectId, this._appStateService.activeFileId);
}
return of({ pages: [] });
}
downloadOriginalFile(fileStatus: FileStatusWrapper): Observable<any> {
return this._fileManagementControllerService.downloadOriginalFile(fileStatus.projectId, fileStatus.fileId, true, fileStatus.lastUploaded, 'body');
}
}

View File

@ -1,116 +0,0 @@
import { Injectable } from '@angular/core';
import { forkJoin, Observable, of } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import {
FileManagementControllerService,
ManualRedactionControllerService,
RedactionLogControllerService,
ViewedPagesControllerService
} from '@redaction/red-ui-http';
import { FileType } from '../model/file-type';
import { FileDataModel } from '../model/file-data.model';
import { AppStateService } from '../../../state/app-state.service';
import { PermissionsService } from '../../../common/service/permissions.service';
import { FileStatusWrapper } from '../model/file-status.wrapper';
@Injectable({
providedIn: 'root'
})
export class SingleFileDownloadService {
constructor(
private readonly _appStateService: AppStateService,
private readonly _permissionsService: PermissionsService,
private readonly _fileManagementControllerService: FileManagementControllerService,
private readonly _manualRedactionControllerService: ManualRedactionControllerService,
private readonly _redactionLogControllerService: RedactionLogControllerService,
private readonly _viewedPagesControllerService: ViewedPagesControllerService
) {}
loadActiveFileManualAnnotations() {
return this._manualRedactionControllerService.getManualRedaction(this._appStateService.activeProjectId, this._appStateService.activeFileId);
}
loadActiveFileData(): Observable<FileDataModel> {
const fileObs = this.loadFile('ORIGINAL', this._appStateService.activeFile);
const reactionLogObs = this._redactionLogControllerService.getRedactionLog(this._appStateService.activeProjectId, this._appStateService.activeFileId);
const manualRedactionsObs = this._manualRedactionControllerService.getManualRedaction(
this._appStateService.activeProjectId,
this._appStateService.activeFileId
);
const viewedPagesObs = this.getViewedPagesForActiveFile();
return forkJoin([fileObs, reactionLogObs, manualRedactionsObs, viewedPagesObs]).pipe(
map((data) => {
return new FileDataModel(this._appStateService.activeFile, ...data);
})
);
}
loadRedactedView(fileData: FileDataModel) {
this.loadFile('REDACTED', fileData.fileStatus).subscribe((redactedFileData) => (fileData.redactedFileData = redactedFileData));
}
getViewedPagesForActiveFile() {
if (this._permissionsService.canMarkPagesAsViewed()) {
return this._viewedPagesControllerService.getViewedPages(this._appStateService.activeProjectId, this._appStateService.activeFileId);
}
return of({ pages: [] });
}
loadFile(
fileType: FileType | string,
fileStatus: FileStatusWrapper,
saveTo: (fileData) => void = () => null,
fetch: () => any = () => null
): Observable<any> {
let fileObs$: Observable<any>;
switch (fileType) {
case FileType.ANNOTATED:
fileObs$ = fetch()
? of(fetch())
: this._fileManagementControllerService
.downloadAnnotatedFile(fileStatus.projectId, fileStatus.fileId, true, fileStatus.lastProcessed, 'body')
.pipe(
tap((data) => {
saveTo(data);
})
);
break;
case FileType.REDACTED:
fileObs$ = fetch()
? of(fetch())
: this._fileManagementControllerService
.downloadRedactedFile(fileStatus.projectId, fileStatus.fileId, true, fileStatus.lastProcessed, 'body')
.pipe(
tap((data) => {
saveTo(data);
})
);
break;
case FileType.FLAT_REDACTED:
fileObs$ = fetch()
? of(fetch())
: this._fileManagementControllerService
.downloadFlatRedacted(fileStatus.projectId, fileStatus.fileId, true, fileStatus.lastProcessed, 'body')
.pipe(
tap((data) => {
saveTo(data);
})
);
break;
case FileType.ORIGINAL:
default:
fileObs$ = fetch()
? of(fetch())
: this._fileManagementControllerService
.downloadOriginalFile(fileStatus.projectId, fileStatus.fileId, true, fileStatus.lastUploaded, 'body')
.pipe(
tap((data) => {
saveTo(data);
})
);
break;
}
return fileObs$;
}
}

View File

@ -4,7 +4,7 @@ import { DebugControllerService } from '@redaction/red-ui-http';
import { FileType } from '../file/model/file-type';
import { FileStatusWrapper } from '../file/model/file-status.wrapper';
import { mergeMap } from 'rxjs/operators';
import { SingleFileDownloadService } from '../file/service/single-file-download.service';
import { PdfViewerDataService } from '../file/service/pdf-viewer-data.service';
@Component({
selector: 'redaction-html-debug-screen',
@ -22,7 +22,7 @@ export class HtmlDebugScreenComponent {
private readonly _activatedRoute: ActivatedRoute,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _debugControllerService: DebugControllerService,
private readonly _fileDownloadService: SingleFileDownloadService
private readonly _fileDownloadService: PdfViewerDataService
) {
this._activatedRoute.params.subscribe((params) => {
this._fileId = params.fileId;

View File

@ -4,7 +4,7 @@ import { environment } from '../../../environments/environment';
import { ActivatedRoute } from '@angular/router';
import { FileType } from '../file/model/file-type';
import { FileStatusWrapper } from '../file/model/file-status.wrapper';
import { SingleFileDownloadService } from '../file/service/single-file-download.service';
import { PdfViewerDataService } from '../file/service/pdf-viewer-data.service';
@Component({
selector: 'redaction-pdf-viewer-screen',
@ -23,7 +23,7 @@ export class PdfViewerScreenComponent implements OnInit {
constructor(
private readonly _activatedRoute: ActivatedRoute,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _fileDownloadService: SingleFileDownloadService
private readonly _fileDownloadService: PdfViewerDataService
) {
this._activatedRoute.params.subscribe((params) => {
this._fileId = params.fileId;

View File

@ -1,7 +1,7 @@
{
"OAUTH_URL": "https://redkc-staging.iqser.cloud/auth/realms/redaction",
"OAUTH_CLIENT_ID": "redaction",
"API_URL": "http://localhost:8080",
"API_URL": "https://timo-redaction-dev.iqser.cloud",
"BACKEND_APP_VERSION": "4.4.40",
"FRONTEND_APP_VERSION": "1.0",
"EULA_URL": "EULA_URL",