RED-3988: transform old pdf viewer component into pdf proxy service
This commit is contained in:
parent
669ec2ba1d
commit
78e49d609e
@ -4,9 +4,10 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { annotationChangesTranslations } from '@translations/annotation-changes-translations';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { MultiSelectService } from '../../services/multi-select.service';
|
||||
import { KeysOf, ListingService } from '@iqser/common-ui';
|
||||
import { KeysOf } from '@iqser/common-ui';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { filter, map, switchMap } from 'rxjs/operators';
|
||||
import { AnnotationsListingService } from '../../services/annotations-listing.service';
|
||||
|
||||
interface Engine {
|
||||
readonly icon: string;
|
||||
@ -41,7 +42,7 @@ export class AnnotationDetailsComponent implements OnChanges {
|
||||
|
||||
constructor(
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _listingService: ListingService<AnnotationWrapper>,
|
||||
private readonly _listingService: AnnotationsListingService,
|
||||
readonly multiSelectService: MultiSelectService,
|
||||
) {
|
||||
this.isSelected$ = this._annotationChanged$.pipe(switchMap(annotation => this._listingService.isSelected$(annotation)));
|
||||
|
||||
@ -2,8 +2,8 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostBinding, Inp
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { BehaviorSubject, filter } from 'rxjs';
|
||||
import { switchMap, tap } from 'rxjs/operators';
|
||||
import { ListingService } from '@iqser/common-ui';
|
||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import { AnnotationsListingService } from '../../services/annotations-listing.service';
|
||||
|
||||
@UntilDestroy()
|
||||
@Component({
|
||||
@ -17,7 +17,7 @@ export class AnnotationReferenceComponent implements OnChanges {
|
||||
@HostBinding('class.active') isSelected = false;
|
||||
private readonly _annotationChanged$ = new BehaviorSubject<AnnotationWrapper>(undefined);
|
||||
|
||||
constructor(private readonly _listingService: ListingService<AnnotationWrapper>, private readonly _changeRef: ChangeDetectorRef) {
|
||||
constructor(private readonly _listingService: AnnotationsListingService, private readonly _changeRef: ChangeDetectorRef) {
|
||||
this._annotationChanged$
|
||||
.pipe(
|
||||
filter(annotation => !!annotation),
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Output } from '@angular/core';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { AnnotationReferencesService } from '../../services/annotation-references.service';
|
||||
import { ListingService } from '@iqser/common-ui';
|
||||
import { Observable, switchMap } from 'rxjs';
|
||||
import { AnnotationsListingService } from '../../services/annotations-listing.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-annotation-references-list',
|
||||
@ -15,7 +15,7 @@ export class AnnotationReferencesListComponent {
|
||||
readonly isSelected$: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
private readonly _listingService: ListingService<AnnotationWrapper>,
|
||||
private readonly _listingService: AnnotationsListingService,
|
||||
readonly annotationReferencesService: AnnotationReferencesService,
|
||||
) {
|
||||
this.isSelected$ = this.annotationReferencesService.annotation$.pipe(
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostBinding, Input, OnChanges, TemplateRef } from '@angular/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { ListingService } from '@iqser/common-ui';
|
||||
import { switchMap, tap } from 'rxjs/operators';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { MultiSelectService } from '../../services/multi-select.service';
|
||||
import { AnnotationsListingService } from '../../services/annotations-listing.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-annotation-wrapper [annotation] [annotationActionsTemplate]',
|
||||
@ -23,7 +23,7 @@ export class AnnotationWrapperComponent implements OnChanges {
|
||||
|
||||
constructor(
|
||||
private readonly _changeRef: ChangeDetectorRef,
|
||||
readonly listingService: ListingService<AnnotationWrapper>,
|
||||
readonly listingService: AnnotationsListingService,
|
||||
readonly multiSelectService: MultiSelectService,
|
||||
) {
|
||||
this.isSelected$ = this._annotationChanged$.pipe(
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
.searching {
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
right: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
mat-spinner {
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
@ -13,6 +13,7 @@ import { dossiersServiceProvider } from '@services/entity-services/dossiers.serv
|
||||
import { FileDataService } from './services/file-data.service';
|
||||
import { AnnotationsListingService } from './services/annotations-listing.service';
|
||||
import { StampService } from './services/stamp.service';
|
||||
import { PdfProxyService } from './services/pdf-proxy.service';
|
||||
|
||||
export const filePreviewScreenProviders = [
|
||||
FilterService,
|
||||
@ -33,4 +34,5 @@ export const filePreviewScreenProviders = [
|
||||
{ provide: ListingService, useExisting: AnnotationsListingService },
|
||||
SearchService,
|
||||
StampService,
|
||||
PdfProxyService,
|
||||
];
|
||||
|
||||
@ -62,15 +62,7 @@
|
||||
<div class="overlay-shadow"></div>
|
||||
|
||||
<div class="content-inner">
|
||||
<div class="content-container">
|
||||
<redaction-pdf-viewer
|
||||
(annotationSelected)="handleAnnotationSelected($event)"
|
||||
(manualAnnotationRequested)="openManualAnnotationDialog($event)"
|
||||
(pageChanged)="viewerPageChanged($event)"
|
||||
[canPerformActions]="canPerformAnnotationActions$ | async"
|
||||
[dossier]="dossier"
|
||||
></redaction-pdf-viewer>
|
||||
</div>
|
||||
<div class="content-container"></div>
|
||||
|
||||
<div class="right-container">
|
||||
<iqser-empty-state
|
||||
@ -98,7 +90,7 @@
|
||||
<ng-template #annotationActionsTemplate let-annotation="annotation">
|
||||
<redaction-annotation-actions
|
||||
[annotations]="[annotation]"
|
||||
[canPerformAnnotationActions]="canPerformAnnotationActions$ | async"
|
||||
[canPerformAnnotationActions]="pdfProxyService.canPerformAnnotationActions$ | async"
|
||||
[iqserHelpMode]="getActionsHelpModeKey(annotation)"
|
||||
[scrollableParentView]="scrollableParentView"
|
||||
></redaction-annotation-actions>
|
||||
|
||||
@ -3,20 +3,20 @@ import { ActivatedRoute, ActivatedRouteSnapshot, NavigationExtras, Router } from
|
||||
import { Core } from '@pdftron/webviewer';
|
||||
import {
|
||||
AutoUnsubscribe,
|
||||
bool,
|
||||
CircleButtonTypes,
|
||||
CustomError,
|
||||
Debounce,
|
||||
ErrorService,
|
||||
FilterService,
|
||||
ListingService,
|
||||
LoadingService,
|
||||
log,
|
||||
NestedFilter,
|
||||
OnAttach,
|
||||
OnDetach,
|
||||
processFilters,
|
||||
ScrollableParentView,
|
||||
ScrollableParentViews,
|
||||
shareDistinctLast,
|
||||
} from '@iqser/common-ui';
|
||||
import { MatDialogRef, MatDialogState } from '@angular/material/dialog';
|
||||
import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
|
||||
@ -25,7 +25,7 @@ import { AnnotationDrawService } from '../pdf-viewer/services/annotation-draw.se
|
||||
import { AnnotationProcessingService } from './services/annotation-processing.service';
|
||||
import { File, ViewMode, ViewModes } from '@red/domain';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { combineLatest, firstValueFrom, Observable, of, pairwise } from 'rxjs';
|
||||
import { combineLatest, firstValueFrom, from, of, pairwise } from 'rxjs';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { download, handleFilterDelta } from '../../utils';
|
||||
import { FilesService } from '@services/files/files.service';
|
||||
@ -46,7 +46,7 @@ import { PageRotationService } from '../pdf-viewer/services/page-rotation.servic
|
||||
import { ComponentCanDeactivate } from '@guards/can-deactivate.guard';
|
||||
import { FilePreviewDialogService } from './services/file-preview-dialog.service';
|
||||
import { FileDataService } from './services/file-data.service';
|
||||
import { ActionsHelpModeKeys, ALL_HOTKEYS } from './utils/constants';
|
||||
import { ActionsHelpModeKeys, ALL_HOTKEYS, TextPopups } from './utils/constants';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { StampService } from './services/stamp.service';
|
||||
import { PdfViewer } from '../pdf-viewer/services/pdf-viewer.service';
|
||||
@ -55,8 +55,12 @@ import { ViewerHeaderService } from '../pdf-viewer/services/viewer-header.servic
|
||||
import { ROTATION_ACTION_BUTTONS } from '../pdf-viewer/utils/constants';
|
||||
import { SkippedService } from './services/skipped.service';
|
||||
import { REDDocumentViewer } from '../pdf-viewer/services/document-viewer.service';
|
||||
import { AnnotationsListingService } from './services/annotations-listing.service';
|
||||
import { PdfProxyService } from './services/pdf-proxy.service';
|
||||
import Annotation = Core.Annotations.Annotation;
|
||||
|
||||
const textActions = [TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE];
|
||||
|
||||
@Component({
|
||||
templateUrl: './file-preview-screen.component.html',
|
||||
styleUrls: ['./file-preview-screen.component.scss'],
|
||||
@ -67,7 +71,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
|
||||
dialogRef: MatDialogRef<unknown>;
|
||||
fullScreen = false;
|
||||
readonly canPerformAnnotationActions$: Observable<boolean>;
|
||||
readonly fileId = this.state.fileId;
|
||||
readonly dossierId = this.state.dossierId;
|
||||
readonly file$ = this.state.file$.pipe(tap(file => this._fileDataService.loadAnnotations(file)));
|
||||
@ -82,7 +85,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
readonly pdf: PdfViewer,
|
||||
readonly documentInfoService: DocumentInfoService,
|
||||
readonly state: FilePreviewStateService,
|
||||
readonly listingService: ListingService<AnnotationWrapper>,
|
||||
readonly listingService: AnnotationsListingService,
|
||||
readonly permissionsService: PermissionsService,
|
||||
readonly multiSelectService: MultiSelectService,
|
||||
readonly excludedPagesService: ExcludedPagesService,
|
||||
@ -109,11 +112,10 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
private readonly _annotationDrawService: AnnotationDrawService,
|
||||
private readonly _annotationProcessingService: AnnotationProcessingService,
|
||||
private readonly _stampService: StampService,
|
||||
readonly pdfProxyService: PdfProxyService,
|
||||
private readonly _injector: Injector,
|
||||
) {
|
||||
super();
|
||||
this.canPerformAnnotationActions$ = this._canPerformAnnotationActions$;
|
||||
|
||||
document.documentElement.addEventListener('fullscreenchange', () => {
|
||||
if (!document.fullscreenElement) {
|
||||
this.fullScreen = false;
|
||||
@ -129,15 +131,23 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
return ScrollableParentViews.ANNOTATIONS_LIST;
|
||||
}
|
||||
|
||||
private get _canPerformAnnotationActions$() {
|
||||
const viewMode$ = this._viewModeService.viewMode$.pipe(tap(() => this.#deactivateMultiSelect()));
|
||||
get #textSelected$() {
|
||||
const textSelected$ = combineLatest([
|
||||
this._documentViewer.textSelected$,
|
||||
this.pdfProxyService.canPerformAnnotationActions$,
|
||||
this.state.file$,
|
||||
]);
|
||||
|
||||
return combineLatest([this.state.file$, this.state.dossier$, viewMode$, this.pdf.compareMode$]).pipe(
|
||||
map(
|
||||
([file, dossier, viewMode]) =>
|
||||
this.permissionsService.canPerformAnnotationActions(file, dossier) && viewMode === 'STANDARD',
|
||||
),
|
||||
shareDistinctLast(),
|
||||
return textSelected$.pipe(
|
||||
tap(([selectedText, canPerformActions, file]) => {
|
||||
const isCurrentPageExcluded = file.isPageExcluded(this.pdf.currentPage);
|
||||
|
||||
if (selectedText.length > 2 && canPerformActions && !isCurrentPageExcluded) {
|
||||
this.pdf.enable(textActions);
|
||||
} else {
|
||||
this.pdf.disable(textActions);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@ -149,8 +159,8 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
async updateViewMode(): Promise<void> {
|
||||
this._logger.info(`[PDF] Update ${this._viewModeService.viewMode} view mode`);
|
||||
|
||||
const annotations = this._annotationManager.get(a => Boolean(a.getCustomData('redact-manager')));
|
||||
const redactions = annotations.filter(a => a.getCustomData('redaction'));
|
||||
const annotations = this._annotationManager.get(a => bool(a.getCustomData('redact-manager')));
|
||||
const redactions = annotations.filter(a => bool(a.getCustomData('redaction')));
|
||||
|
||||
switch (this._viewModeService.viewMode) {
|
||||
case 'STANDARD': {
|
||||
@ -158,17 +168,17 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
const wrappers = await this._fileDataService.annotations;
|
||||
const ocrAnnotationIds = wrappers.filter(a => a.isOCR).map(a => a.id);
|
||||
const standardEntries = annotations
|
||||
.filter(a => a.getCustomData('changeLogRemoved') === 'false')
|
||||
.filter(a => !bool(a.getCustomData('changeLogRemoved')))
|
||||
.filter(a => !ocrAnnotationIds.includes(a.Id));
|
||||
const nonStandardEntries = annotations.filter(a => a.getCustomData('changeLogRemoved') === 'true');
|
||||
const nonStandardEntries = annotations.filter(a => bool(a.getCustomData('changeLogRemoved')));
|
||||
this._setAnnotationsOpacity(standardEntries, true);
|
||||
this._annotationManager.show(standardEntries);
|
||||
this._annotationManager.hide(nonStandardEntries);
|
||||
break;
|
||||
}
|
||||
case 'DELTA': {
|
||||
const changeLogEntries = annotations.filter(a => a.getCustomData('changeLog') === 'true');
|
||||
const nonChangeLogEntries = annotations.filter(a => a.getCustomData('changeLog') === 'false');
|
||||
const changeLogEntries = annotations.filter(a => bool(a.getCustomData('changeLog')));
|
||||
const nonChangeLogEntries = annotations.filter(a => !bool(a.getCustomData('changeLog')));
|
||||
this._setAnnotationsColor(redactions, 'annotationColor');
|
||||
this._setAnnotationsOpacity(changeLogEntries, true);
|
||||
this._annotationManager.show(changeLogEntries);
|
||||
@ -176,7 +186,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
break;
|
||||
}
|
||||
case 'REDACTED': {
|
||||
const nonRedactionEntries = annotations.filter(a => a.getCustomData('redaction') === 'false');
|
||||
const nonRedactionEntries = annotations.filter(a => !bool(a.getCustomData('redaction')));
|
||||
this._setAnnotationsOpacity(redactions);
|
||||
this._setAnnotationsColor(redactions, 'redactionColor');
|
||||
this._annotationManager.show(redactions);
|
||||
@ -197,7 +207,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
}
|
||||
|
||||
ngOnDetach(): void {
|
||||
this._pageRotationService.clearRotations();
|
||||
this._documentViewer.close();
|
||||
super.ngOnDetach();
|
||||
this._changeDetectorRef.markForCheck();
|
||||
@ -209,7 +218,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
}
|
||||
|
||||
this._viewModeService.switchToStandard();
|
||||
this.state.reloadBlob();
|
||||
|
||||
await this.ngOnInit();
|
||||
await this._fileDataService.loadRedactionLog();
|
||||
@ -234,12 +242,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
const reanalyzeFiles = reanalysisService.reanalyzeFilesForDossier([file], this.dossierId, { force: true });
|
||||
await firstValueFrom(reanalyzeFiles);
|
||||
}
|
||||
}
|
||||
|
||||
handleAnnotationSelected(annotationIds: string[]) {
|
||||
console.log(annotationIds);
|
||||
this.listingService.setSelected(annotationIds.map(id => this._fileDataService.find(id)).filter(ann => ann !== undefined));
|
||||
this._changeDetectorRef.markForCheck();
|
||||
this.pdfProxyService.loadViewer();
|
||||
}
|
||||
|
||||
selectPage(pageNumber: number) {
|
||||
@ -355,6 +358,10 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
|
||||
loadAnnotations() {
|
||||
const documentLoaded$ = this._documentViewer.loaded$.pipe(
|
||||
tap(() => {
|
||||
this._pageRotationService.clearRotations();
|
||||
this._viewerHeaderService.disable(ROTATION_ACTION_BUTTONS);
|
||||
}),
|
||||
filter(s => s),
|
||||
tap(() => this.viewerReady()),
|
||||
);
|
||||
@ -479,12 +486,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
});
|
||||
}
|
||||
|
||||
#deactivateMultiSelect() {
|
||||
this.multiSelectService.deactivate();
|
||||
this._annotationManager.deselect();
|
||||
this.handleAnnotationSelected([]);
|
||||
}
|
||||
|
||||
private _setExcludedPageStyles() {
|
||||
const file = this._filesMapService.get(this.dossierId, this.fileId);
|
||||
setTimeout(() => {
|
||||
@ -532,6 +533,24 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
this.addActiveScreenSubscription = this._documentViewer.keyUp$.subscribe($event => {
|
||||
this.handleKeyEvent($event);
|
||||
});
|
||||
|
||||
this.addActiveScreenSubscription = this.#textSelected$.subscribe();
|
||||
|
||||
this.addActiveScreenSubscription = this.state.blob$
|
||||
.pipe(
|
||||
log('Reload blob'),
|
||||
switchMap(blob => from(this._documentViewer.lock()).pipe(map(() => blob))),
|
||||
tap(() => this._errorService.clear()),
|
||||
tap(blob => this.pdf.loadDocument(blob, this.state.file)),
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
this.addActiveScreenSubscription = this.pdfProxyService.manualAnnotationRequested$.subscribe($event => {
|
||||
this.openManualAnnotationDialog($event);
|
||||
});
|
||||
|
||||
this.addActiveScreenSubscription = this.pdfProxyService.pageChanged$.subscribe(page => this.viewerPageChanged(page));
|
||||
this.addActiveScreenSubscription = this.pdfProxyService.annotationSelected$.subscribe();
|
||||
}
|
||||
|
||||
private _handleDeletedDossier(): void {
|
||||
|
||||
@ -11,7 +11,6 @@ import { AnnotationDetailsComponent } from './components/annotation-details/anno
|
||||
import { AnnotationsListComponent } from './components/annotations-list/annotations-list.component';
|
||||
import { PageIndicatorComponent } from './components/page-indicator/page-indicator.component';
|
||||
import { PageExclusionComponent } from './components/page-exclusion/page-exclusion.component';
|
||||
import { PdfPaginatorComponent } from './components/pdf-paginator/pdf-paginator.component';
|
||||
import { AnnotationActionsComponent } from './components/annotation-actions/annotation-actions.component';
|
||||
import { CommentsComponent } from './components/comments/comments.component';
|
||||
import { DocumentInfoComponent } from './components/document-info/document-info.component';
|
||||
@ -70,7 +69,6 @@ const components = [
|
||||
AnnotationsListComponent,
|
||||
PageIndicatorComponent,
|
||||
PageExclusionComponent,
|
||||
PdfPaginatorComponent,
|
||||
AnnotationActionsComponent,
|
||||
CommentsComponent,
|
||||
DocumentInfoComponent,
|
||||
|
||||
@ -4,7 +4,7 @@ import { ManualRedactionService } from './manual-redaction.service';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { Observable } from 'rxjs';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { getFirstRelevantTextPart, translateQuads } from '../../../utils';
|
||||
import { getFirstRelevantTextPart } from '../../../utils';
|
||||
import { AnnotationPermissions } from '@models/file/annotation.permissions';
|
||||
import { BASE_HREF } from '../../../tokens';
|
||||
import { UserService } from '@services/user.service';
|
||||
@ -13,6 +13,7 @@ import {
|
||||
DictionaryEntryTypes,
|
||||
Dossier,
|
||||
IAddRedactionRequest,
|
||||
IHeaderElement,
|
||||
ILegalBasisChangeRequest,
|
||||
IRecategorizationRequest,
|
||||
IRectangle,
|
||||
@ -27,7 +28,7 @@ import {
|
||||
AcceptRecommendationDialogComponent,
|
||||
AcceptRecommendationReturnType,
|
||||
} from '../dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component';
|
||||
import { defaultDialogConfig, List, ListingService } from '@iqser/common-ui';
|
||||
import { defaultDialogConfig, List } from '@iqser/common-ui';
|
||||
import { filter } from 'rxjs/operators';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { FilePreviewStateService } from './file-preview-state.service';
|
||||
@ -38,7 +39,7 @@ import { PdfViewer } from '../../pdf-viewer/services/pdf-viewer.service';
|
||||
import { REDAnnotationManager } from '../../pdf-viewer/services/annotation-manager.service';
|
||||
import { SkippedService } from './skipped.service';
|
||||
import { REDDocumentViewer } from '../../pdf-viewer/services/document-viewer.service';
|
||||
import Quad = Core.Math.Quad;
|
||||
import { AnnotationsListingService } from './annotations-listing.service';
|
||||
|
||||
@Injectable()
|
||||
export class AnnotationActionsService {
|
||||
@ -60,7 +61,7 @@ export class AnnotationActionsService {
|
||||
private readonly _state: FilePreviewStateService,
|
||||
private readonly _fileDataService: FileDataService,
|
||||
private readonly _skippedService: SkippedService,
|
||||
private readonly _listingService: ListingService<AnnotationWrapper>,
|
||||
private readonly _listingService: AnnotationsListingService,
|
||||
) {}
|
||||
|
||||
private get _dossier(): Dossier {
|
||||
@ -197,11 +198,10 @@ export class AnnotationActionsService {
|
||||
});
|
||||
}
|
||||
|
||||
getViewerAvailableActions(): Record<string, unknown>[] {
|
||||
getViewerAvailableActions(annotations: AnnotationWrapper[]): IHeaderElement[] {
|
||||
const dossier = this._state.dossier;
|
||||
const annotations = this._listingService.selected;
|
||||
|
||||
const availableActions = [];
|
||||
const availableActions: IHeaderElement[] = [];
|
||||
const annotationPermissions = annotations.map(annotation => ({
|
||||
annotation,
|
||||
permissions: AnnotationPermissions.forUser(
|
||||
@ -549,7 +549,7 @@ export class AnnotationActionsService {
|
||||
const rect = toPosition(
|
||||
viewerAnnotation.getPageNumber(),
|
||||
this._documentViewer.getHeight(viewerAnnotation.getPageNumber()),
|
||||
this._translateQuads(viewerAnnotation.getPageNumber(), quad),
|
||||
this._pdf.translateQuad(viewerAnnotation.getPageNumber(), quad),
|
||||
);
|
||||
rectangles.push(rect);
|
||||
|
||||
@ -586,11 +586,6 @@ export class AnnotationActionsService {
|
||||
}
|
||||
}
|
||||
|
||||
private _translateQuads(page: number, quad: Quad): Quad {
|
||||
const rotation = this._pdf.documentViewer.getCompleteRotation(page);
|
||||
return translateQuads(page, rotation, quad);
|
||||
}
|
||||
|
||||
private async _extractTextFromRect(page: Core.PDFNet.Page, rect: Core.PDFNet.Rect) {
|
||||
const txt = await this._pdf.PDFNet.TextExtractor.create();
|
||||
await txt.begin(page, rect); // Read the page.
|
||||
|
||||
@ -1,56 +1,48 @@
|
||||
import { Component, EventEmitter, Inject, Input, NgZone, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
|
||||
import { Dossier, IHeaderElement, IManualRedactionEntry } from '@red/domain';
|
||||
import { Core, WebViewerInstance } from '@pdftron/webviewer';
|
||||
import { ChangeDetectorRef, Inject, Injectable, NgZone } from '@angular/core';
|
||||
import { IHeaderElement, IManualRedactionEntry } from '@red/domain';
|
||||
import { Core } from '@pdftron/webviewer';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {
|
||||
ManualRedactionEntryType,
|
||||
ManualRedactionEntryTypes,
|
||||
ManualRedactionEntryWrapper,
|
||||
} from '@models/file/manual-redaction-entry.wrapper';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { AnnotationDrawService } from '../../../pdf-viewer/services/annotation-draw.service';
|
||||
import { AnnotationActionsService } from '../../services/annotation-actions.service';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { BASE_HREF_FN, BaseHrefFn } from '../../../../tokens';
|
||||
import { AutoUnsubscribe, ErrorService, log } from '@iqser/common-ui';
|
||||
import { toPosition } from '../../utils/pdf-calculation.utils';
|
||||
import { MultiSelectService } from '../../services/multi-select.service';
|
||||
import { FilePreviewStateService } from '../../services/file-preview-state.service';
|
||||
import { map, switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
||||
import { PageRotationService } from '../../../pdf-viewer/services/page-rotation.service';
|
||||
import { HeaderElements, TextPopups } from '../../utils/constants';
|
||||
import { from } from 'rxjs';
|
||||
import { FileDataService } from '../../services/file-data.service';
|
||||
import { ViewerHeaderService } from '../../../pdf-viewer/services/viewer-header.service';
|
||||
import { ManualRedactionService } from '../../services/manual-redaction.service';
|
||||
import { PdfViewer } from '../../../pdf-viewer/services/pdf-viewer.service';
|
||||
import { REDAnnotationManager } from '../../../pdf-viewer/services/annotation-manager.service';
|
||||
import { translateQuads } from '../../../../utils';
|
||||
import {
|
||||
ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED,
|
||||
HEADER_ITEMS_TO_TOGGLE,
|
||||
ROTATION_ACTION_BUTTONS,
|
||||
TEXT_POPUPS_TO_TOGGLE,
|
||||
} from '../../../pdf-viewer/utils/constants';
|
||||
import { REDDocumentViewer } from '../../../pdf-viewer/services/document-viewer.service';
|
||||
} from '../../../models/file/manual-redaction-entry.wrapper';
|
||||
import { AnnotationWrapper } from '../../../models/file/annotation.wrapper';
|
||||
import { AnnotationDrawService } from '../../pdf-viewer/services/annotation-draw.service';
|
||||
import { AnnotationActionsService } from './annotation-actions.service';
|
||||
import { UserPreferenceService } from '../../../services/user-preference.service';
|
||||
import { BASE_HREF_FN, BaseHrefFn } from '../../../tokens';
|
||||
import { shareDistinctLast } from '@iqser/common-ui';
|
||||
import { toPosition } from '../utils/pdf-calculation.utils';
|
||||
import { MultiSelectService } from './multi-select.service';
|
||||
import { FilePreviewStateService } from './file-preview-state.service';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
import { HeaderElements, TextPopups } from '../utils/constants';
|
||||
import { FileDataService } from './file-data.service';
|
||||
import { ViewerHeaderService } from '../../pdf-viewer/services/viewer-header.service';
|
||||
import { ManualRedactionService } from './manual-redaction.service';
|
||||
import { PdfViewer } from '../../pdf-viewer/services/pdf-viewer.service';
|
||||
import { REDAnnotationManager } from '../../pdf-viewer/services/annotation-manager.service';
|
||||
import { ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED, HEADER_ITEMS_TO_TOGGLE, TEXT_POPUPS_TO_TOGGLE } from '../../pdf-viewer/utils/constants';
|
||||
import { REDDocumentViewer } from '../../pdf-viewer/services/document-viewer.service';
|
||||
import { combineLatest, Observable, Subject } from 'rxjs';
|
||||
import { ViewModeService } from './view-mode.service';
|
||||
import { PermissionsService } from '../../../services/permissions.service';
|
||||
import { AnnotationsListingService } from './annotations-listing.service';
|
||||
import Annotation = Core.Annotations.Annotation;
|
||||
import Quad = Core.Math.Quad;
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-pdf-viewer',
|
||||
templateUrl: './pdf-paginator.component.html',
|
||||
styleUrls: ['./pdf-paginator.component.scss'],
|
||||
})
|
||||
export class PdfPaginatorComponent extends AutoUnsubscribe implements OnInit, OnChanges {
|
||||
@Input() dossier: Dossier;
|
||||
@Input() canPerformActions = false;
|
||||
@Output() readonly annotationSelected = this.#annotationSelected$;
|
||||
@Output() readonly manualAnnotationRequested = new EventEmitter<ManualRedactionEntryWrapper>();
|
||||
@Output() readonly pageChanged = this.pdf.pageChanged$.pipe(tap(() => this._handleCustomActions()));
|
||||
instance: WebViewerInstance;
|
||||
private _selectedText = '';
|
||||
@Injectable()
|
||||
export class PdfProxyService {
|
||||
readonly annotationSelected$ = this.#annotationSelected$;
|
||||
readonly manualAnnotationRequested$ = new Subject<ManualRedactionEntryWrapper>();
|
||||
readonly pageChanged$ = this._pdf.pageChanged$.pipe(tap(() => this._handleCustomActions()));
|
||||
canPerformActions = true;
|
||||
|
||||
instance = this._pdf.instance;
|
||||
canPerformAnnotationActions$: Observable<boolean>;
|
||||
readonly #visibilityOffIcon = this._convertPath('/assets/icons/general/visibility-off.svg');
|
||||
readonly #visibilityIcon = this._convertPath('/assets/icons/general/visibility.svg');
|
||||
readonly #searchIcon = this._convertPath('/assets/icons/general/pdftron-action-search.svg');
|
||||
readonly #falsePositiveIcon = this._convertPath('/assets/icons/general/pdftron-action-false-positive.svg');
|
||||
readonly #addRedactionIcon = this._convertPath('/assets/icons/general/pdftron-action-add-redaction.svg');
|
||||
readonly #addDictIcon = this._convertPath('/assets/icons/general/pdftron-action-add-dict.svg');
|
||||
@ -63,45 +55,56 @@ export class PdfPaginatorComponent extends AutoUnsubscribe implements OnInit, On
|
||||
private readonly _userPreferenceService: UserPreferenceService,
|
||||
private readonly _annotationDrawService: AnnotationDrawService,
|
||||
private readonly _annotationActionsService: AnnotationActionsService,
|
||||
private readonly _pageRotationService: PageRotationService,
|
||||
private readonly _fileDataService: FileDataService,
|
||||
private readonly _viewerHeaderService: ViewerHeaderService,
|
||||
private readonly _errorService: ErrorService,
|
||||
private readonly _viewModeService: ViewModeService,
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
private readonly _documentViewer: REDDocumentViewer,
|
||||
private readonly _annotationManager: REDAnnotationManager,
|
||||
readonly pdf: PdfViewer,
|
||||
private readonly _pdf: PdfViewer,
|
||||
private readonly _state: FilePreviewStateService,
|
||||
private readonly _multiSelectService: MultiSelectService,
|
||||
private readonly _listingService: AnnotationsListingService,
|
||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
||||
) {
|
||||
super();
|
||||
this.canPerformAnnotationActions$ = this.#canPerformAnnotationActions$;
|
||||
}
|
||||
|
||||
get #canPerformAnnotationActions$() {
|
||||
const viewMode$ = this._viewModeService.viewMode$.pipe(tap(() => this.#deactivateMultiSelect()));
|
||||
|
||||
return combineLatest([this._state.file$, this._state.dossier$, viewMode$, this._pdf.compareMode$]).pipe(
|
||||
map(
|
||||
([file, dossier, viewMode]) =>
|
||||
this._permissionsService.canPerformAnnotationActions(file, dossier) && viewMode === 'STANDARD',
|
||||
),
|
||||
tap(canPerformActions => (this.canPerformActions = canPerformActions)),
|
||||
tap(() => this._handleCustomActions()),
|
||||
shareDistinctLast(),
|
||||
);
|
||||
}
|
||||
|
||||
get #annotationSelected$() {
|
||||
return this._annotationManager.annotationSelected$.pipe(map(value => this.#processSelectedAnnotations(...value)));
|
||||
return this._annotationManager.annotationSelected$.pipe(
|
||||
map(value => this.#processSelectedAnnotations(...value)),
|
||||
tap(annotations => this.handleAnnotationSelected(annotations)),
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this._loadViewer();
|
||||
|
||||
this.addActiveScreenSubscription = this._state.blob$
|
||||
.pipe(
|
||||
log('Reload blob'),
|
||||
switchMap(blob => from(this._documentViewer.lock()).pipe(map(() => blob))),
|
||||
withLatestFrom(this._state.file$),
|
||||
tap(() => this._errorService.clear()),
|
||||
tap(([blob, file]) => this.pdf.loadDocument(blob, file)),
|
||||
tap(() => {
|
||||
this._pageRotationService.clearRotations();
|
||||
this._viewerHeaderService.disable(ROTATION_ACTION_BUTTONS);
|
||||
}),
|
||||
)
|
||||
.subscribe();
|
||||
handleAnnotationSelected(annotationIds: string[]) {
|
||||
this._listingService.setSelected(annotationIds.map(id => this._fileDataService.find(id)).filter(ann => ann !== undefined));
|
||||
this._changeDetectorRef.markForCheck();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.canPerformActions && !!this.instance) {
|
||||
this._handleCustomActions();
|
||||
}
|
||||
loadViewer() {
|
||||
this._configureElements();
|
||||
this._configureTextPopup();
|
||||
}
|
||||
|
||||
#deactivateMultiSelect() {
|
||||
this._multiSelectService.deactivate();
|
||||
this._annotationManager.deselect();
|
||||
this.handleAnnotationSelected([]);
|
||||
}
|
||||
|
||||
#processSelectedAnnotations(annotations: Annotation[], action) {
|
||||
@ -110,7 +113,7 @@ export class PdfPaginatorComponent extends AutoUnsubscribe implements OnInit, On
|
||||
if (action === 'deselected') {
|
||||
// Remove deselected annotations from selected list
|
||||
nextAnnotations = this._annotationManager.selected.filter(ann => !annotations.some(a => a.Id === ann.Id));
|
||||
this.pdf.disable(TextPopups.ADD_RECTANGLE);
|
||||
this._pdf.disable(TextPopups.ADD_RECTANGLE);
|
||||
return nextAnnotations.map(ann => ann.Id);
|
||||
} else if (!this._multiSelectService.isEnabled) {
|
||||
// Only choose the last selected annotation, to bypass viewer multi select
|
||||
@ -123,46 +126,18 @@ export class PdfPaginatorComponent extends AutoUnsubscribe implements OnInit, On
|
||||
}
|
||||
|
||||
this.#configureAnnotationSpecificActions(annotations);
|
||||
this._toggleRectangleAnnotationAction(annotations.length === 1 && annotations[0].ReadOnly);
|
||||
|
||||
if (!(annotations.length === 1 && annotations[0].ReadOnly)) {
|
||||
this._pdf.enable(TextPopups.ADD_RECTANGLE);
|
||||
} else {
|
||||
this._pdf.disable(TextPopups.ADD_RECTANGLE);
|
||||
}
|
||||
|
||||
return nextAnnotations.map(ann => ann.Id);
|
||||
}
|
||||
|
||||
private _loadViewer() {
|
||||
this.instance = this.pdf.instance;
|
||||
|
||||
this._configureElements();
|
||||
this._configureTextPopup();
|
||||
|
||||
this.pdf.documentViewer.addEventListener('textSelected', (_, selectedText, pageNumber: number) => {
|
||||
this._selectedText = selectedText;
|
||||
|
||||
if (this.pdf.isCompare && pageNumber % 2 === 0) {
|
||||
this.pdf.disable('textPopup');
|
||||
} else {
|
||||
this.pdf.enable('textPopup');
|
||||
}
|
||||
|
||||
const isCurrentPageExcluded = this._state.file.isPageExcluded(this.pdf.currentPage);
|
||||
const textActions = [TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE];
|
||||
|
||||
if (selectedText.length > 2 && this.canPerformActions && !isCurrentPageExcluded) {
|
||||
this.pdf.enable(textActions);
|
||||
} else {
|
||||
this.pdf.disable(textActions);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _toggleRectangleAnnotationAction(disable = false) {
|
||||
if (!disable) {
|
||||
this.pdf.enable(TextPopups.ADD_RECTANGLE);
|
||||
} else {
|
||||
this.pdf.disable(TextPopups.ADD_RECTANGLE);
|
||||
}
|
||||
}
|
||||
|
||||
private _configureElements() {
|
||||
const dossierTemplateId = this.dossier.dossierTemplateId;
|
||||
const dossierTemplateId = this._state.dossierTemplateId;
|
||||
const color = this._annotationDrawService.getAndConvertColor(dossierTemplateId, dossierTemplateId, 'manual');
|
||||
this._documentViewer.setRectangleToolStyles(color);
|
||||
}
|
||||
@ -209,12 +184,12 @@ export class PdfPaginatorComponent extends AutoUnsubscribe implements OnInit, On
|
||||
]);
|
||||
}
|
||||
|
||||
const actions = this._annotationActionsService.getViewerAvailableActions();
|
||||
const actions = this._annotationActionsService.getViewerAvailableActions(annotationWrappers);
|
||||
this.instance.UI.annotationPopup.add(actions);
|
||||
}
|
||||
|
||||
private _configureRectangleAnnotationPopup(annotation: Annotation) {
|
||||
if (!this.pdf.isCompare || annotation.getPageNumber() % 2 === 1) {
|
||||
if (!this._pdf.isCompare || annotation.getPageNumber() % 2 === 1) {
|
||||
this.instance.UI.annotationPopup.add([
|
||||
{
|
||||
type: 'actionButton',
|
||||
@ -234,7 +209,7 @@ export class PdfPaginatorComponent extends AutoUnsubscribe implements OnInit, On
|
||||
const manualRedactionEntry = this._getManualRedaction({ [activePage]: quads });
|
||||
this._cleanUpSelectionAndButtonState();
|
||||
|
||||
this.manualAnnotationRequested.emit({ manualRedactionEntry, type: 'REDACTION' });
|
||||
this.manualAnnotationRequested$.next({ manualRedactionEntry, type: 'REDACTION' });
|
||||
}
|
||||
|
||||
private _cleanUpSelectionAndButtonState() {
|
||||
@ -243,25 +218,7 @@ export class PdfPaginatorComponent extends AutoUnsubscribe implements OnInit, On
|
||||
}
|
||||
|
||||
private _configureTextPopup() {
|
||||
const searchButton = {
|
||||
type: 'actionButton',
|
||||
img: this.#searchIcon,
|
||||
title: this._translateService.instant('pdf-viewer.text-popup.actions.search'),
|
||||
onClick: () => {
|
||||
const text = this.pdf.documentViewer.getSelectedText();
|
||||
const searchOptions = {
|
||||
caseSensitive: true, // match case
|
||||
wholeWord: true, // match whole words only
|
||||
wildcard: false, // allow using '*' as a wildcard value
|
||||
regex: false, // string is treated as a regular expression
|
||||
searchUp: false, // search from the end of the document upwards
|
||||
ambientString: true, // return ambient string as part of the result
|
||||
};
|
||||
this.instance.UI.openElements(['searchPanel']);
|
||||
setTimeout(() => this.instance.UI.searchTextFull(text, searchOptions), 250);
|
||||
},
|
||||
};
|
||||
const popups: IHeaderElement[] = [searchButton];
|
||||
const popups: IHeaderElement[] = [];
|
||||
|
||||
// Adding directly to the false-positive dict is only available in dev-mode
|
||||
if (this._userPreferenceService.areDevFeaturesEnabled) {
|
||||
@ -288,38 +245,38 @@ export class PdfPaginatorComponent extends AutoUnsubscribe implements OnInit, On
|
||||
title: this.#getTitle(ManualRedactionEntryTypes.DICTIONARY),
|
||||
onClick: () => this._addManualRedactionOfType(ManualRedactionEntryTypes.DICTIONARY),
|
||||
});
|
||||
console.log(popups);
|
||||
|
||||
this.instance.UI.textPopup.add(popups);
|
||||
console.log('configure popup');
|
||||
this._pdf.configureTextPopups(popups);
|
||||
|
||||
return this._handleCustomActions();
|
||||
}
|
||||
|
||||
#getTitle(type: ManualRedactionEntryType) {
|
||||
return this._translateService.instant(this._manualRedactionService.getTitle(type, this.dossier));
|
||||
return this._translateService.instant(this._manualRedactionService.getTitle(type, this._state.dossier));
|
||||
}
|
||||
|
||||
private _addManualRedactionOfType(type: ManualRedactionEntryType) {
|
||||
const selectedQuads: Readonly<Record<string, Core.Math.Quad[]>> = this.pdf.documentViewer.getSelectedTextQuads();
|
||||
const text = this.pdf.documentViewer.getSelectedText();
|
||||
const selectedQuads: Record<string, Quad[]> = this._pdf.documentViewer.getSelectedTextQuads();
|
||||
const text = this._pdf.documentViewer.getSelectedText();
|
||||
const manualRedactionEntry = this._getManualRedaction(selectedQuads, text, true);
|
||||
this.manualAnnotationRequested.emit({ manualRedactionEntry, type });
|
||||
this.manualAnnotationRequested$.next({ manualRedactionEntry, type });
|
||||
}
|
||||
|
||||
private _handleCustomActions() {
|
||||
const isCurrentPageExcluded = this._state.file.isPageExcluded(this.pdf.currentPage);
|
||||
const isCurrentPageExcluded = this._state.file.isPageExcluded(this._pdf.currentPage);
|
||||
|
||||
if (this.canPerformActions && !isCurrentPageExcluded) {
|
||||
try {
|
||||
this.pdf.instance.UI.enableTools(['AnnotationCreateRectangle']);
|
||||
this._pdf.instance.UI.enableTools(['AnnotationCreateRectangle']);
|
||||
} catch (e) {
|
||||
// happens
|
||||
}
|
||||
this.pdf.enable(TEXT_POPUPS_TO_TOGGLE);
|
||||
this._pdf.enable(TEXT_POPUPS_TO_TOGGLE);
|
||||
this._viewerHeaderService.enable(HEADER_ITEMS_TO_TOGGLE);
|
||||
|
||||
if (this._selectedText.length > 2) {
|
||||
this.pdf.enable([TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE]);
|
||||
if (this._documentViewer.selectedText.length > 2) {
|
||||
this._pdf.enable([TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE]);
|
||||
}
|
||||
|
||||
return;
|
||||
@ -334,25 +291,21 @@ export class PdfPaginatorComponent extends AutoUnsubscribe implements OnInit, On
|
||||
);
|
||||
headerElementsToDisable = headerElementsToDisable.filter(element => !ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED.includes(element));
|
||||
} else {
|
||||
this.pdf.instance.UI.disableTools(['AnnotationCreateRectangle']);
|
||||
this._pdf.instance.UI.disableTools(['AnnotationCreateRectangle']);
|
||||
}
|
||||
|
||||
this.pdf.disable(textPopupElementsToDisable);
|
||||
this._pdf.disable(textPopupElementsToDisable);
|
||||
this._viewerHeaderService.disable(headerElementsToDisable);
|
||||
}
|
||||
|
||||
private _getManualRedaction(
|
||||
quads: Readonly<Record<string, Core.Math.Quad[]>>,
|
||||
text?: string,
|
||||
convertQuads = false,
|
||||
): IManualRedactionEntry {
|
||||
private _getManualRedaction(quads: Record<string, Quad[]>, text?: string, convertQuads = false): IManualRedactionEntry {
|
||||
const entry: IManualRedactionEntry = { positions: [] };
|
||||
|
||||
for (const key of Object.keys(quads)) {
|
||||
for (const quad of quads[key]) {
|
||||
const page = parseInt(key, 10);
|
||||
const pageHeight = this.pdf.documentViewer.getPageHeight(page);
|
||||
entry.positions.push(toPosition(page, pageHeight, convertQuads ? this.#translateQuad(page, quad) : quad));
|
||||
const pageHeight = this._documentViewer.getHeight(page);
|
||||
entry.positions.push(toPosition(page, pageHeight, convertQuads ? this._pdf.translateQuad(page, quad) : quad));
|
||||
}
|
||||
}
|
||||
|
||||
@ -360,9 +313,4 @@ export class PdfPaginatorComponent extends AutoUnsubscribe implements OnInit, On
|
||||
entry.rectangle = !text;
|
||||
return entry;
|
||||
}
|
||||
|
||||
#translateQuad(page: number, quad: Core.Math.Quad) {
|
||||
const rotation = this.pdf.documentViewer.getCompleteRotation(page);
|
||||
return translateQuads(page, rotation, quad);
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { skip, tap } from 'rxjs/operators';
|
||||
import { shareDistinctLast } from '@iqser/common-ui';
|
||||
import { bool, shareDistinctLast } from '@iqser/common-ui';
|
||||
import { REDAnnotationManager } from '../../pdf-viewer/services/annotation-manager.service';
|
||||
|
||||
@Injectable()
|
||||
@ -28,7 +28,7 @@ export class SkippedService {
|
||||
}
|
||||
|
||||
private _handleIgnoreAnnotationsDrawing(hideSkipped: boolean): void {
|
||||
const ignored = this._annotationManager.get(a => Boolean(a.getCustomData('skipped')));
|
||||
const ignored = this._annotationManager.get(a => bool(a.getCustomData('skipped')));
|
||||
if (hideSkipped) {
|
||||
this._annotationManager.hide(ignored);
|
||||
} else {
|
||||
|
||||
@ -1,18 +1,16 @@
|
||||
import { IRectangle } from '@red/domain';
|
||||
import { Core } from '@pdftron/webviewer';
|
||||
import Quad = Core.Math.Quad;
|
||||
|
||||
export const toPosition = (
|
||||
page: number,
|
||||
pageHeight: number,
|
||||
selectedQuad: { x1: number; x2: number; x3: number; x4: number; y4: number; y2: number },
|
||||
): IRectangle => {
|
||||
const height = selectedQuad.y2 - selectedQuad.y4;
|
||||
export const toPosition = (page: number, pageHeight: number, { x1, x2, x3, x4, y2, y4 }: Quad): IRectangle => {
|
||||
const height = y2 - y4;
|
||||
return {
|
||||
page: page,
|
||||
page,
|
||||
topLeft: {
|
||||
x: Math.min(selectedQuad.x3, selectedQuad.x4, selectedQuad.x2, selectedQuad.x1),
|
||||
y: pageHeight - (selectedQuad.y4 + height),
|
||||
x: Math.min(x3, x4, x2, x1),
|
||||
y: pageHeight - (y4 + height),
|
||||
},
|
||||
height: height,
|
||||
width: Math.max(4, Math.abs(selectedQuad.x3 - selectedQuad.x4), Math.abs(selectedQuad.x3 - selectedQuad.x1)),
|
||||
height,
|
||||
width: Math.max(4, Math.abs(x3 - x4), Math.abs(x3 - x1)),
|
||||
};
|
||||
};
|
||||
|
||||
@ -47,9 +47,7 @@ export class REDAnnotationManager {
|
||||
|
||||
get(annotation: AnnotationWrapper | string): Annotation;
|
||||
get(annotations: List | List<AnnotationWrapper>): Annotation[];
|
||||
|
||||
get(predicate?: (value: Annotation) => boolean): Annotation[];
|
||||
|
||||
get(argument?: AnnotationPredicate | List<AnnotationWrapper> | List | AnnotationWrapper | string): Annotation | Annotation[] {
|
||||
if (isStringOrWrapper(argument)) {
|
||||
return this.#getById(argument);
|
||||
|
||||
@ -10,12 +10,16 @@ import { log, shareLast } from '@iqser/common-ui';
|
||||
import { stopAndPrevent, stopAndPreventIfNotAllowed } from '../utils/functions';
|
||||
import DocumentViewer = Core.DocumentViewer;
|
||||
import Color = Core.Annotations.Color;
|
||||
import Quad = Core.Math.Quad;
|
||||
|
||||
@Injectable()
|
||||
export class REDDocumentViewer {
|
||||
loaded$: Observable<boolean>;
|
||||
pageComplete$: Observable<boolean>;
|
||||
keyUp$: Observable<KeyboardEvent>;
|
||||
textSelected$: Observable<string>;
|
||||
|
||||
selectedText = '';
|
||||
|
||||
#document: DocumentViewer;
|
||||
|
||||
@ -67,6 +71,24 @@ export class REDDocumentViewer {
|
||||
return fromEvent(this.#document, 'pageComplete').pipe(debounceTime(300));
|
||||
}
|
||||
|
||||
get #textSelected$(): Observable<string> {
|
||||
return fromEvent<[Quad, string, number]>(this.#document, 'textSelected').pipe(
|
||||
tap(([, selectedText]) => (this.selectedText = selectedText)),
|
||||
tap(([, , pageNumber]) => {
|
||||
if (this._pdf.isCompare && pageNumber % 2 === 0) {
|
||||
this._pdf.disable('textPopup');
|
||||
} else {
|
||||
this._pdf.enable('textPopup');
|
||||
}
|
||||
}),
|
||||
map(([, selectedText]) => selectedText),
|
||||
);
|
||||
}
|
||||
|
||||
get #loaded$() {
|
||||
return merge(this.#documentUnloaded$, this.#documentLoaded$).pipe(shareLast());
|
||||
}
|
||||
|
||||
close() {
|
||||
this._logger.info('[PDF] Closing document');
|
||||
this.#document.closeDocument();
|
||||
@ -80,9 +102,10 @@ export class REDDocumentViewer {
|
||||
|
||||
init(document: DocumentViewer) {
|
||||
this.#document = document;
|
||||
this.loaded$ = merge(this.#documentUnloaded$, this.#documentLoaded$).pipe(shareLast());
|
||||
this.loaded$ = this.#loaded$;
|
||||
this.pageComplete$ = this.#pageComplete$.pipe(shareLast());
|
||||
this.keyUp$ = this.#keyUp$;
|
||||
this.textSelected$ = this.#textSelected$;
|
||||
}
|
||||
|
||||
async lock() {
|
||||
|
||||
@ -1,23 +1,25 @@
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { Inject, Injectable, Injector } from '@angular/core';
|
||||
import WebViewer, { Core, WebViewerInstance, WebViewerOptions } from '@pdftron/webviewer';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { BASE_HREF_FN } from '../../../tokens';
|
||||
import { File } from '@red/domain';
|
||||
import { BASE_HREF_FN, BaseHrefFn } from '../../../tokens';
|
||||
import { File, IHeaderElement } from '@red/domain';
|
||||
import { ErrorService, shareDistinctLast, shareLast } from '@iqser/common-ui';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { distinctUntilChanged, map, startWith, tap } from 'rxjs/operators';
|
||||
import { BehaviorSubject, combineLatest, fromEvent, Observable } from 'rxjs';
|
||||
import { ConfigService } from '../../../services/config.service';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { DISABLED_HOTKEYS, DOCUMENT_LOADING_ERROR, USELESS_ELEMENTS } from '../utils/constants';
|
||||
import { DISABLED_HOTKEYS, DOCUMENT_LOADING_ERROR, SEARCH_OPTIONS, USELESS_ELEMENTS } from '../utils/constants';
|
||||
import { Rgb } from '../utils/types';
|
||||
import { asList } from '../utils/functions';
|
||||
import { REDAnnotationManager } from './annotation-manager.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import AnnotationManager = Core.AnnotationManager;
|
||||
import TextTool = Core.Tools.TextTool;
|
||||
import Annotation = Core.Annotations.Annotation;
|
||||
import TextHighlightAnnotation = Core.Annotations.TextHighlightAnnotation;
|
||||
import DocumentViewer = Core.DocumentViewer;
|
||||
import Quad = Core.Math.Quad;
|
||||
|
||||
@Injectable()
|
||||
export class PdfViewer {
|
||||
@ -42,12 +44,23 @@ export class PdfViewer {
|
||||
#instance: WebViewerInstance;
|
||||
#currentBlob: Blob;
|
||||
readonly #compareMode$ = new BehaviorSubject(false);
|
||||
readonly #searchButton: IHeaderElement = {
|
||||
type: 'actionButton',
|
||||
img: this._convertPath('/assets/icons/general/pdftron-action-search.svg'),
|
||||
title: this._translateService.instant('pdf-viewer.text-popup.actions.search'),
|
||||
onClick: () => {
|
||||
this.#instance.UI.openElements(['searchPanel']);
|
||||
setTimeout(() => this.#searchForSelectedText(), 250);
|
||||
},
|
||||
};
|
||||
|
||||
constructor(
|
||||
private readonly _logger: NGXLogger,
|
||||
private readonly _activatedRoute: ActivatedRoute,
|
||||
private readonly _annotationManager: REDAnnotationManager,
|
||||
private readonly _injector: Injector,
|
||||
private readonly _activatedRoute: ActivatedRoute,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _annotationManager: REDAnnotationManager,
|
||||
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
|
||||
) {}
|
||||
|
||||
get instance() {
|
||||
@ -178,6 +191,21 @@ export class PdfViewer {
|
||||
return new this.#instance.Core.Math.Quad(x1, y1, x2, y2, x3, y3, x4, y4);
|
||||
}
|
||||
|
||||
translateQuad(page: number, quad: Quad): Quad {
|
||||
const rotationsEnum = this.#instance.Core.PageRotation;
|
||||
switch (this.documentViewer.getCompleteRotation(page)) {
|
||||
case rotationsEnum.E_90:
|
||||
return this.quad(quad.x2, quad.y2, quad.x3, quad.y3, quad.x4, quad.y4, quad.x1, quad.y1);
|
||||
case rotationsEnum.E_180:
|
||||
return this.quad(quad.x3, quad.y3, quad.x4, quad.y4, quad.x1, quad.y1, quad.x2, quad.y2);
|
||||
case rotationsEnum.E_270:
|
||||
return this.quad(quad.x4, quad.y4, quad.x1, quad.y1, quad.x2, quad.y2, quad.x3, quad.y3);
|
||||
case rotationsEnum.E_0:
|
||||
default:
|
||||
return quad;
|
||||
}
|
||||
}
|
||||
|
||||
color(rgb: Rgb) {
|
||||
return new this.#instance.Core.Annotations.Color(rgb.r, rgb.g, rgb.b);
|
||||
}
|
||||
@ -194,6 +222,15 @@ export class PdfViewer {
|
||||
return annotation instanceof this.#instance.Core.Annotations.TextHighlightAnnotation;
|
||||
}
|
||||
|
||||
configureTextPopups(popups: IHeaderElement[]) {
|
||||
this.#instance.UI.textPopup.update([]);
|
||||
this.#instance.UI.textPopup.add([...popups, this.#searchButton]);
|
||||
}
|
||||
|
||||
#searchForSelectedText() {
|
||||
this.#instance.UI.searchTextFull(this.documentViewer.getSelectedText(), SEARCH_OPTIONS);
|
||||
}
|
||||
|
||||
#clearSearchResultsWhenVisibilityChanged() {
|
||||
const iframeWindow = this.#instance.UI.iframeWindow;
|
||||
iframeWindow.addEventListener('visibilityChanged', (event: any) => {
|
||||
@ -232,12 +269,11 @@ export class PdfViewer {
|
||||
}
|
||||
|
||||
#getInstance(htmlElement: HTMLElement) {
|
||||
const convertPath = this._injector.get(BASE_HREF_FN);
|
||||
const options: WebViewerOptions = {
|
||||
licenseKey: environment.licenseKey ? window.atob(environment.licenseKey) : null,
|
||||
fullAPI: true,
|
||||
path: convertPath('/assets/wv-resources'),
|
||||
css: convertPath('/assets/pdftron/stylesheet.css'),
|
||||
path: this._convertPath('/assets/wv-resources'),
|
||||
css: this._convertPath('/assets/pdftron/stylesheet.css'),
|
||||
backendType: 'ems',
|
||||
};
|
||||
|
||||
|
||||
@ -22,6 +22,15 @@ export const ALLOWED_KEYBOARD_SHORTCUTS: List = ['+', '-', 'p', 'r', 'Escape'] a
|
||||
|
||||
export const DOCUMENT_LOADING_ERROR = new CustomError(_('error.file-preview.label'), _('error.file-preview.action'), 'iqser:refresh');
|
||||
|
||||
export const SEARCH_OPTIONS = {
|
||||
caseSensitive: true, // match case
|
||||
wholeWord: true, // match whole words only
|
||||
wildcard: false, // allow using '*' as a wildcard value
|
||||
regex: false, // string is treated as a regular expression
|
||||
searchUp: false, // search from the end of the document upwards
|
||||
ambientString: true, // return ambient string as part of the result
|
||||
};
|
||||
|
||||
export const USELESS_ELEMENTS = [
|
||||
'pageNavOverlay',
|
||||
'menuButton',
|
||||
|
||||
@ -11,5 +11,4 @@ export * from './functions';
|
||||
export * from './global-error-handler.service';
|
||||
export * from './missing-translations-handler';
|
||||
export * from './page-stamper';
|
||||
export * from './pdf-coordinates';
|
||||
export * from './pruning-translation-loader';
|
||||
|
||||
@ -1,55 +0,0 @@
|
||||
import { Core } from '@pdftron/webviewer';
|
||||
import Quad = Core.Math.Quad;
|
||||
|
||||
enum PageRotation {
|
||||
E_0 = 0,
|
||||
E_90 = 1,
|
||||
E_180 = 2,
|
||||
E_270 = 3,
|
||||
}
|
||||
|
||||
export function translateQuads(page: number, rotation: number, quad: Quad): Quad {
|
||||
let result;
|
||||
switch (rotation) {
|
||||
case PageRotation.E_90:
|
||||
result = {
|
||||
x1: quad.x2,
|
||||
x2: quad.x3,
|
||||
x3: quad.x4,
|
||||
x4: quad.x1,
|
||||
y1: quad.y2,
|
||||
y2: quad.y3,
|
||||
y3: quad.y4,
|
||||
y4: quad.y1,
|
||||
};
|
||||
break;
|
||||
case PageRotation.E_180:
|
||||
result = {
|
||||
x1: quad.x3,
|
||||
x2: quad.x4,
|
||||
x3: quad.x1,
|
||||
x4: quad.x2,
|
||||
y1: quad.y3,
|
||||
y2: quad.y4,
|
||||
y3: quad.y1,
|
||||
y4: quad.y2,
|
||||
};
|
||||
break;
|
||||
case PageRotation.E_270:
|
||||
result = {
|
||||
x1: quad.x4,
|
||||
x2: quad.x1,
|
||||
x3: quad.x2,
|
||||
x4: quad.x3,
|
||||
y1: quad.y4,
|
||||
y2: quad.y1,
|
||||
y3: quad.y2,
|
||||
y4: quad.y3,
|
||||
};
|
||||
break;
|
||||
case PageRotation.E_0:
|
||||
default:
|
||||
result = quad;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user