RED-8748 - extracted file preview header logic in another component

This commit is contained in:
Valentin Mihai 2024-03-28 12:27:36 +02:00
parent 690ffea6c5
commit 46e016d55e
14 changed files with 359 additions and 311 deletions

View File

@ -3,8 +3,6 @@ import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { MultiSelectService } from '../../services/multi-select.service';
import { annotationTypesTranslations } from '@translations/annotation-types-translations';
import { Roles } from '@users/roles';
import { ImageCategory } from '../../utils/constants';
import { ManualRedactionTypes } from '@red/domain';
@Component({
selector: 'redaction-annotation-card',

View File

@ -0,0 +1,86 @@
<div class="page-header">
<div class="flex flex-1">
<redaction-view-switch></redaction-view-switch>
</div>
<!-- TODO: mode this file preview header to a separate component-->
<div #actionsWrapper class="flex-2 actions-container">
<redaction-processing-indicator [file]="file" class="mr-16"></redaction-processing-indicator>
<redaction-user-management></redaction-user-management>
<ng-container *ngIf="permissionsService.isApprover(state.dossier()) && !!file.lastReviewer">
<div class="vertical-line"></div>
<div class="all-caps-label mr-16 ml-8 label">
{{ 'file-preview.last-assignee' | translate }}
</div>
<iqser-initials-avatar [user]="lastAssignee()" [withName]="true"></iqser-initials-avatar>
</ng-container>
<div class="vertical-line"></div>
<!-- TODO: mode these actions to a separate component -->
<iqser-circle-button
(action)="openComponentLogView()"
*allow="roles.getRss"
[attr.help-mode-key]="'editor_scm'"
[tooltip]="'file-preview.open-rss-view' | translate"
class="ml-8"
icon="red:extract"
tooltipPosition="below"
iqserDisableStopPropagation
></iqser-circle-button>
<redaction-file-actions
[dossier]="state.dossier()"
[file]="file"
[helpModeKeyPrefix]="'editor'"
[minWidth]="width"
type="file-preview"
iqserDisableStopPropagation
></redaction-file-actions>
<iqser-circle-button
(action)="getTables()"
*allow="roles.getTables"
[icon]="'red:csv'"
[tooltip]="'file-preview.get-tables' | translate"
class="ml-2"
iqserDisableStopPropagation
></iqser-circle-button>
<iqser-circle-button
(action)="toggleFullScreen()"
[attr.help-mode-key]="'editor_full_screen'"
[icon]="fullScreen ? 'red:exit-fullscreen' : 'red:fullscreen'"
[tooltip]="'file-preview.fullscreen' | translate"
class="ml-2"
iqserDisableStopPropagation
></iqser-circle-button>
<!-- Dev Mode Features-->
<iqser-circle-button
(action)="downloadOriginalFile(file)"
*ngIf="isIqserDevMode"
[tooltip]="'file-preview.download-original-file' | translate"
[type]="circleButtonTypes.primary"
class="ml-8"
icon="iqser:download"
iqserDisableStopPropagation
></iqser-circle-button>
<!-- End Dev Mode Features-->
<iqser-circle-button
*ngIf="!fullScreen"
[attr.help-mode-key]="'editor_close'"
[routerLink]="state.dossier().routerLink"
[tooltip]="'common.close' | translate"
class="ml-8"
icon="iqser:close"
iqserDisableStopPropagation
></iqser-circle-button>
</div>
</div>

View File

@ -0,0 +1,9 @@
.page-header {
max-width: 100vw;
}
.actions-container {
display: flex;
justify-content: flex-end;
align-items: center;
}

View File

@ -0,0 +1,217 @@
import {
AfterViewInit,
ChangeDetectorRef,
Component,
computed,
ElementRef,
HostListener,
Input,
NgZone,
OnDestroy,
OnInit,
ViewChild,
} from '@angular/core';
import { Roles } from '@users/roles';
import { CircleButtonTypes, HelpModeService, IqserDialog, IqserPermissionsService, isIqserDevMode, LoadingService } from '@iqser/common-ui';
import { Bind, Debounce, OnDetach } from '@iqser/common-ui/lib/utils';
import { File } from '@red/domain';
import { PermissionsService } from '@services/permissions.service';
import { FilePreviewStateService } from '../../services/file-preview-state.service';
import { UserPreferenceService } from '@users/user-preference.service';
import JSZip from 'jszip';
import { saveAs } from 'file-saver';
import { PdfViewer } from '../../../pdf-viewer/services/pdf-viewer.service';
import { AnnotationDrawService } from '../../../pdf-viewer/services/annotation-draw.service';
import { TablesService } from '../../services/tables.service';
import { ALL_HOTKEYS } from '../../utils/constants';
import { Router } from '@angular/router';
import { AnnotationActionsService } from '../../services/annotation-actions.service';
import { FileDataService } from '../../services/file-data.service';
import { REDAnnotationManager } from '../../../pdf-viewer/services/annotation-manager.service';
import { MatDialog } from '@angular/material/dialog';
import { download } from '@utils/file-download-utils';
import { firstValueFrom } from 'rxjs';
import { FileManagementService } from '@services/files/file-management.service';
import { StructuredComponentManagementDialogComponent } from '../../dialogs/docu-mine/structured-component-management-dialog/structured-component-management-dialog.component';
@Component({
selector: 'redaction-file-header',
templateUrl: './file-header.component.html',
styleUrls: ['/file-header.component.scss'],
})
export class FileHeaderComponent implements OnInit, AfterViewInit, OnDetach, OnDestroy {
@ViewChild('actionsWrapper', { static: false }) private readonly _actionsWrapper: ElementRef;
@Input() file: File;
protected readonly roles = Roles;
protected readonly circleButtonTypes = CircleButtonTypes;
readonly lastAssignee = computed(() => this.getLastAssignee());
readonly isIqserDevMode = isIqserDevMode();
width: number;
fullScreen = false;
constructor(
private readonly _changeRef: ChangeDetectorRef,
private readonly _iqserPermissionsService: IqserPermissionsService,
private readonly _userPreferenceService: UserPreferenceService,
private readonly _iqserDialog: IqserDialog,
private readonly _loadingService: LoadingService,
private readonly _pdf: PdfViewer,
private readonly _annotationDrawService: AnnotationDrawService,
private readonly _tablesService: TablesService,
private readonly _router: Router,
private readonly _ngZone: NgZone,
private readonly _helpModeService: HelpModeService,
private readonly _annotationActionsService: AnnotationActionsService,
private readonly _fileDataService: FileDataService,
private readonly _annotationManager: REDAnnotationManager,
private readonly _dialog: MatDialog,
private readonly _fileManagementService: FileManagementService,
readonly state: FilePreviewStateService,
readonly permissionsService: PermissionsService,
) {}
ngOnInit() {
this.#openComponentLogDialogIfDefault();
document.documentElement.addEventListener('fullscreenchange', this.fullscreenListener);
}
ngAfterViewInit() {
const _observer = new ResizeObserver((entries: ResizeObserverEntry[]) => {
this._updateItemWidth(entries[0]);
});
_observer.observe(this._actionsWrapper.nativeElement);
}
ngOnDetach() {
document.documentElement.removeEventListener('fullscreenchange', this.fullscreenListener);
}
ngOnDestroy() {
document.documentElement.removeEventListener('fullscreenchange', this.fullscreenListener);
}
async downloadOriginalFile({ cacheIdentifier, dossierId, fileId, filename }: File) {
const originalFile = this._fileManagementService.downloadOriginal(dossierId, fileId, 'response', cacheIdentifier);
download(await firstValueFrom(originalFile), filename);
}
getLastAssignee() {
const { isApproved, lastReviewer, lastApprover } = this.state.file();
const isRss = this._iqserPermissionsService.has(this.roles.getRss);
return isApproved ? (isRss ? lastReviewer : lastApprover) : lastReviewer;
}
openComponentLogView() {
const data = { file: this.state.file(), dictionaries: this.state.dictionaries };
this._iqserDialog.openDefault(StructuredComponentManagementDialogComponent, { data });
}
async getTables() {
this._loadingService.start();
const currentPage = this._pdf.currentPage();
const tables = await this._tablesService.get(this.state.dossierId, this.state.fileId, this._pdf.currentPage());
await this._annotationDrawService.drawTables(tables, currentPage, this.state.dossierTemplateId);
const filename = this.state.file().filename;
const zip = new JSZip();
tables.forEach((t, index) => {
const blob = new Blob([atob(t.csvAsBytes)], {
type: 'text/csv;charset=utf-8',
});
zip.file(filename + '_page' + currentPage + '_table' + (index + 1) + '.csv', blob);
});
saveAs(await zip.generateAsync({ type: 'blob' }), filename + '_tables.zip');
this._loadingService.stop();
}
toggleFullScreen() {
this.fullScreen = !this.fullScreen;
if (this.fullScreen) {
this.#openFullScreen();
} else {
this.closeFullScreen();
}
}
closeFullScreen() {
if (!!document.fullscreenElement && document.exitFullscreen) {
document.exitFullscreen().then();
}
}
@Bind()
fullscreenListener() {
if (!document.fullscreenElement) {
this.fullScreen = false;
}
}
@HostListener('document:keyup', ['$event'])
handleKeyEvent($event: KeyboardEvent) {
if (this._router.url.indexOf('/file/') < 0) {
return;
}
if (!ALL_HOTKEYS.includes($event.key) || this._dialog.openDialogs.length) {
return;
}
if (['Escape'].includes($event.key)) {
$event.preventDefault();
if (this._annotationManager.resizingAnnotationId) {
const resizedAnnotation = this._fileDataService
.annotations()
.find(annotation => annotation.id === this._annotationManager.resizingAnnotationId);
this._annotationActionsService.cancelResize(resizedAnnotation).then();
}
this.fullScreen = false;
this.closeFullScreen();
this._changeRef.markForCheck();
}
if (!$event.ctrlKey && !$event.metaKey && ['f', 'F'].includes($event.key)) {
// if you type in an input, don't toggle full-screen
if ($event.target instanceof HTMLInputElement || $event.target instanceof HTMLTextAreaElement) {
return;
}
this.toggleFullScreen();
return;
}
if (['h', 'H'].includes($event.key)) {
if ($event.target instanceof HTMLInputElement || $event.target instanceof HTMLTextAreaElement) {
return;
}
this._ngZone.run(() => {
window.focus();
this._helpModeService.activateHelpMode(false);
});
return;
}
}
#openFullScreen() {
const documentElement = document.documentElement;
if (documentElement.requestFullscreen) {
documentElement.requestFullscreen().then();
}
}
#openComponentLogDialogIfDefault() {
if (this.permissionsService.canViewRssDialog() && this._userPreferenceService.getOpenScmDialogByDefault()) {
this.openComponentLogView();
}
}
@Debounce(30)
private _updateItemWidth(entry: ResizeObserverEntry): void {
this.width = entry.contentRect.width;
this._changeRef.detectChanges();
}
}

View File

@ -1,21 +1,30 @@
import { KeyValuePipe, NgForOf, NgIf } from '@angular/common';
import { ChangeDetectionStrategy, Component, Inject, OnInit, signal } from '@angular/core';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
import { ReplaceNbspPipe } from '@common-ui/pipes/replace-nbsp.pipe';
import { BaseDialogComponent, CircleButtonComponent, EditableInputComponent, IconButtonComponent } from '@iqser/common-ui';
import { TranslateModule } from '@ngx-translate/core';
import { ComponentLogEntry, Dictionary, IFile, WorkflowFileStatuses } from '@red/domain';
import { FilesMapService } from '@services/files/files-map.service';
import { UserPreferenceService } from '@users/user-preference.service';
import { firstValueFrom } from 'rxjs';
import { ChangeDetectionStrategy, Component, Inject, OnInit, signal } from '@angular/core';
import { KeyValuePipe, NgForOf, NgIf } from '@angular/common';
import {
CircleButtonComponent,
EditableInputComponent,
IconButtonComponent,
IconButtonTypes,
IqserDialogComponent,
LoadingService,
} from '@iqser/common-ui';
import { TranslateModule } from '@ngx-translate/core';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import { ReplaceNbspPipe } from '@common-ui/pipes/replace-nbsp.pipe';
import { ComponentLogService } from '@services/files/component-log.service';
import { UserPreferenceService } from '@users/user-preference.service';
import { FilesMapService } from '@services/files/files-map.service';
import { firstValueFrom } from 'rxjs';
interface ScmData {
file: IFile;
dictionaries: Dictionary[];
}
interface ScmResult {}
@Component({
templateUrl: './structured-component-management-dialog.component.html',
styleUrls: ['./structured-component-management-dialog.component.scss'],
@ -34,18 +43,22 @@ interface ScmData {
ReplaceNbspPipe,
],
})
export class StructuredComponentManagementDialogComponent extends BaseDialogComponent implements OnInit {
export class StructuredComponentManagementDialogComponent
extends IqserDialogComponent<StructuredComponentManagementDialogComponent, ScmData, ScmResult>
implements OnInit
{
readonly componentLogData = signal<ComponentLogEntry[] | undefined>(undefined);
readonly openScmDialogByDefault = signal(this.userPreferences.getOpenScmDialogByDefault());
readonly iconButtonTypes = IconButtonTypes;
constructor(
protected readonly _dialogRef: MatDialogRef<StructuredComponentManagementDialogComponent>,
private readonly _componentLogService: ComponentLogService,
readonly userPreferences: UserPreferenceService,
private readonly _filesMapService: FilesMapService,
private readonly _loadingService: LoadingService,
readonly userPreferences: UserPreferenceService,
@Inject(MAT_DIALOG_DATA) readonly data: ScmData,
) {
super(_dialogRef);
super();
}
get canEdit() {
@ -60,8 +73,6 @@ export class StructuredComponentManagementDialogComponent extends BaseDialogComp
return `value-cell-${index}`;
}
originalOrder = (): number => 0;
exportJSON() {
return firstValueFrom(
this._componentLogService.exportJSON(this.data.file.dossierTemplateId, this.data.file.dossierId, this.data.file),

View File

@ -1,90 +1,5 @@
<section *ngIf="state.file() as file">
<div class="page-header">
<div class="flex flex-1">
<redaction-view-switch></redaction-view-switch>
</div>
<!-- TODO: mode this file preview header to a separate component-->
<div #actionsWrapper class="flex-2 actions-container">
<redaction-processing-indicator [file]="file" class="mr-16"></redaction-processing-indicator>
<redaction-user-management></redaction-user-management>
<ng-container *ngIf="permissionsService.isApprover(state.dossier()) && !!file.lastReviewer">
<div class="vertical-line"></div>
<div class="all-caps-label mr-16 ml-8 label">
{{ 'file-preview.last-assignee' | translate }}
</div>
<iqser-initials-avatar [user]="lastAssignee()" [withName]="true"></iqser-initials-avatar>
</ng-container>
<div class="vertical-line"></div>
<!-- TODO: mode these actions to a separate component -->
<iqser-circle-button
(action)="openComponentLogView()"
*allow="roles.getRss"
[attr.help-mode-key]="'editor_scm'"
[tooltip]="'file-preview.open-rss-view' | translate"
class="ml-8"
icon="red:extract"
tooltipPosition="below"
iqserDisableStopPropagation
></iqser-circle-button>
<redaction-file-actions
[dossier]="state.dossier()"
[file]="file"
[helpModeKeyPrefix]="'editor'"
[minWidth]="width"
type="file-preview"
iqserDisableStopPropagation
></redaction-file-actions>
<iqser-circle-button
(action)="getTables()"
*allow="roles.getTables"
[icon]="'red:csv'"
[tooltip]="'file-preview.get-tables' | translate"
class="ml-2"
iqserDisableStopPropagation
></iqser-circle-button>
<iqser-circle-button
(action)="toggleFullScreen()"
[attr.help-mode-key]="'editor_full_screen'"
[icon]="fullScreen ? 'red:exit-fullscreen' : 'red:fullscreen'"
[tooltip]="'file-preview.fullscreen' | translate"
class="ml-2"
iqserDisableStopPropagation
></iqser-circle-button>
<!-- Dev Mode Features-->
<iqser-circle-button
(action)="downloadOriginalFile(file)"
*ngIf="isIqserDevMode"
[tooltip]="'file-preview.download-original-file' | translate"
[type]="circleButtonTypes.primary"
class="ml-8"
icon="iqser:download"
iqserDisableStopPropagation
></iqser-circle-button>
<!-- End Dev Mode Features-->
<iqser-circle-button
*ngIf="!fullScreen"
[attr.help-mode-key]="'editor_close'"
[routerLink]="state.dossier().routerLink"
[tooltip]="'common.close' | translate"
class="ml-8"
icon="iqser:close"
iqserDisableStopPropagation
></iqser-circle-button>
</div>
</div>
<redaction-file-header [file]="file"></redaction-file-header>
<div class="overlay-shadow"></div>
@ -94,7 +9,9 @@
</div>
<div class="right-container">
<redaction-file-preview-right-container [iqserDisableStopPropagation]="state.isEditingReviewer()"></redaction-file-preview-right-container>
<redaction-file-preview-right-container
[iqserDisableStopPropagation]="state.isEditingReviewer()"
></redaction-file-preview-right-container>
</div>
</div>
</section>

View File

@ -5,16 +5,6 @@
margin: 0 16px;
}
.page-header {
max-width: 100vw;
}
.actions-container {
display: flex;
justify-content: flex-end;
align-items: center;
}
.content-inner {
position: absolute;
}

View File

@ -1,18 +1,4 @@
import {
AfterViewInit,
ChangeDetectorRef,
Component,
computed,
effect,
ElementRef,
HostListener,
NgZone,
OnDestroy,
OnInit,
TemplateRef,
ViewChild,
} from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { ChangeDetectorRef, Component, effect, NgZone, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { ActivatedRouteSnapshot, NavigationExtras, Router } from '@angular/router';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { ComponentCanDeactivate } from '@guards/can-deactivate.guard';
@ -23,16 +9,13 @@ import {
CustomError,
ErrorService,
getConfig,
HelpModeService,
IConfirmationDialogData,
IqserDialog,
IqserPermissionsService,
isIqserDevMode,
LoadingService,
Toaster,
} from '@iqser/common-ui';
import { copyLocalStorageFiltersValues, FilterService, NestedFilter, processFilters } from '@iqser/common-ui/lib/filtering';
import { AutoUnsubscribe, Bind, bool, Debounce, List, OnAttach, OnDetach } from '@iqser/common-ui/lib/utils';
import { AutoUnsubscribe, Bind, bool, List, OnAttach, OnDetach } from '@iqser/common-ui/lib/utils';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { ManualRedactionEntryTypes, ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
import { Dictionary, File, ViewModes } from '@red/domain';
@ -46,8 +29,6 @@ import { PermissionsService } from '@services/permissions.service';
import { ReanalysisService } from '@services/reanalysis.service';
import { Roles } from '@users/roles';
import { PreferencesKeys, UserPreferenceService } from '@users/user-preference.service';
import { saveAs } from 'file-saver';
import JSZip from 'jszip';
import { NGXLogger } from 'ngx-logger';
import { combineLatest, first, firstValueFrom, Observable, of, pairwise } from 'rxjs';
import { catchError, filter, map, startWith, switchMap, tap } from 'rxjs/operators';
@ -73,36 +54,25 @@ import { ManualRedactionService } from './services/manual-redaction.service';
import { PdfProxyService } from './services/pdf-proxy.service';
import { SkippedService } from './services/skipped.service';
import { StampService } from './services/stamp.service';
import { TablesService } from './services/tables.service';
import { ViewModeService } from './services/view-mode.service';
import { ALL_HOTKEYS } from './utils/constants';
import { RedactTextData } from './utils/dialog-types';
import { AnnotationActionsService } from './services/annotation-actions.service';
@Component({
templateUrl: './file-preview-screen.component.html',
styleUrls: ['./file-preview-screen.component.scss'],
providers: filePreviewScreenProviders,
})
export class FilePreviewScreenComponent
extends AutoUnsubscribe
implements AfterViewInit, OnInit, OnDestroy, OnAttach, OnDetach, ComponentCanDeactivate
{
export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnInit, OnDestroy, OnAttach, OnDetach, ComponentCanDeactivate {
readonly circleButtonTypes = CircleButtonTypes;
readonly roles = Roles;
fullScreen = false;
readonly fileId = this.state.fileId;
readonly dossierId = this.state.dossierId;
readonly lastAssignee = computed(() => this.getLastAssignee());
width: number;
readonly isIqserDevMode = isIqserDevMode();
@ViewChild('annotationFilterTemplate', {
read: TemplateRef,
static: false,
})
private readonly _filterTemplate: TemplateRef<unknown>;
#loadAllAnnotationsEnabled = false;
@ViewChild('actionsWrapper', { static: false }) private readonly _actionsWrapper: ElementRef;
readonly #isDocumine = getConfig().IS_DOCUMINE;
constructor(
@ -112,7 +82,6 @@ export class FilePreviewScreenComponent
readonly userPreferenceService: UserPreferenceService,
readonly pdfProxyService: PdfProxyService,
readonly configService: ConfigService,
private readonly _iqserPermissionsService: IqserPermissionsService,
private readonly _listingService: AnnotationsListingService,
private readonly _router: Router,
private readonly _ngZone: NgZone,
@ -141,11 +110,7 @@ export class FilePreviewScreenComponent
private readonly _filesService: FilesService,
private readonly _fileManagementService: FileManagementService,
private readonly _readableRedactionsService: ReadableRedactionsService,
private readonly _helpModeService: HelpModeService,
private readonly _dossierTemplatesService: DossierTemplatesService,
private readonly _dialog: MatDialog,
private readonly _tablesService: TablesService,
private readonly _annotationActionsService: AnnotationActionsService,
) {
super();
effect(() => {
@ -211,12 +176,6 @@ export class FilePreviewScreenComponent
);
}
getLastAssignee() {
const { isApproved, lastReviewer, lastApprover } = this.state.file();
const isRss = this._iqserPermissionsService.has(this.roles.getRss);
return isApproved ? (isRss ? lastReviewer : lastApprover) : lastReviewer;
}
deleteEarmarksOnViewChange$() {
const isChangingFromEarmarksViewMode$ = this._viewModeService.viewMode$.pipe(
pairwise(),
@ -293,24 +252,15 @@ export class FilePreviewScreenComponent
this._viewerHeaderService.resetCompareButtons();
this._viewerHeaderService.enableLoadAllAnnotations(); // Reset the button state (since the viewer is reused between files)
super.ngOnDetach();
document.documentElement.removeEventListener('fullscreenchange', this.fullscreenListener);
this.pdf.instance.UI.hotkeys.off('esc');
this._changeRef.markForCheck();
}
ngOnDestroy() {
document.documentElement.removeEventListener('fullscreenchange', this.fullscreenListener);
this.pdf.instance.UI.hotkeys.off('esc');
super.ngOnDestroy();
}
@Bind()
fullscreenListener() {
if (!document.fullscreenElement) {
this.fullScreen = false;
}
}
@Bind()
handleDeleteRectangleOnEsc($event: KeyboardEvent) {
$event.preventDefault();
@ -355,19 +305,10 @@ export class FilePreviewScreenComponent
this.pdfProxyService.configureElements();
this.#restoreOldFilters();
document.documentElement.addEventListener('fullscreenchange', this.fullscreenListener);
this.pdf.instance.UI.hotkeys.on('esc', this.handleDeleteRectangleOnEsc);
this.#openComponentLogDialogIfDefault();
this._viewerHeaderService.resetLayers();
}
ngAfterViewInit() {
const _observer = new ResizeObserver((entries: ResizeObserverEntry[]) => {
this._updateItemWidth(entries[0]);
});
_observer.observe(this._actionsWrapper.nativeElement);
}
openManualAnnotationDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) {
const file = this.state.file();
@ -393,59 +334,6 @@ export class FilePreviewScreenComponent
);
}
toggleFullScreen() {
this.fullScreen = !this.fullScreen;
if (this.fullScreen) {
this.#openFullScreen();
} else {
this.closeFullScreen();
}
}
@HostListener('document:keyup', ['$event'])
handleKeyEvent($event: KeyboardEvent) {
if (this._router.url.indexOf('/file/') < 0) {
return;
}
if (!ALL_HOTKEYS.includes($event.key) || this._dialog.openDialogs.length) {
return;
}
if (['Escape'].includes($event.key)) {
$event.preventDefault();
if (this._annotationManager.resizingAnnotationId) {
const resizedAnnotation = this._fileDataService
.annotations()
.find(annotation => annotation.id === this._annotationManager.resizingAnnotationId);
this._annotationActionsService.cancelResize(resizedAnnotation).then();
}
this.fullScreen = false;
this.closeFullScreen();
this._changeRef.markForCheck();
}
if (!$event.ctrlKey && !$event.metaKey && ['f', 'F'].includes($event.key)) {
// if you type in an input, don't toggle full-screen
if ($event.target instanceof HTMLInputElement || $event.target instanceof HTMLTextAreaElement) {
return;
}
this.toggleFullScreen();
return;
}
if (['h', 'H'].includes($event.key)) {
if ($event.target instanceof HTMLInputElement || $event.target instanceof HTMLTextAreaElement) {
return;
}
this._ngZone.run(() => {
window.focus();
this._helpModeService.activateHelpMode(false);
});
return;
}
}
async viewerReady(pageNumber?: string) {
if (pageNumber) {
const file = this.state.file();
@ -467,21 +355,6 @@ export class FilePreviewScreenComponent
this._changeRef.markForCheck();
}
closeFullScreen() {
if (!!document.fullscreenElement && document.exitFullscreen) {
document.exitFullscreen().then();
}
}
async downloadOriginalFile({ cacheIdentifier, dossierId, fileId, filename }: File) {
const originalFile = this._fileManagementService.downloadOriginal(dossierId, fileId, 'response', cacheIdentifier);
download(await firstValueFrom(originalFile), filename);
}
openComponentLogView() {
this._dialogService.openDialog('componentLog', { file: this.state.file(), dictionaries: this.state.dictionaries });
}
loadAnnotations$() {
const annotations$ = this._fileDataService.annotations$.pipe(
startWith([] as AnnotationWrapper[]),
@ -527,28 +400,6 @@ export class FilePreviewScreenComponent
return this.#cleanupAndRedrawAnnotations(annotationsToDraw);
}
async getTables() {
this._loadingService.start();
const currentPage = this.pdf.currentPage();
const tables = await this._tablesService.get(this.state.dossierId, this.state.fileId, this.pdf.currentPage());
await this._annotationDrawService.drawTables(tables, currentPage, this.state.dossierTemplateId);
const filename = this.state.file().filename;
const zip = new JSZip();
tables.forEach((t, index) => {
const blob = new Blob([atob(t.csvAsBytes)], {
type: 'text/csv;charset=utf-8',
});
zip.file(filename + '_page' + currentPage + '_table' + (index + 1) + '.csv', blob);
});
saveAs(await zip.generateAsync({ type: 'blob' }), filename + '_tables.zip');
this._loadingService.stop();
}
async #openRedactTextDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) {
const file = this.state.file();
@ -572,12 +423,6 @@ export class FilePreviewScreenComponent
return firstValueFrom(addAndReload$.pipe(catchError(() => of(undefined))));
}
@Debounce(30)
private _updateItemWidth(entry: ResizeObserverEntry): void {
this.width = entry.contentRect.width;
this._changeRef.detectChanges();
}
#getAnnotationsToDraw(oldAnnotations: AnnotationWrapper[], newAnnotations: AnnotationWrapper[]) {
const currentPage = this.pdf.currentPage();
const currentPageAnnotations = this._annotationManager.get(a => a.getPageNumber() === currentPage);
@ -711,10 +556,6 @@ export class FilePreviewScreenComponent
.pipe(tap(() => this.#handleDeletedFile()))
.subscribe();
this.addActiveScreenSubscription = this._documentViewer.keyUp$.subscribe($event => {
this.handleKeyEvent($event);
});
this.addActiveScreenSubscription = this.#earmarks$.subscribe();
this.addActiveScreenSubscription = this.deleteEarmarksOnViewChange$().subscribe();
@ -845,13 +686,6 @@ export class FilePreviewScreenComponent
});
}
#openFullScreen() {
const documentElement = document.documentElement;
if (documentElement.requestFullscreen) {
documentElement.requestFullscreen().then();
}
}
#navigateToDossier() {
this._logger.info('Navigating to ', this.state.dossier().dossierName);
return this._router.navigate([this.state.dossier().routerLink]);
@ -878,12 +712,6 @@ export class FilePreviewScreenComponent
});
}
#openComponentLogDialogIfDefault() {
if (this.permissionsService.canViewRssDialog() && this.userPreferenceService.getOpenScmDialogByDefault()) {
this.openComponentLogView();
}
}
#getRedactTextDialog(data: RedactTextData) {
if (this.#isDocumine) {
return this._iqserDialog.openDefault(AddAnnotationDialogComponent, { data });

View File

@ -70,6 +70,7 @@ import { DocumentUnloadedGuard } from './services/document-unloaded.guard';
import { FilePreviewDialogService } from './services/file-preview-dialog.service';
import { ManualRedactionService } from './services/manual-redaction.service';
import { TablesService } from './services/tables.service';
import { FileHeaderComponent } from './components/file-header/file-header.component';
const routes: IqserRoutes = [
{
@ -119,6 +120,7 @@ const components = [
FilePreviewScreenComponent,
FilePreviewRightContainerComponent,
ReadonlyBannerComponent,
FileHeaderComponent,
];
@NgModule({

View File

@ -6,16 +6,8 @@ import { DocumentInfoDialogComponent } from '../dialogs/document-info-dialog/doc
import { ForceAnnotationDialogComponent } from '../dialogs/force-redaction-dialog/force-annotation-dialog.component';
import { HighlightActionDialogComponent } from '../dialogs/highlight-action-dialog/highlight-action-dialog.component';
import { ManualAnnotationDialogComponent } from '../dialogs/manual-redaction-dialog/manual-annotation-dialog.component';
import { StructuredComponentManagementDialogComponent } from '../dialogs/structured-component-management-dialog/structured-component-management-dialog.component';
type DialogType =
| 'confirm'
| 'documentInfo'
| 'componentLog'
| 'changeLegalBasis'
| 'forceAnnotation'
| 'manualAnnotation'
| 'highlightAction';
type DialogType = 'confirm' | 'documentInfo' | 'changeLegalBasis' | 'forceAnnotation' | 'manualAnnotation' | 'highlightAction';
@Injectable()
export class FilePreviewDialogService extends DialogService<DialogType> {
@ -41,10 +33,6 @@ export class FilePreviewDialogService extends DialogService<DialogType> {
highlightAction: {
component: HighlightActionDialogComponent,
},
componentLog: {
component: StructuredComponentManagementDialogComponent,
dialogConfig: { width: '90vw' },
},
};
constructor(protected readonly _dialog: MatDialog) {

View File

@ -133,9 +133,11 @@ export class PdfProxyService {
}
configureElements() {
const hexColor = this._dictionariesMapService.get(this._state.dossierTemplateId, 'manual').hexColor;
const color = this._annotationDrawService.convertColor(hexColor);
this._documentViewer.setRectangleToolStyles(color);
const hexColor = this._dictionariesMapService.get(this._state.dossierTemplateId, 'manual')?.hexColor;
if (hexColor) {
const color = this._annotationDrawService.convertColor(hexColor);
this._documentViewer.setRectangleToolStyles(color);
}
}
#configureRectangleAnnotationPopup(annotation: Annotation) {

View File

@ -1,9 +1,9 @@
{
"ADMIN_CONTACT_NAME": null,
"ADMIN_CONTACT_URL": null,
"API_URL": "https://dan1.iqser.cloud",
"API_URL": "https://frontend2.iqser.cloud",
"APP_NAME": "RedactManager",
"IS_DOCUMINE": false,
"IS_DOCUMINE": true,
"RULE_EDITOR_DEV_ONLY": false,
"AUTO_READ_TIME": 3,
"BACKEND_APP_VERSION": "4.4.40",
@ -13,13 +13,13 @@
"MAX_RETRIES_ON_SERVER_ERROR": 3,
"OAUTH_CLIENT_ID": "redaction",
"OAUTH_IDP_HINT": null,
"OAUTH_URL": "https://dan1.iqser.cloud/auth",
"OAUTH_URL": "https://frontend2.iqser.cloud/auth",
"RECENT_PERIOD_IN_HOURS": 24,
"SELECTION_MODE": "structural",
"MANUAL_BASE_URL": "https://docs.redactmanager.com/preview",
"ANNOTATIONS_THRESHOLD": 1000,
"THEME": "redact",
"BASE_TRANSLATIONS_DIRECTORY": "/assets/i18n/redact/",
"THEME": "scm",
"BASE_TRANSLATIONS_DIRECTORY": "/assets/i18n/scm/",
"AVAILABLE_NOTIFICATIONS_DAYS": 30,
"AVAILABLE_OLD_NOTIFICATIONS_MINUTES": 60,
"NOTIFICATIONS_THRESHOLD": 1000,