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 { MultiSelectService } from '../../services/multi-select.service';
import { annotationTypesTranslations } from '@translations/annotation-types-translations'; import { annotationTypesTranslations } from '@translations/annotation-types-translations';
import { Roles } from '@users/roles'; import { Roles } from '@users/roles';
import { ImageCategory } from '../../utils/constants';
import { ManualRedactionTypes } from '@red/domain';
@Component({ @Component({
selector: 'redaction-annotation-card', 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 { ComponentLogEntry, Dictionary, IFile, WorkflowFileStatuses } from '@red/domain';
import { FilesMapService } from '@services/files/files-map.service'; import { ChangeDetectionStrategy, Component, Inject, OnInit, signal } from '@angular/core';
import { UserPreferenceService } from '@users/user-preference.service'; import { KeyValuePipe, NgForOf, NgIf } from '@angular/common';
import { firstValueFrom } from 'rxjs'; 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 { 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 { interface ScmData {
file: IFile; file: IFile;
dictionaries: Dictionary[]; dictionaries: Dictionary[];
} }
interface ScmResult {}
@Component({ @Component({
templateUrl: './structured-component-management-dialog.component.html', templateUrl: './structured-component-management-dialog.component.html',
styleUrls: ['./structured-component-management-dialog.component.scss'], styleUrls: ['./structured-component-management-dialog.component.scss'],
@ -34,18 +43,22 @@ interface ScmData {
ReplaceNbspPipe, 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 componentLogData = signal<ComponentLogEntry[] | undefined>(undefined);
readonly openScmDialogByDefault = signal(this.userPreferences.getOpenScmDialogByDefault()); readonly openScmDialogByDefault = signal(this.userPreferences.getOpenScmDialogByDefault());
readonly iconButtonTypes = IconButtonTypes;
constructor( constructor(
protected readonly _dialogRef: MatDialogRef<StructuredComponentManagementDialogComponent>,
private readonly _componentLogService: ComponentLogService, private readonly _componentLogService: ComponentLogService,
readonly userPreferences: UserPreferenceService,
private readonly _filesMapService: FilesMapService, private readonly _filesMapService: FilesMapService,
private readonly _loadingService: LoadingService,
readonly userPreferences: UserPreferenceService,
@Inject(MAT_DIALOG_DATA) readonly data: ScmData, @Inject(MAT_DIALOG_DATA) readonly data: ScmData,
) { ) {
super(_dialogRef); super();
} }
get canEdit() { get canEdit() {
@ -60,8 +73,6 @@ export class StructuredComponentManagementDialogComponent extends BaseDialogComp
return `value-cell-${index}`; return `value-cell-${index}`;
} }
originalOrder = (): number => 0;
exportJSON() { exportJSON() {
return firstValueFrom( return firstValueFrom(
this._componentLogService.exportJSON(this.data.file.dossierTemplateId, this.data.file.dossierId, this.data.file), 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"> <section *ngIf="state.file() as file">
<div class="page-header"> <redaction-file-header [file]="file"></redaction-file-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>
<div class="overlay-shadow"></div> <div class="overlay-shadow"></div>
@ -94,7 +9,9 @@
</div> </div>
<div class="right-container"> <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>
</div> </div>
</section> </section>

View File

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

View File

@ -1,18 +1,4 @@
import { import { ChangeDetectorRef, Component, effect, NgZone, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
AfterViewInit,
ChangeDetectorRef,
Component,
computed,
effect,
ElementRef,
HostListener,
NgZone,
OnDestroy,
OnInit,
TemplateRef,
ViewChild,
} from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRouteSnapshot, NavigationExtras, Router } from '@angular/router'; import { ActivatedRouteSnapshot, NavigationExtras, Router } from '@angular/router';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { ComponentCanDeactivate } from '@guards/can-deactivate.guard'; import { ComponentCanDeactivate } from '@guards/can-deactivate.guard';
@ -23,16 +9,13 @@ import {
CustomError, CustomError,
ErrorService, ErrorService,
getConfig, getConfig,
HelpModeService,
IConfirmationDialogData, IConfirmationDialogData,
IqserDialog, IqserDialog,
IqserPermissionsService,
isIqserDevMode,
LoadingService, LoadingService,
Toaster, Toaster,
} from '@iqser/common-ui'; } from '@iqser/common-ui';
import { copyLocalStorageFiltersValues, FilterService, NestedFilter, processFilters } from '@iqser/common-ui/lib/filtering'; 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 { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { ManualRedactionEntryTypes, ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper'; import { ManualRedactionEntryTypes, ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
import { Dictionary, File, ViewModes } from '@red/domain'; import { Dictionary, File, ViewModes } from '@red/domain';
@ -46,8 +29,6 @@ import { PermissionsService } from '@services/permissions.service';
import { ReanalysisService } from '@services/reanalysis.service'; import { ReanalysisService } from '@services/reanalysis.service';
import { Roles } from '@users/roles'; import { Roles } from '@users/roles';
import { PreferencesKeys, UserPreferenceService } from '@users/user-preference.service'; import { PreferencesKeys, UserPreferenceService } from '@users/user-preference.service';
import { saveAs } from 'file-saver';
import JSZip from 'jszip';
import { NGXLogger } from 'ngx-logger'; import { NGXLogger } from 'ngx-logger';
import { combineLatest, first, firstValueFrom, Observable, of, pairwise } from 'rxjs'; import { combineLatest, first, firstValueFrom, Observable, of, pairwise } from 'rxjs';
import { catchError, filter, map, startWith, switchMap, tap } from 'rxjs/operators'; 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 { PdfProxyService } from './services/pdf-proxy.service';
import { SkippedService } from './services/skipped.service'; import { SkippedService } from './services/skipped.service';
import { StampService } from './services/stamp.service'; import { StampService } from './services/stamp.service';
import { TablesService } from './services/tables.service';
import { ViewModeService } from './services/view-mode.service'; import { ViewModeService } from './services/view-mode.service';
import { ALL_HOTKEYS } from './utils/constants';
import { RedactTextData } from './utils/dialog-types'; import { RedactTextData } from './utils/dialog-types';
import { AnnotationActionsService } from './services/annotation-actions.service';
@Component({ @Component({
templateUrl: './file-preview-screen.component.html', templateUrl: './file-preview-screen.component.html',
styleUrls: ['./file-preview-screen.component.scss'], styleUrls: ['./file-preview-screen.component.scss'],
providers: filePreviewScreenProviders, providers: filePreviewScreenProviders,
}) })
export class FilePreviewScreenComponent export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnInit, OnDestroy, OnAttach, OnDetach, ComponentCanDeactivate {
extends AutoUnsubscribe
implements AfterViewInit, OnInit, OnDestroy, OnAttach, OnDetach, ComponentCanDeactivate
{
readonly circleButtonTypes = CircleButtonTypes; readonly circleButtonTypes = CircleButtonTypes;
readonly roles = Roles; readonly roles = Roles;
fullScreen = false;
readonly fileId = this.state.fileId; readonly fileId = this.state.fileId;
readonly dossierId = this.state.dossierId; readonly dossierId = this.state.dossierId;
readonly lastAssignee = computed(() => this.getLastAssignee());
width: number;
readonly isIqserDevMode = isIqserDevMode();
@ViewChild('annotationFilterTemplate', { @ViewChild('annotationFilterTemplate', {
read: TemplateRef, read: TemplateRef,
static: false, static: false,
}) })
private readonly _filterTemplate: TemplateRef<unknown>; private readonly _filterTemplate: TemplateRef<unknown>;
#loadAllAnnotationsEnabled = false; #loadAllAnnotationsEnabled = false;
@ViewChild('actionsWrapper', { static: false }) private readonly _actionsWrapper: ElementRef;
readonly #isDocumine = getConfig().IS_DOCUMINE; readonly #isDocumine = getConfig().IS_DOCUMINE;
constructor( constructor(
@ -112,7 +82,6 @@ export class FilePreviewScreenComponent
readonly userPreferenceService: UserPreferenceService, readonly userPreferenceService: UserPreferenceService,
readonly pdfProxyService: PdfProxyService, readonly pdfProxyService: PdfProxyService,
readonly configService: ConfigService, readonly configService: ConfigService,
private readonly _iqserPermissionsService: IqserPermissionsService,
private readonly _listingService: AnnotationsListingService, private readonly _listingService: AnnotationsListingService,
private readonly _router: Router, private readonly _router: Router,
private readonly _ngZone: NgZone, private readonly _ngZone: NgZone,
@ -141,11 +110,7 @@ export class FilePreviewScreenComponent
private readonly _filesService: FilesService, private readonly _filesService: FilesService,
private readonly _fileManagementService: FileManagementService, private readonly _fileManagementService: FileManagementService,
private readonly _readableRedactionsService: ReadableRedactionsService, private readonly _readableRedactionsService: ReadableRedactionsService,
private readonly _helpModeService: HelpModeService,
private readonly _dossierTemplatesService: DossierTemplatesService, private readonly _dossierTemplatesService: DossierTemplatesService,
private readonly _dialog: MatDialog,
private readonly _tablesService: TablesService,
private readonly _annotationActionsService: AnnotationActionsService,
) { ) {
super(); super();
effect(() => { 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$() { deleteEarmarksOnViewChange$() {
const isChangingFromEarmarksViewMode$ = this._viewModeService.viewMode$.pipe( const isChangingFromEarmarksViewMode$ = this._viewModeService.viewMode$.pipe(
pairwise(), pairwise(),
@ -293,24 +252,15 @@ export class FilePreviewScreenComponent
this._viewerHeaderService.resetCompareButtons(); this._viewerHeaderService.resetCompareButtons();
this._viewerHeaderService.enableLoadAllAnnotations(); // Reset the button state (since the viewer is reused between files) this._viewerHeaderService.enableLoadAllAnnotations(); // Reset the button state (since the viewer is reused between files)
super.ngOnDetach(); super.ngOnDetach();
document.documentElement.removeEventListener('fullscreenchange', this.fullscreenListener);
this.pdf.instance.UI.hotkeys.off('esc'); this.pdf.instance.UI.hotkeys.off('esc');
this._changeRef.markForCheck(); this._changeRef.markForCheck();
} }
ngOnDestroy() { ngOnDestroy() {
document.documentElement.removeEventListener('fullscreenchange', this.fullscreenListener);
this.pdf.instance.UI.hotkeys.off('esc'); this.pdf.instance.UI.hotkeys.off('esc');
super.ngOnDestroy(); super.ngOnDestroy();
} }
@Bind()
fullscreenListener() {
if (!document.fullscreenElement) {
this.fullScreen = false;
}
}
@Bind() @Bind()
handleDeleteRectangleOnEsc($event: KeyboardEvent) { handleDeleteRectangleOnEsc($event: KeyboardEvent) {
$event.preventDefault(); $event.preventDefault();
@ -355,19 +305,10 @@ export class FilePreviewScreenComponent
this.pdfProxyService.configureElements(); this.pdfProxyService.configureElements();
this.#restoreOldFilters(); this.#restoreOldFilters();
document.documentElement.addEventListener('fullscreenchange', this.fullscreenListener);
this.pdf.instance.UI.hotkeys.on('esc', this.handleDeleteRectangleOnEsc); this.pdf.instance.UI.hotkeys.on('esc', this.handleDeleteRectangleOnEsc);
this.#openComponentLogDialogIfDefault();
this._viewerHeaderService.resetLayers(); this._viewerHeaderService.resetLayers();
} }
ngAfterViewInit() {
const _observer = new ResizeObserver((entries: ResizeObserverEntry[]) => {
this._updateItemWidth(entries[0]);
});
_observer.observe(this._actionsWrapper.nativeElement);
}
openManualAnnotationDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) { openManualAnnotationDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) {
const file = this.state.file(); 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) { async viewerReady(pageNumber?: string) {
if (pageNumber) { if (pageNumber) {
const file = this.state.file(); const file = this.state.file();
@ -467,21 +355,6 @@ export class FilePreviewScreenComponent
this._changeRef.markForCheck(); 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$() { loadAnnotations$() {
const annotations$ = this._fileDataService.annotations$.pipe( const annotations$ = this._fileDataService.annotations$.pipe(
startWith([] as AnnotationWrapper[]), startWith([] as AnnotationWrapper[]),
@ -527,28 +400,6 @@ export class FilePreviewScreenComponent
return this.#cleanupAndRedrawAnnotations(annotationsToDraw); 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) { async #openRedactTextDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) {
const file = this.state.file(); const file = this.state.file();
@ -572,12 +423,6 @@ export class FilePreviewScreenComponent
return firstValueFrom(addAndReload$.pipe(catchError(() => of(undefined)))); 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[]) { #getAnnotationsToDraw(oldAnnotations: AnnotationWrapper[], newAnnotations: AnnotationWrapper[]) {
const currentPage = this.pdf.currentPage(); const currentPage = this.pdf.currentPage();
const currentPageAnnotations = this._annotationManager.get(a => a.getPageNumber() === currentPage); const currentPageAnnotations = this._annotationManager.get(a => a.getPageNumber() === currentPage);
@ -711,10 +556,6 @@ export class FilePreviewScreenComponent
.pipe(tap(() => this.#handleDeletedFile())) .pipe(tap(() => this.#handleDeletedFile()))
.subscribe(); .subscribe();
this.addActiveScreenSubscription = this._documentViewer.keyUp$.subscribe($event => {
this.handleKeyEvent($event);
});
this.addActiveScreenSubscription = this.#earmarks$.subscribe(); this.addActiveScreenSubscription = this.#earmarks$.subscribe();
this.addActiveScreenSubscription = this.deleteEarmarksOnViewChange$().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() { #navigateToDossier() {
this._logger.info('Navigating to ', this.state.dossier().dossierName); this._logger.info('Navigating to ', this.state.dossier().dossierName);
return this._router.navigate([this.state.dossier().routerLink]); 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) { #getRedactTextDialog(data: RedactTextData) {
if (this.#isDocumine) { if (this.#isDocumine) {
return this._iqserDialog.openDefault(AddAnnotationDialogComponent, { data }); 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 { FilePreviewDialogService } from './services/file-preview-dialog.service';
import { ManualRedactionService } from './services/manual-redaction.service'; import { ManualRedactionService } from './services/manual-redaction.service';
import { TablesService } from './services/tables.service'; import { TablesService } from './services/tables.service';
import { FileHeaderComponent } from './components/file-header/file-header.component';
const routes: IqserRoutes = [ const routes: IqserRoutes = [
{ {
@ -119,6 +120,7 @@ const components = [
FilePreviewScreenComponent, FilePreviewScreenComponent,
FilePreviewRightContainerComponent, FilePreviewRightContainerComponent,
ReadonlyBannerComponent, ReadonlyBannerComponent,
FileHeaderComponent,
]; ];
@NgModule({ @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 { ForceAnnotationDialogComponent } from '../dialogs/force-redaction-dialog/force-annotation-dialog.component';
import { HighlightActionDialogComponent } from '../dialogs/highlight-action-dialog/highlight-action-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 { 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 = type DialogType = 'confirm' | 'documentInfo' | 'changeLegalBasis' | 'forceAnnotation' | 'manualAnnotation' | 'highlightAction';
| 'confirm'
| 'documentInfo'
| 'componentLog'
| 'changeLegalBasis'
| 'forceAnnotation'
| 'manualAnnotation'
| 'highlightAction';
@Injectable() @Injectable()
export class FilePreviewDialogService extends DialogService<DialogType> { export class FilePreviewDialogService extends DialogService<DialogType> {
@ -41,10 +33,6 @@ export class FilePreviewDialogService extends DialogService<DialogType> {
highlightAction: { highlightAction: {
component: HighlightActionDialogComponent, component: HighlightActionDialogComponent,
}, },
componentLog: {
component: StructuredComponentManagementDialogComponent,
dialogConfig: { width: '90vw' },
},
}; };
constructor(protected readonly _dialog: MatDialog) { constructor(protected readonly _dialog: MatDialog) {

View File

@ -133,10 +133,12 @@ export class PdfProxyService {
} }
configureElements() { configureElements() {
const hexColor = this._dictionariesMapService.get(this._state.dossierTemplateId, 'manual').hexColor; const hexColor = this._dictionariesMapService.get(this._state.dossierTemplateId, 'manual')?.hexColor;
if (hexColor) {
const color = this._annotationDrawService.convertColor(hexColor); const color = this._annotationDrawService.convertColor(hexColor);
this._documentViewer.setRectangleToolStyles(color); this._documentViewer.setRectangleToolStyles(color);
} }
}
#configureRectangleAnnotationPopup(annotation: Annotation) { #configureRectangleAnnotationPopup(annotation: Annotation) {
if (!this._pdf.isCompareMode() || annotation.getPageNumber() % 2 === 1) { if (!this._pdf.isCompareMode() || annotation.getPageNumber() % 2 === 1) {

View File

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