better fix for RED-2484

This commit is contained in:
Dan Percic 2021-12-15 23:30:45 +02:00
parent ce13cda5a6
commit 947962f667
9 changed files with 145 additions and 159 deletions

View File

@ -19,6 +19,7 @@
iqserHelpMode="bulk-select-annotations"
translate="file-preview.tabs.annotations.select"
></div>
<iqser-popup-filter
[actionsTemplate]="annotationFilterActionTemplate"
[primaryFiltersSlug]="'primaryFilters'"
@ -101,7 +102,7 @@
[active]="pageNumber === activeViewerPage"
[file]="file"
[number]="pageNumber"
[showDottedIcon]="hasOnlyManualRedactionsAndNotExcluded(pageNumber)"
[showDottedIcon]="hasOnlyManualRedactionsAndIsExcluded(pageNumber)"
[viewedPages]="viewedPages"
></redaction-page-indicator>
</div>
@ -118,99 +119,103 @@
</div>
<div style="overflow: hidden; width: 100%">
<ng-container *ngIf="excludedPagesService.hidden$ | async">
<div [attr.anotation-page-header]="activeViewerPage" [class.padding-left-0]="currentPageIsExcluded" class="page-separator">
<span *ngIf="!!activeViewerPage" class="flex-align-items-center">
<iqser-circle-button
(action)="excludedPagesService.toggle()"
*ngIf="currentPageIsExcluded"
[tooltip]="'file-preview.excluded-from-redaction' | translate | capitalize"
icon="red:exclude-pages"
tooltipPosition="above"
></iqser-circle-button>
<div
[attr.anotation-page-header]="activeViewerPage"
[class.padding-left-0]="currentPageIsExcluded"
[hidden]="excludedPagesService.shown$ | async"
class="page-separator"
>
<span *ngIf="!!activeViewerPage" class="flex-align-items-center">
<iqser-circle-button
(action)="excludedPagesService.toggle()"
*ngIf="currentPageIsExcluded"
[tooltip]="'file-preview.excluded-from-redaction' | translate | capitalize"
icon="red:exclude-pages"
tooltipPosition="above"
></iqser-circle-button>
<span class="all-caps-label">
<span translate="page"></span> {{ activeViewerPage }} -
{{ activeAnnotations?.length || 0 }}
<span [translate]="activeAnnotations?.length === 1 ? 'annotation' : 'annotations'"></span>
</span>
<span class="all-caps-label">
<span translate="page"></span> {{ activeViewerPage }} -
{{ activeAnnotations?.length || 0 }}
<span [translate]="activeAnnotations?.length === 1 ? 'annotation' : 'annotations'"></span>
</span>
</span>
<div *ngIf="multiSelectActive$ | async">
<div
(click)="selectAllOnActivePage()"
class="all-caps-label primary pointer"
translate="file-preview.tabs.annotations.select-all"
></div>
<div
(click)="deselectAllOnActivePage()"
class="all-caps-label primary pointer"
translate="file-preview.tabs.annotations.select-none"
></div>
<div *ngIf="multiSelectActive$ | async">
<div
(click)="selectAllOnActivePage()"
class="all-caps-label primary pointer"
translate="file-preview.tabs.annotations.select-all"
></div>
<div
(click)="deselectAllOnActivePage()"
class="all-caps-label primary pointer"
translate="file-preview.tabs.annotations.select-none"
></div>
</div>
</div>
<div
#annotationsElement
(keydown)="preventKeyDefault($event)"
(keyup)="preventKeyDefault($event)"
[class.active-panel]="!pagesPanelActive"
[hidden]="excludedPagesService.shown$ | async"
class="annotations"
iqserHasScrollbar
tabindex="1"
>
<ng-container *ngIf="activeViewerPage && !displayedAnnotations.get(activeViewerPage)?.length">
<iqser-empty-state
[horizontalPadding]="24"
[text]="'file-preview.no-data.title' | translate"
[verticalPadding]="40"
icon="iqser:document"
>
<ng-container *ngIf="currentPageIsExcluded">
{{ 'file-preview.tabs.annotations.page-is' | translate }}
<a
(click)="excludedPagesService.toggle()"
class="with-underline"
translate="file-preview.excluded-from-redaction"
></a
>.
</ng-container>
</iqser-empty-state>
<div *ngIf="displayedPages.length" class="no-annotations-buttons-container mt-32">
<iqser-icon-button
(action)="jumpToPreviousWithAnnotations()"
[disabled]="activeViewerPage <= displayedPages[0]"
[label]="'file-preview.tabs.annotations.jump-to-previous' | translate"
[type]="iconButtonTypes.dark"
icon="red:nav-prev"
></iqser-icon-button>
<iqser-icon-button
(action)="jumpToNextWithAnnotations()"
[disabled]="activeViewerPage >= displayedPages[displayedPages.length - 1]"
[label]="'file-preview.tabs.annotations.jump-to-next' | translate"
[type]="iconButtonTypes.dark"
class="mt-8"
icon="red:nav-next"
></iqser-icon-button>
</div>
</div>
</ng-container>
<div
#annotationsElement
(keydown)="preventKeyDefault($event)"
(keyup)="preventKeyDefault($event)"
[class.active-panel]="!pagesPanelActive"
class="annotations"
iqserHasScrollbar
tabindex="1"
>
<ng-container *ngIf="activeViewerPage && !displayedAnnotations.get(activeViewerPage)?.length">
<iqser-empty-state
[horizontalPadding]="24"
[text]="'file-preview.no-data.title' | translate"
[verticalPadding]="40"
icon="iqser:document"
>
<ng-container *ngIf="currentPageIsExcluded">
{{ 'file-preview.tabs.annotations.page-is' | translate }}
<a
(click)="excludedPagesService.toggle()"
class="with-underline"
translate="file-preview.excluded-from-redaction"
></a
>.
</ng-container>
</iqser-empty-state>
<div *ngIf="displayedPages.length" class="no-annotations-buttons-container mt-32">
<iqser-icon-button
(action)="jumpToPreviousWithAnnotations()"
[disabled]="activeViewerPage <= displayedPages[0]"
[label]="'file-preview.tabs.annotations.jump-to-previous' | translate"
[type]="iconButtonTypes.dark"
icon="red:nav-prev"
></iqser-icon-button>
<iqser-icon-button
(action)="jumpToNextWithAnnotations()"
[disabled]="activeViewerPage >= displayedPages[displayedPages.length - 1]"
[label]="'file-preview.tabs.annotations.jump-to-next' | translate"
[type]="iconButtonTypes.dark"
class="mt-8"
icon="red:nav-next"
></iqser-icon-button>
</div>
</ng-container>
<redaction-annotations-list
(deselectAnnotations)="deselectAnnotations.emit($event)"
(pagesPanelActive)="pagesPanelActive = $event"
(selectAnnotations)="selectAnnotations.emit($event)"
[activeViewerPage]="activeViewerPage"
[annotationActionsTemplate]="annotationActionsTemplate"
[annotations]="(displayedAnnotations$ | async)?.get(activeViewerPage)"
[canMultiSelect]="!isReadOnly"
[file]="file"
[selectedAnnotations]="selectedAnnotations"
iqserHelpMode="workload-annotations-list"
></redaction-annotations-list>
</div>
</ng-container>
<redaction-annotations-list
(deselectAnnotations)="deselectAnnotations.emit($event)"
(pagesPanelActive)="pagesPanelActive = $event"
(selectAnnotations)="selectAnnotations.emit($event)"
[activeViewerPage]="activeViewerPage"
[annotationActionsTemplate]="annotationActionsTemplate"
[annotations]="(displayedAnnotations$ | async)?.get(activeViewerPage)"
[canMultiSelect]="!isReadOnly"
[file]="file"
[selectedAnnotations]="selectedAnnotations"
iqserHelpMode="workload-annotations-list"
></redaction-annotations-list>
</div>
<redaction-page-exclusion *ngIf="showExcludedPages$ | async" [file]="file"></redaction-page-exclusion>
</div>

View File

@ -151,3 +151,7 @@
::ng-deep .page-separator iqser-circle-button mat-icon {
color: var(--iqser-primary);
}
[hidden] {
display: none !important;
}

View File

@ -21,7 +21,6 @@ import {
IconButtonTypes,
INestedFilter,
IqserEventTarget,
Required,
shareDistinctLast,
shareLast,
} from '@iqser/common-ui';
@ -38,7 +37,7 @@ const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'E
const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
@Component({
selector: 'redaction-file-workload',
selector: 'redaction-file-workload [file]',
templateUrl: './file-workload.component.html',
styleUrls: ['./file-workload.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
@ -53,7 +52,7 @@ export class FileWorkloadComponent {
@Input() shouldDeselectAnnotationsOnPageChange: boolean;
@Input() dialogRef: MatDialogRef<unknown>;
@Input() viewedPages: IViewedPage[];
@Input() @Required() file!: File;
@Input() file!: File;
@Input() hideSkipped: boolean;
@Input() annotationActionsTemplate: TemplateRef<unknown>;
@Input() viewer: WebViewerInstance;
@ -161,7 +160,7 @@ export class FileWorkloadComponent {
}
}
hasOnlyManualRedactionsAndNotExcluded(pageNumber: number): boolean {
hasOnlyManualRedactionsAndIsExcluded(pageNumber: number): boolean {
const hasOnlyManualRedactions = this.displayedAnnotations.get(pageNumber).every(annotation => annotation.manual);
return hasOnlyManualRedactions && this.file.excludedPages.includes(pageNumber);
}

View File

@ -67,7 +67,6 @@ export class PageExclusionComponent implements OnChanges {
)
.toPromise();
this._inputComponent.reset();
this.excludedPagesService.toggle();
} catch (e) {
this._toaster.error(_('file-preview.tabs.exclude-pages.error'));
}
@ -86,7 +85,6 @@ export class PageExclusionComponent implements OnChanges {
)
.toPromise();
this._inputComponent.reset();
this.excludedPagesService.toggle();
this._loadingService.stop();
}
}

View File

@ -14,12 +14,8 @@ import { FilesMapService } from '@services/entity-services/files-map.service';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy, OnChanges {
@Input()
file: File;
@Input()
active = false;
@Input() file: File;
@Input() active = false;
@Input() showDottedIcon = false;
@Input() number: number;
@Input() viewedPages: IViewedPage[];
@ -84,23 +80,6 @@ export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy
}
}
// @HostListener('window:keydown', ['$event'])
// handleKeyDown(event: KeyboardEvent) {
// if (this.canMarkPagesAsViewed) {
// if (this.active && event.key === ' ') {
// if (
// document.activeElement &&
// document.activeElement.className.indexOf('activePanel') >= 0
// ) {
// this.toggleReadState();
// event.stopPropagation();
// event.preventDefault();
// return false;
// }
// }
// }
// }
private async _markPageRead() {
await this._viewedPagesService.addPage({ page: this.number }, this.file.dossierId, this.file.fileId).toPromise();
if (this.activePage) {

View File

@ -284,7 +284,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
this._selectedText = selectedText;
const textActions = [dataElements.ADD_DICTIONARY, dataElements.ADD_FALSE_POSITIVE];
if (selectedText.length > 2 && this.canPerformActions && !this.utils.isCurrentPageExcluded) {
if (selectedText.length > 2 && this.canPerformActions && !this.utils.isCurrentPageExcluded(this.file)) {
this.instance.UI.enableElements(textActions);
} else {
this.instance.UI.disableElements(textActions);
@ -588,7 +588,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
ANNOTATION_POPUP,
];
if (this.canPerformActions && !this.utils.isCurrentPageExcluded) {
if (this.canPerformActions && !this.utils.isCurrentPageExcluded(this.file)) {
try {
this.instance.UI.enableTools(['AnnotationCreateRectangle']);
} catch (e) {
@ -605,7 +605,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
let elementsToDisable = [...elements, ADD_RECTANGLE];
if (this.utils.isCurrentPageExcluded) {
if (this.utils.isCurrentPageExcluded(this.file)) {
const allowedActionsWhenPageExcluded: string[] = [ANNOTATION_POPUP, ADD_RECTANGLE, ADD_REDACTION, SHAPE_TOOL_GROUP_BUTTON];
elementsToDisable = elementsToDisable.filter(element => !allowedActionsWhenPageExcluded.includes(element));
} else {

View File

@ -330,7 +330,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
.map(p => p.page)
.filter((item, pos, self) => self.indexOf(item) === pos);
for (const page of distinctPages) {
await this._cleanupAndRedrawManualAnnotationsForEntirePage(page);
await this._reloadAnnotationsForPage(page);
}
}
},
@ -398,7 +398,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
this._instance = $event;
this.ready = true;
await this._stampPDF();
await this._cleanupAndRedrawManualAnnotations();
await this._reloadAnnotations();
this._setExcludedPageStyles();
this._instance.Core.documentViewer.addEventListener('pageComplete', () => {
@ -419,7 +419,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
async annotationsChangedByReviewAction(annotation: AnnotationWrapper) {
await this._cleanupAndRedrawManualAnnotationsForEntirePage(annotation?.pageNumber || this.activeViewerPage);
await this._reloadAnnotationsForPage(annotation?.pageNumber || this.activeViewerPage);
}
ocredFile(): void {
@ -548,27 +548,32 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
await this._loadFileData(file, !this._reloadFileOnReanalysis);
this._reloadFileOnReanalysis = false;
this._loadingService.stop();
await this._cleanupAndRedrawManualAnnotations();
await this._reloadAnnotations();
});
}
private async _loadFileData(file: File, performUpdate = false): Promise<void> {
private async _loadFileData(file: File, performUpdate = false): Promise<void | boolean> {
if (!file || file.isError) {
return this._router.navigate([this._dossiersService.find(this.dossierId).routerLink]);
}
const fileData = await this._fileDownloadService.loadDataFor(file).toPromise();
if (!file?.isPending && !file?.isError) {
if (!file.isPending) {
let excludedOrIncludedPages = new Set<number>();
let currentPageAnnotations: AnnotationWrapper[] = [];
if (performUpdate && !!this.fileData) {
this.fileData.redactionLog = fileData.redactionLog;
this.fileData.viewedPages = fileData.viewedPages;
excludedOrIncludedPages = new Set([...this.fileData.file.excludedPages, ...file.excludedPages]);
currentPageAnnotations = this.annotations.filter(a => excludedOrIncludedPages.has(a.pageNumber));
this.fileData.file = file;
} else {
this.fileData = fileData;
}
this.rebuildFilters();
return;
}
if (file.isError) {
await this._router.navigate([this._dossiersService.find(this.dossierId).routerLink]);
return this._cleanupAndRedrawAnnotations(currentPageAnnotations, a => excludedOrIncludedPages.has(a.pageNumber));
}
}
@ -578,36 +583,32 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
this._workloadComponent?.scrollAnnotations();
}
private async _cleanupAndRedrawManualAnnotations() {
const currentAnnotations = this.annotations;
private async _reloadAnnotations() {
this.fileData.redactionLog = await this._fileDownloadService.loadRedactionLogFor(this.dossierId, this.fileId).toPromise();
this.rebuildFilters();
if (this.viewModeService.viewMode === 'STANDARD') {
currentAnnotations.forEach(annotation => {
this._findAndDeleteAnnotation(annotation.id);
});
const newAnnotations = this.annotations;
this._handleDeltaAnnotationFilters(currentAnnotations, newAnnotations);
await this._redrawAnnotations(newAnnotations);
}
await this._cleanupAndRedrawAnnotations(this.annotations);
}
private async _cleanupAndRedrawManualAnnotationsForEntirePage(page: number) {
private async _reloadAnnotationsForPage(page: number) {
const currentPageAnnotations = this.annotations.filter(a => a.pageNumber === page);
const currentPageAnnotationIds = currentPageAnnotations.map(a => a.id);
await this._filesService.reload(this.dossierId, this.fileId).toPromise();
this.fileData.redactionLog = await this._fileDownloadService.loadRedactionLogFor(this.dossierId, this.fileId).toPromise();
await this._cleanupAndRedrawAnnotations(currentPageAnnotations, annotation => annotation.pageNumber === page);
}
private async _cleanupAndRedrawAnnotations(
annotationsToDelete: AnnotationWrapper[],
newAnnotationsFilter?: (annotation: AnnotationWrapper) => boolean,
) {
this.rebuildFilters();
if (this.viewModeService.viewMode === 'STANDARD') {
currentPageAnnotationIds.forEach(id => {
this._findAndDeleteAnnotation(id);
annotationsToDelete?.forEach(annotation => {
this._findAndDeleteAnnotation(annotation.id);
});
const newPageAnnotations = this.annotations.filter(item => item.pageNumber === page);
this._handleDeltaAnnotationFilters(currentPageAnnotations, newPageAnnotations);
const newPageAnnotations = newAnnotationsFilter ? this.annotations.filter(newAnnotationsFilter) : this.annotations;
this._handleDeltaAnnotationFilters(annotationsToDelete ?? [], newPageAnnotations);
await this._redrawAnnotations(newPageAnnotations);
}
}

View File

@ -2,6 +2,7 @@ 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 { File } from '@red/domain';
import Annotation = Core.Annotations.Annotation;
const DISABLED_HOTKEYS = [
@ -39,7 +40,6 @@ const DISABLED_HOTKEYS = [
export class PdfViewerUtils {
ready = false;
excludedPages: number[] = [];
constructor(readonly instance: WebViewerInstance, readonly viewModeService: ViewModeService) {}
@ -47,11 +47,6 @@ export class PdfViewerUtils {
return this.viewModeService.isCompare ? 2 : 1;
}
get isCurrentPageExcluded() {
const currentPage = this.currentPage;
return !!this.excludedPages?.includes(currentPage);
}
get currentPage() {
try {
return this.viewModeService.isCompare ? Math.ceil(this._currentInternalPage / 2) : this._currentInternalPage;
@ -88,6 +83,11 @@ export class PdfViewerUtils {
return this.instance?.Core.documentViewer?.getPageCount();
}
isCurrentPageExcluded(file: File) {
const currentPage = this.currentPage;
return !!file?.excludedPages?.includes(currentPage);
}
navigateToPage(pageNumber: string | number) {
const parsedNumber = typeof pageNumber === 'string' ? parseInt(pageNumber, 10) : pageNumber;
this._navigateToPage(this.paginationOffset === 2 ? parsedNumber * this.paginationOffset - 1 : parsedNumber);

View File

@ -42,9 +42,9 @@ export class FilesService extends EntitiesService<File, IFile> {
}
@Validate()
setUnderApprovalFor(@RequiredParam() fileIds: List, @RequiredParam() dossierId: string, asigneeId: string) {
setUnderApprovalFor(@RequiredParam() fileIds: List, @RequiredParam() dossierId: string, assigneeId: string) {
const url = `${this._defaultModelPath}/under-approval/${dossierId}/bulk`;
return this._post<unknown>(fileIds, url, [{ key: 'assigneeId', value: asigneeId }]).pipe(switchMap(() => this.loadAll(dossierId)));
return this._post<unknown>(fileIds, url, [{ key: 'assigneeId', value: assigneeId }]).pipe(switchMap(() => this.loadAll(dossierId)));
}
/**