From 7e93e2f0f2a0ab1d4b409912c724d540647e923c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Tue, 15 Mar 2022 17:29:34 +0200 Subject: [PATCH] Fixed rebase --- .../app/modules/admin/admin-routing.module.ts | 2 +- ...dossier-states-listing-screen.component.ts | 6 +- .../src/app/modules/archive/archive.module.ts | 2 +- .../dossier-details-stats.component.ts | 10 +- .../dossier-details-stats.component.ts | 41 - .../dossiers-listing-details.component.ts | 88 --- .../dossiers-listing/config.service.ts | 266 ------- .../user-management.component.ts | 115 --- .../file-preview-screen.component.ts | 703 ------------------ .../services/annotation-actions.service.ts | 600 --------------- .../src/app/modules/shared/shared.module.ts | 4 +- 11 files changed, 7 insertions(+), 1830 deletions(-) delete mode 100644 apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/dossier-details-stats/dossier-details-stats.component.ts delete mode 100644 apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-details/dossiers-listing-details.component.ts delete mode 100644 apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/config.service.ts delete mode 100644 apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/user-management/user-management.component.ts delete mode 100644 apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts delete mode 100644 apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/annotation-actions.service.ts diff --git a/apps/red-ui/src/app/modules/admin/admin-routing.module.ts b/apps/red-ui/src/app/modules/admin/admin-routing.module.ts index 5065145c8..785351d42 100644 --- a/apps/red-ui/src/app/modules/admin/admin-routing.module.ts +++ b/apps/red-ui/src/app/modules/admin/admin-routing.module.ts @@ -22,7 +22,7 @@ import { DICTIONARY_TYPE, DOSSIER_TEMPLATE_ID } from '@utils/constants'; import { DossierTemplateExistsGuard } from '@guards/dossier-template-exists.guard'; import { DictionaryExistsGuard } from '@guards/dictionary-exists.guard'; import { DossierStatesListingScreenComponent } from './screens/dossier-states-listing/dossier-states-listing-screen.component'; -import { DossiersGuard } from '../../guards/dossiers.guard'; +import { DossiersGuard } from '@guards/dossiers.guard'; import { ACTIVE_DOSSIERS_SERVICE } from '../../tokens'; const routes: Routes = [ diff --git a/apps/red-ui/src/app/modules/admin/screens/dossier-states-listing/dossier-states-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/dossier-states-listing/dossier-states-listing-screen.component.ts index 1c30861ff..3d7ff95bd 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dossier-states-listing/dossier-states-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/dossier-states-listing/dossier-states-listing-screen.component.ts @@ -9,14 +9,12 @@ import { } from '@iqser/common-ui'; import { DossierState, IDossierState } from '@red/domain'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; -import { DossierStateService } from '@services/entity-services/dossier-state.service'; -import { firstValueFrom } from 'rxjs'; +import { firstValueFrom, Observable } from 'rxjs'; import { AdminDialogService } from '../../services/admin-dialog.service'; import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component'; import { ActivatedRoute } from '@angular/router'; import { DossierStatesMapService } from '@services/entity-services/dossier-states-map.service'; -import { tap } from 'rxjs/operators'; +import { map, tap } from 'rxjs/operators'; import { PermissionsService } from '@services/permissions.service'; import { DossierStatesService } from '@services/entity-services/dossier-states.service'; diff --git a/apps/red-ui/src/app/modules/archive/archive.module.ts b/apps/red-ui/src/app/modules/archive/archive.module.ts index d02cfba49..72a80db27 100644 --- a/apps/red-ui/src/app/modules/archive/archive.module.ts +++ b/apps/red-ui/src/app/modules/archive/archive.module.ts @@ -3,8 +3,8 @@ import { CommonModule } from '@angular/common'; import { ArchivedDossiersScreenComponent } from './screens/archived-dossiers-screen/archived-dossiers-screen.component'; import { ArchiveRoutingModule } from './archive-routing.module'; import { TableItemComponent } from './components/table-item/table-item.component'; -import { ConfigService } from '@services/config.service'; import { SharedModule } from '@shared/shared.module'; +import { ConfigService } from './services/config.service'; const components = [TableItemComponent]; const screens = [ArchivedDossiersScreenComponent]; diff --git a/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details-stats/dossier-details-stats.component.ts b/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details-stats/dossier-details-stats.component.ts index 51624e53a..171927d46 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details-stats/dossier-details-stats.component.ts +++ b/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details-stats/dossier-details-stats.component.ts @@ -1,12 +1,10 @@ import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; import { Dossier, DossierAttributeWithValue, DossierStats } from '@red/domain'; -import { DossiersDialogService } from '../../../dossier/services/dossiers-dialog.service'; import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; import { FilesService } from '@services/entity-services/files.service'; import { firstValueFrom, Observable } from 'rxjs'; -import { map, switchMap } from 'rxjs/operators'; import { DossierStatsService } from '@services/dossiers/dossier-stats.service'; -import { FilesMapService } from '@services/entity-services/files-map.service'; +import { DossiersDialogService } from '../../../dossier/services/dossiers-dialog.service'; @Component({ selector: 'redaction-dossier-details-stats', @@ -20,7 +18,6 @@ export class DossierDetailsStatsComponent implements OnInit { attributesExpanded = false; dossierTemplateName: string; - deletedFilesCount$: Observable; dossierStats$: Observable; constructor( @@ -28,15 +25,10 @@ export class DossierDetailsStatsComponent implements OnInit { private readonly _dialogService: DossiersDialogService, private readonly _filesService: FilesService, private readonly _dossierStatsService: DossierStatsService, - private readonly _filesMapService: FilesMapService, ) {} ngOnInit() { this.dossierStats$ = this._dossierStatsService.watch$(this.dossier.dossierId); - this.deletedFilesCount$ = this._filesMapService.get$(this.dossier.dossierId).pipe( - switchMap(() => this._filesService.getDeletedFilesFor(this.dossier.id)), - map(files => files.length), - ); this.dossierTemplateName = this._dossierTemplatesService.find(this.dossier.dossierTemplateId)?.name || '-'; } diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/dossier-details-stats/dossier-details-stats.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/dossier-details-stats/dossier-details-stats.component.ts deleted file mode 100644 index 43a969833..000000000 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/dossier-details-stats/dossier-details-stats.component.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; -import { Dossier, DossierAttributeWithValue, DossierStats } from '@red/domain'; -import { DossiersDialogService } from '../../../../services/dossiers-dialog.service'; -import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; -import { FilesService } from '@services/entity-services/files.service'; -import { firstValueFrom, Observable } from 'rxjs'; -import { DossierStatsService } from '@services/dossiers/dossier-stats.service'; - -@Component({ - selector: 'redaction-dossier-details-stats', - templateUrl: './dossier-details-stats.component.html', - styleUrls: ['./dossier-details-stats.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class DossierDetailsStatsComponent implements OnInit { - @Input() dossierAttributes: DossierAttributeWithValue[]; - @Input() dossier: Dossier; - - attributesExpanded = false; - dossierTemplateName: string; - dossierStats$: Observable; - - constructor( - private readonly _dossierTemplatesService: DossierTemplatesService, - private readonly _dialogService: DossiersDialogService, - private readonly _filesService: FilesService, - private readonly _dossierStatsService: DossierStatsService, - ) {} - - ngOnInit() { - this.dossierStats$ = this._dossierStatsService.watch$(this.dossier.dossierId); - this.dossierTemplateName = this._dossierTemplatesService.find(this.dossier.dossierTemplateId)?.name || '-'; - } - - openEditDossierDialog(section: string): void { - const data = { dossierId: this.dossier.dossierId, section }; - this._dialogService.openDialog('editDossier', null, data, async () => { - await firstValueFrom(this._filesService.loadAll(this.dossier.dossierId, this.dossier.routerPath)); - }); - } -} diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-details/dossiers-listing-details.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-details/dossiers-listing-details.component.ts deleted file mode 100644 index 72d2d8988..000000000 --- a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-details/dossiers-listing-details.component.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component'; -import { FilterService, mapEach } from '@iqser/common-ui'; -import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; -import { combineLatest, Observable } from 'rxjs'; -import { DossierStats, FileCountPerWorkflowStatus, StatusSorter } from '@red/domain'; -import { workflowFileStatusTranslations } from '../../../../../../translations/file-status-translations'; -import { TranslateChartService } from '@services/translate-chart.service'; -import { filter, map, switchMap } from 'rxjs/operators'; -import { DossierStatsService } from '@services/dossiers/dossier-stats.service'; -import { DossierStateService } from '@services/entity-services/dossier-state.service'; -import { TranslateService } from '@ngx-translate/core'; - -@Component({ - selector: 'redaction-dossiers-listing-details', - templateUrl: './dossiers-listing-details.component.html', - styleUrls: ['./dossiers-listing-details.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class DossiersListingDetailsComponent { - readonly documentsChartData$: Observable; - readonly dossiersChartData$: Observable; - - constructor( - readonly filterService: FilterService, - readonly activeDossiersService: ActiveDossiersService, - private readonly _dossierStatsMap: DossierStatsService, - private readonly _translateChartService: TranslateChartService, - private readonly _dossierStateService: DossierStateService, - private readonly _translateService: TranslateService, - ) { - this.documentsChartData$ = this.activeDossiersService.all$.pipe( - mapEach(dossier => _dossierStatsMap.watch$(dossier.dossierId)), - switchMap(stats$ => combineLatest(stats$)), - filter(stats => !stats.some(s => s === undefined)), - map(stats => this._toChartData(stats)), - ); - - this.dossiersChartData$ = this.activeDossiersService.all$.pipe(map(() => this._toDossierChartData())); - } - - private _toDossierChartData(): DoughnutChartConfig[] { - this._dossierStateService.all.forEach( - state => (state.dossierCount = this.activeDossiersService.getCountWithState(state.dossierStatusId)), - ); - const configArray: DoughnutChartConfig[] = [ - ...this._dossierStateService.all - .reduce((acc, { color, dossierCount, name }) => { - const key = name + '-' + color; - const item = acc.get(key) ?? Object.assign({}, { value: 0, label: name, color: color }); - - return acc.set(key, { ...item, value: item.value + dossierCount }); - }, new Map()) - .values(), - ]; - - const notAssignedLength = this.activeDossiersService.all.length - configArray.map(v => v.value).reduce((acc, val) => acc + val, 0); - configArray.push({ - value: notAssignedLength, - label: this._translateService.instant('edit-dossier-dialog.general-info.form.dossier-status.placeholder'), - color: '#E2E4E9', - }); - - return configArray; - } - - private _toChartData(stats: DossierStats[]) { - const chartData: FileCountPerWorkflowStatus = {}; - stats.forEach(stat => { - const statuses: FileCountPerWorkflowStatus = stat.fileCountPerWorkflowStatus; - Object.keys(statuses).forEach(status => { - chartData[status] = chartData[status] ? (chartData[status] as number) + (statuses[status] as number) : statuses[status]; - }); - }); - - const documentsChartData = Object.keys(chartData).map( - status => - ({ - value: chartData[status], - color: status, - label: workflowFileStatusTranslations[status], - key: status, - } as DoughnutChartConfig), - ); - documentsChartData.sort((a, b) => StatusSorter.byStatus(a.key, b.key)); - return this._translateChartService.translateStatus(documentsChartData); - } -} diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/config.service.ts b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/config.service.ts deleted file mode 100644 index d4d510284..000000000 --- a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/config.service.ts +++ /dev/null @@ -1,266 +0,0 @@ -import { Injectable, TemplateRef } from '@angular/core'; -import { ButtonConfig, IFilterGroup, INestedFilter, keyChecker, NestedFilter, TableColumnConfig } from '@iqser/common-ui'; -import { Dossier, StatusSorter, User } from '@red/domain'; -import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { TranslateService } from '@ngx-translate/core'; -import { UserPreferenceService } from '@services/user-preference.service'; -import { UserService } from '@services/user.service'; -import { workflowFileStatusTranslations } from '../../../../translations/file-status-translations'; -import { dossierMemberChecker, dossierStateChecker, dossierTemplateChecker, RedactionFilterSorter } from '@utils/index'; -import { workloadTranslations } from '../../translations/workload-translations'; -import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; -import { DossierStatsService } from '@services/dossiers/dossier-stats.service'; -import { DossierStateService } from '@services/entity-services/dossier-state.service'; - -@Injectable() -export class ConfigService { - constructor( - private readonly _translateService: TranslateService, - private readonly _userPreferenceService: UserPreferenceService, - private readonly _userService: UserService, - private readonly _dossierTemplatesService: DossierTemplatesService, - private readonly _dossierStatsService: DossierStatsService, - private readonly _dossierStateService: DossierStateService, - ) {} - - get tableConfig(): TableColumnConfig[] { - return [ - { label: _('dossier-listing.table-col-names.name'), sortByKey: 'searchKey', width: '2fr' }, - // { label: _('dossier-listing.table-col-names.last-modified') }, - { label: _('dossier-listing.table-col-names.needs-work') }, - { label: _('dossier-listing.table-col-names.owner'), class: 'user-column' }, - { label: _('dossier-listing.table-col-names.documents-status'), class: 'flex-end', width: 'auto' }, - { label: _('dossier-listing.table-col-names.dossier-status'), class: 'flex-end' }, - ]; - } - - get _currentUser(): User { - return this._userService.currentUser; - } - - _myDossiersChecker = (dw: Dossier) => dw.ownerId === this._currentUser.id; - - _toApproveChecker = (dw: Dossier) => dw.approverIds.includes(this._currentUser.id); - - _toReviewChecker = (dw: Dossier) => dw.memberIds.includes(this._currentUser.id); - - _otherChecker = (dw: Dossier) => !dw.memberIds.includes(this._currentUser.id); - - buttonsConfig(addDossier: () => void): ButtonConfig[] { - return [ - { - label: _('dossier-listing.add-new'), - action: addDossier, - hide: !this._currentUser.isManager, - icon: 'iqser:plus', - type: 'primary', - helpModeKey: 'new_dossier_button', - }, - ]; - } - - filterGroups(entities: Dossier[], needsWorkFilterTemplate: TemplateRef) { - const allDistinctFileStatus = new Set(); - const allDistinctPeople = new Set(); - const allDistinctNeedsWork = new Set(); - const allDistinctDossierTemplates = new Set(); - const allDistinctDossierStates = new Set(); - - const filterGroups: IFilterGroup[] = []; - - entities?.forEach(entry => { - entry.memberIds.forEach(f => allDistinctPeople.add(f)); - allDistinctDossierTemplates.add(entry.dossierTemplateId); - if (entry.dossierStatusId) { - allDistinctDossierStates.add(entry.dossierStatusId); - } - const stats = this._dossierStatsService.get(entry.dossierId); - - if (!stats) { - return; - } - - Object.keys(stats?.fileCountPerWorkflowStatus).forEach(status => allDistinctFileStatus.add(status)); - - if (stats.hasHintsNoRedactionsFilePresent) { - allDistinctNeedsWork.add('hint'); - } - if (stats.hasRedactionsFilePresent) { - allDistinctNeedsWork.add('redaction'); - } - if (stats.hasSuggestionsFilePresent) { - allDistinctNeedsWork.add('suggestion'); - } - if (stats.hasNoFlagsFilePresent) { - allDistinctNeedsWork.add('none'); - } - }); - - const dossierStatesFilters = [...allDistinctDossierStates].map( - id => - new NestedFilter({ - id: id, - label: this._dossierStateService.find(id).name, - }), - ); - - filterGroups.push({ - slug: 'dossierStatesFilters', - label: this._translateService.instant('filters.dossier-status'), - icon: 'red:status', - hide: dossierStatesFilters.length <= 1, - filters: dossierStatesFilters, - checker: dossierStateChecker, - }); - - const statusFilters = [...allDistinctFileStatus].map( - status => - new NestedFilter({ - id: status, - label: this._translateService.instant(workflowFileStatusTranslations[status]), - }), - ); - - filterGroups.push({ - slug: 'statusFilters', - label: this._translateService.instant('filters.documents-status'), - icon: 'red:status', - filters: statusFilters.sort((a, b) => StatusSorter[a.id] - StatusSorter[b.id]), - checker: (dossier: Dossier, filter: INestedFilter) => this._dossierStatusChecker(dossier, filter), - }); - - const peopleFilters = [...allDistinctPeople].map( - userId => - new NestedFilter({ - id: userId, - label: this._userService.getNameForId(userId), - }), - ); - - filterGroups.push({ - slug: 'peopleFilters', - label: this._translateService.instant('filters.people'), - icon: 'red:user', - filters: peopleFilters, - checker: dossierMemberChecker, - }); - - const needsWorkFilters = [...allDistinctNeedsWork].map( - type => - new NestedFilter({ - id: type, - label: workloadTranslations[type], - }), - ); - - filterGroups.push({ - slug: 'needsWorkFilters', - label: this._translateService.instant('filters.needs-work'), - icon: 'red:needs-work', - filterTemplate: needsWorkFilterTemplate, - filters: needsWorkFilters.sort((a, b) => RedactionFilterSorter[a.id] - RedactionFilterSorter[b.id]), - checker: (dossier: Dossier, filter: INestedFilter) => this._annotationFilterChecker(dossier, filter), - matchAll: true, - }); - - const dossierTemplateFilters = [...allDistinctDossierTemplates].map( - id => - new NestedFilter({ - id: id, - label: this._dossierTemplatesService.find(id)?.name || '-', - }), - ); - - filterGroups.push({ - slug: 'dossierTemplateFilters', - label: this._translateService.instant('filters.dossier-templates'), - icon: 'red:template', - hide: dossierTemplateFilters.length <= 1, - filters: dossierTemplateFilters, - checker: dossierTemplateChecker, - }); - - filterGroups.push({ - slug: 'quickFilters', - filters: this._quickFilters(entities), - checker: (dw: Dossier, filter: NestedFilter) => filter.checked && filter.checker(dw), - }); - - const dossierFilters = entities.map( - dossier => - new NestedFilter({ - id: dossier.dossierName, - label: dossier.dossierName, - }), - ); - filterGroups.push({ - slug: 'dossierNameFilter', - label: this._translateService.instant('dossier-listing.filters.label'), - icon: 'red:folder', - filters: dossierFilters, - filterceptionPlaceholder: this._translateService.instant('dossier-listing.filters.search'), - checker: keyChecker('dossierName'), - }); - - return filterGroups; - } - - private _quickFilters(entities: Dossier[]): NestedFilter[] { - const myDossiersLabel = this._translateService.instant('dossier-listing.quick-filters.my-dossiers'); - const filters = [ - { - id: 'my-dossiers', - label: myDossiersLabel, - checker: this._myDossiersChecker, - disabled: entities.filter(this._myDossiersChecker).length === 0, - helpModeKey: 'dossiers_quickfilter_my_dossiers', - }, - { - id: 'to-approve', - label: this._translateService.instant('dossier-listing.quick-filters.to-approve'), - checker: this._toApproveChecker, - disabled: entities.filter(this._toApproveChecker).length === 0, - }, - { - id: 'to-review', - label: this._translateService.instant('dossier-listing.quick-filters.to-review'), - checker: this._toReviewChecker, - disabled: entities.filter(this._toReviewChecker).length === 0, - }, - { - id: 'other', - label: this._translateService.instant('dossier-listing.quick-filters.other'), - checker: this._otherChecker, - disabled: entities.filter(this._otherChecker).length === 0, - }, - ].map(filter => new NestedFilter(filter)); - - return filters.filter(f => f.label === myDossiersLabel || this._userPreferenceService.areDevFeaturesEnabled); - } - - private _dossierStatusChecker = (dossier: Dossier, filter: INestedFilter) => { - const stats = this._dossierStatsService.get(dossier.dossierId); - return stats?.fileCountPerWorkflowStatus[filter.id]; - }; - - private _annotationFilterChecker = (dossier: Dossier, filter: INestedFilter) => { - const stats = this._dossierStatsService.get(dossier.dossierId); - switch (filter.id) { - // case 'analysis': { - // return stats.reanalysisRequired; - // } - case 'suggestion': { - return stats.hasSuggestionsFilePresent; - } - case 'redaction': { - return stats.hasRedactionsFilePresent; - } - case 'hint': { - return stats.hasHintsNoRedactionsFilePresent; - } - case 'none': { - return stats.hasNoFlagsFilePresent; - } - } - }; -} diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/user-management/user-management.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/user-management/user-management.component.ts deleted file mode 100644 index 3cd5b87fa..000000000 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/user-management/user-management.component.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Dossier, File, StatusBarConfigs, User } from '@red/domain'; -import { List, LoadingService, Toaster } from '@iqser/common-ui'; -import { PermissionsService } from '@services/permissions.service'; -import { FileAssignService } from '../../../../shared/services/file-assign.service'; -import { workflowFileStatusTranslations } from '../../../../../../translations/file-status-translations'; -import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { UserService } from '@services/user.service'; -import { FilesService } from '@services/entity-services/files.service'; -import { TranslateService } from '@ngx-translate/core'; -import { BehaviorSubject, combineLatest, firstValueFrom, Observable, switchMap } from 'rxjs'; -import { FilePreviewStateService } from '../../services/file-preview-state.service'; -import { distinctUntilChanged, map } from 'rxjs/operators'; -import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; - -@Component({ - selector: 'redaction-user-management', - templateUrl: './user-management.component.html', - styleUrls: ['./user-management.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class UserManagementComponent { - readonly translations = workflowFileStatusTranslations; - readonly statusBarConfig$: Observable; - readonly assignTooltip$: Observable; - readonly canAssignReviewer$: Observable; - readonly canAssignToSelf$: Observable; - readonly editingReviewer$ = new BehaviorSubject(false); - readonly canAssignOrUnassign$: Observable; - readonly canAssign$: Observable; - readonly usersOptions$: Observable; - private readonly _dossier$: Observable; - private readonly _canAssignUser$: Observable; - private readonly _canUnassignUser$: Observable; - - constructor( - readonly fileAssignService: FileAssignService, - readonly permissionsService: PermissionsService, - readonly userService: UserService, - readonly filesService: FilesService, - readonly toaster: Toaster, - readonly loadingService: LoadingService, - readonly translateService: TranslateService, - readonly stateService: FilePreviewStateService, - private readonly _activeDossiersService: ActiveDossiersService, - ) { - this._dossier$ = this.stateService.file$.pipe(switchMap(file => this._activeDossiersService.getEntityChanged$(file.dossierId))); - this.statusBarConfig$ = this.stateService.file$.pipe(map(file => [{ length: 1, color: file.workflowStatus }])); - this.assignTooltip$ = this.stateService.file$.pipe( - map(file => - file.isUnderApproval - ? this.translateService.instant(_('dossier-overview.assign-approver')) - : file.assignee - ? this.translateService.instant(_('file-preview.change-reviewer')) - : this.translateService.instant(_('file-preview.assign-reviewer')), - ), - ); - - this.canAssignToSelf$ = this.stateService.file$.pipe( - map(file => this.permissionsService.canAssignToSelf(file)), - distinctUntilChanged(), - ); - this._canAssignUser$ = this.stateService.file$.pipe( - map(file => this.permissionsService.canAssignUser(file)), - distinctUntilChanged(), - ); - this._canUnassignUser$ = this.stateService.file$.pipe( - map(file => this.permissionsService.canUnassignUser(file)), - distinctUntilChanged(), - ); - - this.canAssignOrUnassign$ = combineLatest([this._canAssignUser$, this._canUnassignUser$]).pipe( - map(([canAssignUser, canUnassignUser]) => canAssignUser || canUnassignUser), - distinctUntilChanged(), - ); - - this.canAssign$ = combineLatest([this.canAssignToSelf$, this.canAssignOrUnassign$]).pipe( - map(([canAssignToSelf, canAssignOrUnassign]) => canAssignToSelf || canAssignOrUnassign), - distinctUntilChanged(), - ); - - this.canAssignReviewer$ = combineLatest([this.stateService.file$, this._canAssignUser$, this._dossier$]).pipe( - map(([file, canAssignUser, dossier]) => !file.assignee && canAssignUser && dossier.hasReviewers), - distinctUntilChanged(), - ); - - this.usersOptions$ = combineLatest([this._canUnassignUser$, this.stateService.file$, this._dossier$]).pipe( - map(([canUnassignUser, file, dossier]) => { - const unassignUser = canUnassignUser ? [undefined] : []; - return file.isUnderApproval ? [...dossier.approverIds, ...unassignUser] : [...dossier.memberIds, ...unassignUser]; - }), - ); - } - - async assignReviewer(file: File, user: User | string) { - const assigneeId = typeof user === 'string' ? user : user?.id; - const reviewerName = this.userService.getNameForId(assigneeId); - - const { dossierId, filename } = file; - this.loadingService.start(); - - if (!assigneeId) { - await firstValueFrom(this.filesService.setUnassigned([file], dossierId)); - } else if (file.isNew || file.isUnderReview) { - await firstValueFrom(this.filesService.setReviewerFor([file], dossierId, assigneeId)); - } else if (file.isUnderApproval) { - await firstValueFrom(this.filesService.setUnderApprovalFor([file], dossierId, assigneeId)); - } - - this.loadingService.stop(); - - this.toaster.info(_('assignment.reviewer'), { params: { reviewerName, filename } }); - this.editingReviewer$.next(false); - } -} diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts deleted file mode 100644 index 5a80b46bf..000000000 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts +++ /dev/null @@ -1,703 +0,0 @@ -import { ChangeDetectorRef, Component, HostListener, NgZone, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core'; -import { ActivatedRoute, ActivatedRouteSnapshot, NavigationExtras, Router } from '@angular/router'; -import { Core } from '@pdftron/webviewer'; -import { - AutoUnsubscribe, - CircleButtonTypes, - CustomError, - Debounce, - ErrorService, - FilterService, - LoadingService, - NestedFilter, - OnAttach, - OnDetach, - processFilters, - shareDistinctLast, -} from '@iqser/common-ui'; -import { MatDialogRef, MatDialogState } from '@angular/material/dialog'; -import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper'; -import { AnnotationWrapper } from '@models/file/annotation.wrapper'; -import { ManualAnnotationResponse } from '@models/file/manual-annotation-response'; -import { AnnotationDrawService } from './services/annotation-draw.service'; -import { AnnotationProcessingService } from '../../services/annotation-processing.service'; -import { File, ViewMode } from '@red/domain'; -import { PermissionsService } from '@services/permissions.service'; -import { combineLatest, firstValueFrom, Observable, of, timer } from 'rxjs'; -import { UserPreferenceService } from '@services/user-preference.service'; -import { PdfViewerDataService } from '../../services/pdf-viewer-data.service'; -import { download } from '@utils/file-download-utils'; -import { FileWorkloadComponent } from './components/file-workload/file-workload.component'; -import { DossiersDialogService } from '../../services/dossiers-dialog.service'; -import { clearStamps, stampPDFPage } from '@utils/page-stamper'; -import { TranslateService } from '@ngx-translate/core'; -import { handleFilterDelta } from '@utils/filter-utils'; -import { FilesService } from '@services/entity-services/files.service'; -import { FileManagementService } from '@services/entity-services/file-management.service'; -import { catchError, map, switchMap, tap } from 'rxjs/operators'; -import { FilesMapService } from '@services/entity-services/files-map.service'; -import { WatermarkService } from '@shared/services/watermark.service'; -import { ExcludedPagesService } from './services/excluded-pages.service'; -import { ViewModeService } from './services/view-mode.service'; -import { MultiSelectService } from './services/multi-select.service'; -import { DocumentInfoService } from './services/document-info.service'; -import { ReanalysisService } from '../../../../services/reanalysis.service'; -import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { SkippedService } from './services/skipped.service'; -import { FilePreviewStateService } from './services/file-preview-state.service'; -import { FileDataModel } from '../../../../models/file/file-data.model'; -import { filePreviewScreenProviders } from './file-preview-providers'; -import { ManualAnnotationService } from '../../services/manual-annotation.service'; -import { DossiersService } from '@services/dossiers/dossiers.service'; -import { PageRotationService } from './services/page-rotation.service'; -import { ComponentCanDeactivate } from '../../../../guards/can-deactivate.guard'; -import { PdfViewer } from './services/pdf-viewer.service'; -import Annotation = Core.Annotations.Annotation; -import PDFNet = Core.PDFNet; - -const ALL_HOTKEY_ARRAY = ['Escape', 'F', 'f', 'ArrowUp', 'ArrowDown']; - -@Component({ - templateUrl: './file-preview-screen.component.html', - styleUrls: ['./file-preview-screen.component.scss'], - providers: filePreviewScreenProviders, -}) -export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnInit, OnDestroy, OnAttach, OnDetach, ComponentCanDeactivate { - readonly circleButtonTypes = CircleButtonTypes; - - dialogRef: MatDialogRef; - fullScreen = false; - selectedAnnotations: AnnotationWrapper[] = []; - displayPdfViewer = false; - activeViewerPage: number = null; - readonly canPerformAnnotationActions$: Observable; - readonly fileId = this.stateService.fileId; - readonly dossierId = this.stateService.dossierId; - readonly file$ = this.stateService.file$.pipe(tap(file => this._fileUpdated(file))); - ready = false; - private _lastPage: string; - - @ViewChild('fileWorkloadComponent') private readonly _workloadComponent: FileWorkloadComponent; - @ViewChild('annotationFilterTemplate', { - read: TemplateRef, - static: false, - }) - private readonly _filterTemplate: TemplateRef; - - constructor( - readonly permissionsService: PermissionsService, - readonly userPreferenceService: UserPreferenceService, - readonly stateService: FilePreviewStateService, - private readonly _watermarkService: WatermarkService, - private readonly _changeDetectorRef: ChangeDetectorRef, - private readonly _activatedRoute: ActivatedRoute, - private readonly _dialogService: DossiersDialogService, - private readonly _router: Router, - private readonly _annotationProcessingService: AnnotationProcessingService, - private readonly _annotationDrawService: AnnotationDrawService, - private readonly _pdfViewerDataService: PdfViewerDataService, - private readonly _filesService: FilesService, - private readonly _ngZone: NgZone, - private readonly _fileManagementService: FileManagementService, - private readonly _loadingService: LoadingService, - private readonly _filterService: FilterService, - private readonly _translateService: TranslateService, - private readonly _filesMapService: FilesMapService, - private readonly _dossiersService: DossiersService, - private readonly _reanalysisService: ReanalysisService, - private readonly _errorService: ErrorService, - private readonly _pageRotationService: PageRotationService, - private readonly _skippedService: SkippedService, - private readonly _pdf: PdfViewer, - private readonly _manualAnnotationService: ManualAnnotationService, - readonly excludedPagesService: ExcludedPagesService, - private readonly _viewModeService: ViewModeService, - readonly multiSelectService: MultiSelectService, - readonly documentInfoService: DocumentInfoService, - ) { - super(); - this.canPerformAnnotationActions$ = this._canPerformAnnotationActions$; - - document.documentElement.addEventListener('fullscreenchange', () => { - if (!document.fullscreenElement) { - this.fullScreen = false; - } - }); - } - - get changed() { - return this._pageRotationService.hasRotations(); - } - - get visibleAnnotations(): AnnotationWrapper[] { - return this._fileData ? this._fileData.getVisibleAnnotations(this._viewModeService.viewMode) : []; - } - - get allAnnotations(): AnnotationWrapper[] { - return this._fileData ? this._fileData.allAnnotations : []; - } - - private get _fileData(): FileDataModel { - return this.stateService.fileData; - } - - private get _canPerformAnnotationActions$() { - const viewMode$ = this._viewModeService.viewMode$.pipe(tap(() => this.#deactivateMultiSelect())); - - return combineLatest([this.stateService.file$, viewMode$, this._viewModeService.compareMode$]).pipe( - map(([file, viewMode]) => this.permissionsService.canPerformAnnotationActions(file) && viewMode === 'STANDARD'), - shareDistinctLast(), - ); - } - - async save() { - await this._pageRotationService.applyRotation(); - } - - async updateViewMode(): Promise { - if (!this._pdf.ready) { - return; - } - - const textHighlightAnnotationIds = this._fileData.textHighlightAnnotations.map(a => a.id); - const textHighlightAnnotations = this._pdf.getAnnotations((a: Core.Annotations.Annotation) => - textHighlightAnnotationIds.includes(a.Id), - ); - - this._pdf.deleteAnnotations(textHighlightAnnotations); - - const ocrAnnotationIds = this._fileData.allAnnotations.filter(a => a.isOCR).map(a => a.id); - const annotations = this._pdf.getAnnotations(a => a.getCustomData('redact-manager')); - const redactions = annotations.filter(a => a.getCustomData('redaction')); - - switch (this._viewModeService.viewMode) { - case 'STANDARD': { - this._setAnnotationsColor(redactions, 'annotationColor'); - const standardEntries = annotations - .filter(a => a.getCustomData('changeLogRemoved') === 'false') - .filter(a => !ocrAnnotationIds.includes(a.Id)); - const nonStandardEntries = annotations.filter(a => a.getCustomData('changeLogRemoved') === 'true'); - this._setAnnotationsOpacity(standardEntries, true); - this._pdf.showAnnotations(standardEntries); - this._pdf.hideAnnotations(nonStandardEntries); - break; - } - case 'DELTA': { - const changeLogEntries = annotations.filter(a => a.getCustomData('changeLog') === 'true'); - const nonChangeLogEntries = annotations.filter(a => a.getCustomData('changeLog') === 'false'); - this._setAnnotationsColor(redactions, 'annotationColor'); - this._setAnnotationsOpacity(changeLogEntries, true); - this._pdf.showAnnotations(changeLogEntries); - this._pdf.hideAnnotations(nonChangeLogEntries); - break; - } - case 'REDACTED': { - const nonRedactionEntries = annotations.filter(a => a.getCustomData('redaction') === 'false'); - this._setAnnotationsOpacity(redactions); - this._setAnnotationsColor(redactions, 'redactionColor'); - this._pdf.showAnnotations(redactions); - this._pdf.hideAnnotations(nonRedactionEntries); - break; - } - case 'TEXT_HIGHLIGHTS': { - this._loadingService.start(); - const textHighlights = await firstValueFrom(this._pdfViewerDataService.loadTextHighlightsFor(this.dossierId, this.fileId)); - this._pdf.hideAnnotations(annotations); - this._fileData.textHighlights = textHighlights; - await this._annotationDrawService.drawAnnotations(this._fileData.textHighlightAnnotations); - this._loadingService.stop(); - } - } - - await this._stampPDF(); - this.rebuildFilters(); - } - - ngOnDetach(): void { - this._pageRotationService.clearRotations(); - this.displayPdfViewer = false; - super.ngOnDetach(); - this._changeDetectorRef.markForCheck(); - } - - async ngOnAttach(previousRoute: ActivatedRouteSnapshot): Promise { - const file = await this.stateService.file; - if (!file.canBeOpened) { - return this._router.navigate([this._dossiersService.find(this.dossierId)?.routerLink]); - } - this._viewModeService.compareMode = false; - this._viewModeService.switchToStandard(); - - await this.ngOnInit(); - this._lastPage = previousRoute.queryParams.page; - this._changeDetectorRef.markForCheck(); - } - - async ngOnInit(): Promise { - this.ready = false; - this._loadingService.start(); - await this.userPreferenceService.saveLastOpenedFileForDossier(this.dossierId, this.fileId); - this._subscribeToFileUpdates(); - - const file = await this.stateService.file; - if (file?.analysisRequired && !file.excludedFromAutomaticAnalysis) { - const reanalyzeFiles = this._reanalysisService.reanalyzeFilesForDossier([file], this.dossierId, { force: true }); - await firstValueFrom(reanalyzeFiles); - } - - this.displayPdfViewer = true; - } - - rebuildFilters(deletePreviousAnnotations = false): void { - const startTime = new Date().getTime(); - if (deletePreviousAnnotations) { - this._pdf.deleteAnnotations(); - - console.log(`[REDACTION] Delete previous annotations time: ${new Date().getTime() - startTime} ms`); - } - const processStartTime = new Date().getTime(); - - const annotationFilters = this._annotationProcessingService.getAnnotationFilter(this.visibleAnnotations); - const primaryFilters = this._filterService.getGroup('primaryFilters')?.filters; - this._filterService.addFilterGroup({ - slug: 'primaryFilters', - filterTemplate: this._filterTemplate, - filters: processFilters(primaryFilters, annotationFilters), - }); - const secondaryFilters = this._filterService.getGroup('secondaryFilters')?.filters; - this._filterService.addFilterGroup({ - slug: 'secondaryFilters', - filterTemplate: this._filterTemplate, - filters: processFilters(secondaryFilters, AnnotationProcessingService.secondaryAnnotationFilters(this._fileData?.viewedPages)), - }); - console.log(`[REDACTION] Process time: ${new Date().getTime() - processStartTime} ms`); - console.log(`[REDACTION] Filter rebuild time: ${new Date().getTime() - startTime}`); - } - - handleAnnotationSelected(annotationIds: string[]) { - this.selectedAnnotations = annotationIds - .map(id => this.visibleAnnotations.find(annotationWrapper => annotationWrapper.id === id)) - .filter(ann => ann !== undefined); - if (this.selectedAnnotations.length > 1) { - this.multiSelectService.activate(); - } - this._workloadComponent.scrollToSelectedAnnotation(); - this._changeDetectorRef.markForCheck(); - } - - @Debounce(10) - selectAnnotations(annotations?: AnnotationWrapper[]) { - if (annotations) { - const annotationsToSelect = this.multiSelectService.isActive ? [...this.selectedAnnotations, ...annotations] : annotations; - this._pdf.selectAnnotations(annotationsToSelect, this.multiSelectService.isActive); - } else { - this._pdf.deselectAllAnnotations(); - } - } - - deselectAnnotations(annotations: AnnotationWrapper[]) { - this._pdf.deselectAnnotations(annotations); - } - - selectPage(pageNumber: number) { - this._pdf.navigateToPage(pageNumber); - this._workloadComponent?.scrollAnnotationsToPage(pageNumber, 'always'); - this._lastPage = pageNumber.toString(); - } - - openManualAnnotationDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) { - this._ngZone.run(() => { - this.dialogRef = this._dialogService.openDialog( - 'manualAnnotation', - null, - { manualRedactionEntryWrapper, dossierId: this.dossierId }, - async (entryWrapper: ManualRedactionEntryWrapper) => { - const addAnnotation$ = this._manualAnnotationService - .addAnnotation(entryWrapper.manualRedactionEntry, this.dossierId, this.fileId) - .pipe(catchError(() => of(undefined))); - const addAnnotationResponse = await firstValueFrom(addAnnotation$); - const response = new ManualAnnotationResponse(entryWrapper, addAnnotationResponse); - - if (response?.annotationId) { - const annotation = this._pdf.annotationManager.getAnnotationById(response.manualRedactionEntryWrapper.rectId); - this._pdf.deleteAnnotations([annotation]); - const distinctPages = manualRedactionEntryWrapper.manualRedactionEntry.positions - .map(p => p.page) - .filter((item, pos, self) => self.indexOf(item) === pos); - for (const page of distinctPages) { - await this._reloadAnnotationsForPage(page); - } - await this.updateViewMode(); - } - }, - ); - }); - } - - toggleFullScreen() { - this.fullScreen = !this.fullScreen; - if (this.fullScreen) { - this._openFullScreen(); - } else { - this.closeFullScreen(); - } - } - - handleArrowEvent($event: KeyboardEvent): void { - if (['ArrowUp', 'ArrowDown'].includes($event.key)) { - if (this.selectedAnnotations.length === 1) { - this._workloadComponent.navigateAnnotations($event); - } - } - } - - @HostListener('window:keyup', ['$event']) - handleKeyEvent($event: KeyboardEvent) { - if (this._router.url.indexOf('/file/') < 0) { - return; - } - - if (!ALL_HOTKEY_ARRAY.includes($event.key) || this.dialogRef?.getState() === MatDialogState.OPEN) { - return; - } - - if (['Escape'].includes($event.key)) { - this.fullScreen = false; - this.closeFullScreen(); - this._changeDetectorRef.markForCheck(); - } - - if (['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; - } - } - - async viewerPageChanged($event: any) { - if (typeof $event !== 'number') { - return; - } - - this._scrollViews(); - this.multiSelectService.deactivate(); - - // Add current page in URL query params - const extras: NavigationExtras = { - queryParams: { page: $event }, - queryParamsHandling: 'merge', - replaceUrl: true, - }; - await this._router.navigate([], extras); - - this.activeViewerPage = this._pdf.currentPage; - this._changeDetectorRef.markForCheck(); - } - - @Debounce() - async viewerReady() { - this.ready = true; - this._pdf.ready = true; - - await this._reloadAnnotations(); - this._setExcludedPageStyles(); - - this._pdf.documentViewer.addEventListener('pageComplete', () => { - this._setExcludedPageStyles(); - }); - - // Go to initial page from query params - const pageNumber: string = this._lastPage || this._activatedRoute.snapshot.queryParams.page; - if (pageNumber) { - setTimeout(() => { - this.selectPage(parseInt(pageNumber, 10)); - this.activeViewerPage = this._pdf.currentPage; - this._scrollViews(); - this._changeDetectorRef.markForCheck(); - this._loadingService.stop(); - }); - } else { - this._loadingService.stop(); - } - this._changeDetectorRef.markForCheck(); - } - - async annotationsChangedByReviewAction(annotation?: AnnotationWrapper) { - this.multiSelectService.deactivate(); - const file = await this.stateService.file; - const fileReloaded = await firstValueFrom(this._filesService.reload(this.dossierId, file)); - if (!fileReloaded) { - await this._reloadAnnotationsForPage(annotation?.pageNumber ?? this.activeViewerPage); - } - } - - closeFullScreen() { - if (!!document.fullscreenElement && document.exitFullscreen) { - document.exitFullscreen().then(); - } - } - - async switchView(viewMode: ViewMode) { - this._viewModeService.viewMode = viewMode; - await this.updateViewMode(); - this._scrollViews(); - } - - async downloadOriginalFile(file: File) { - const originalFile = this._fileManagementService.downloadOriginalFile( - this.dossierId, - this.fileId, - 'response', - file.cacheIdentifier, - ); - download(await firstValueFrom(originalFile), file.filename); - } - - #deactivateMultiSelect(): void { - this.multiSelectService.deactivate(); - this._pdf.deselectAllAnnotations(); - this.handleAnnotationSelected([]); - } - - private _setExcludedPageStyles() { - const file = this._filesMapService.get(this.dossierId, this.fileId); - setTimeout(() => { - const iframeDoc = this._pdf.UI.iframeWindow.document; - const pageContainer = iframeDoc.getElementById(`pageWidgetContainer${this.activeViewerPage}`); - if (pageContainer) { - if (file.excludedPages.includes(this.activeViewerPage)) { - pageContainer.classList.add('excluded-page'); - } else { - pageContainer.classList.remove('excluded-page'); - } - } - }, 100); - } - - private async _stampPDF() { - const pdfDoc = await this._pdf.documentViewer.getDocument().getPDFDoc(); - const file = await this.stateService.file; - const allPages = [...Array(file.numberOfPages).keys()].map(page => page + 1); - - if (!pdfDoc || !this._pdf.ready) { - return; - } - - await clearStamps(pdfDoc, this._pdf.PDFNet, allPages); - - if (this._viewModeService.isRedacted) { - const dossier = await this.stateService.dossier; - if (dossier.watermarkPreviewEnabled) { - await this._stampPreview(pdfDoc, dossier.dossierTemplateId); - } - } else { - await this._stampExcludedPages(pdfDoc, file.excludedPages); - } - this._pdf.documentViewer.refreshAll(); - this._pdf.documentViewer.updateView([this.activeViewerPage], this.activeViewerPage); - this._changeDetectorRef.markForCheck(); - } - - private async _stampPreview(document: PDFNet.PDFDoc, dossierTemplateId: string) { - const watermark = await this._watermarkService.getWatermark(dossierTemplateId).toPromise(); - await stampPDFPage( - document, - this._pdf.PDFNet, - watermark.text, - watermark.fontSize, - watermark.fontType, - watermark.orientation, - watermark.opacity, - watermark.hexColor, - Array.from({ length: await document.getPageCount() }, (x, i) => i + 1), - ); - } - - private async _stampExcludedPages(document: PDFNet.PDFDoc, excludedPages: number[]) { - if (excludedPages && excludedPages.length > 0) { - await stampPDFPage( - document, - this._pdf.PDFNet, - this._translateService.instant('file-preview.excluded-from-redaction') as string, - 17, - 'courier', - 'TOP_LEFT', - 50, - '#dd4d50', - excludedPages, - ); - } - } - - private async _fileUpdated(file: File): Promise { - await this._loadFileData(file); - await this._reloadAnnotations(); - } - - private _subscribeToFileUpdates(): void { - this.addActiveScreenSubscription = timer(0, 5000) - .pipe( - switchMap(() => this.stateService.file$), - switchMap(file => this._filesService.reload(this.dossierId, file)), - ) - .subscribe(); - - this.addActiveScreenSubscription = this._dossiersService - .getEntityDeleted$(this.dossierId) - .pipe(tap(() => this._handleDeletedDossier())) - .subscribe(); - - this.addActiveScreenSubscription = this._filesMapService - .watchDeleted$(this.fileId) - .pipe(tap(() => this._handleDeletedFile())) - .subscribe(); - - this.addActiveScreenSubscription = this._skippedService.hideSkipped$ - .pipe(tap(hideSkipped => this._handleIgnoreAnnotationsDrawing(hideSkipped))) - .subscribe(); - } - - private _handleDeletedDossier(): void { - this._errorService.set( - new CustomError(_('error.deleted-entity.file-dossier.label'), _('error.deleted-entity.file-dossier.action'), 'iqser:expand'), - ); - } - - private _handleDeletedFile(): void { - this._errorService.set( - new CustomError(_('error.deleted-entity.file.label'), _('error.deleted-entity.file.action'), 'iqser:expand'), - ); - } - - private async _loadFileData(file: File): Promise { - if (!file || file.isError) { - const dossier = await this.stateService.dossier; - return this._router.navigate([dossier.routerLink]); - } - - if (file.isUnprocessed) { - return; - } - - this.stateService.fileData = await firstValueFrom(this._pdfViewerDataService.loadDataFor(file)); - } - - @Debounce(0) - private _scrollViews() { - this._workloadComponent?.scrollQuickNavigation(); - this._workloadComponent?.scrollAnnotations(); - } - - private async _reloadAnnotations() { - this._deleteAnnotations(); - await this._cleanupAndRedrawAnnotations(); - await this.updateViewMode(); - } - - private async _reloadAnnotationsForPage(page: number) { - const file = await this.stateService.file; - // if this action triggered a re-processing, - // we don't want to redraw for this page since they will get redrawn as soon as processing ends; - if (file.isProcessing) { - return; - } - - const currentPageAnnotations = this.visibleAnnotations.filter(a => a.pageNumber === page); - this._fileData.redactionLog = await firstValueFrom(this._pdfViewerDataService.loadRedactionLogFor(this.dossierId, this.fileId)); - - this._deleteAnnotations(currentPageAnnotations); - await this._cleanupAndRedrawAnnotations(annotation => annotation.pageNumber === page); - } - - private _deleteAnnotations(annotationsToDelete?: AnnotationWrapper[]) { - if (!this._pdf.ready) { - return; - } - - if (!annotationsToDelete) { - this._pdf.deleteAnnotations(); - } - annotationsToDelete?.forEach(annotation => { - this._findAndDeleteAnnotation(annotation.id); - }); - } - - private async _cleanupAndRedrawAnnotations(newAnnotationsFilter?: (annotation: AnnotationWrapper) => boolean) { - if (!this._pdf.ready) { - return; - } - - const currentFilters = this._filterService.getGroup('primaryFilters')?.filters || []; - this.rebuildFilters(); - - const startTime = new Date().getTime(); - const annotations = this._fileData.allAnnotations; - const newAnnotations = newAnnotationsFilter ? annotations.filter(newAnnotationsFilter) : annotations; - - if (currentFilters) { - this._handleDeltaAnnotationFilters(currentFilters, this.visibleAnnotations); - } - - await this._annotationDrawService.drawAnnotations(newAnnotations); - console.log(`[REDACTION] Annotations redraw time: ${new Date().getTime() - startTime} ms for ${newAnnotations.length} annotations`); - } - - private _handleDeltaAnnotationFilters(currentFilters: NestedFilter[], newAnnotations: AnnotationWrapper[]) { - const primaryFilterGroup = this._filterService.getGroup('primaryFilters'); - const primaryFilters = primaryFilterGroup.filters; - const secondaryFilters = this._filterService.getGroup('secondaryFilters').filters; - const hasAnyFilterSet = [...primaryFilters, ...secondaryFilters].find(f => f.checked || f.indeterminate); - - if (!hasAnyFilterSet) { - return; - } - - const newPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter(newAnnotations); - - handleFilterDelta(currentFilters, newPageSpecificFilters, primaryFilters); - this._filterService.addFilterGroup({ - ...primaryFilterGroup, - filters: primaryFilters, - }); - } - - private _findAndDeleteAnnotation(id: string) { - const viewerAnnotation = this._pdf.annotationManager.getAnnotationById(id); - if (viewerAnnotation) { - this._pdf.deleteAnnotations([viewerAnnotation]); - } - } - - private _openFullScreen() { - const documentElement = document.documentElement; - if (documentElement.requestFullscreen) { - documentElement.requestFullscreen().then(); - } - } - - private _handleIgnoreAnnotationsDrawing(hideSkipped: boolean): void { - const ignored = this._pdf.getAnnotations(a => a.getCustomData('skipped')); - if (hideSkipped) { - this._pdf.hideAnnotations(ignored); - } else { - this._pdf.showAnnotations(ignored); - } - } - - private _setAnnotationsOpacity(annotations: Annotation[], restoreToOriginal: boolean = false) { - annotations.forEach(annotation => { - annotation['Opacity'] = restoreToOriginal ? parseFloat(annotation.getCustomData('opacity')) : 1; - }); - } - - private _setAnnotationsColor(annotations: Annotation[], customData: string) { - annotations.forEach(annotation => { - const color = this._annotationDrawService.convertColor(annotation.getCustomData(customData)); - annotation['StrokeColor'] = color; - annotation['FillColor'] = color; - }); - } -} diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/annotation-actions.service.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/annotation-actions.service.ts deleted file mode 100644 index 6dc483356..000000000 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/annotation-actions.service.ts +++ /dev/null @@ -1,600 +0,0 @@ -import { EventEmitter, Inject, Injectable, NgZone } from '@angular/core'; -import { PermissionsService } from '@services/permissions.service'; -import { ManualAnnotationService } from '../../../services/manual-annotation.service'; -import { AnnotationWrapper } from '@models/file/annotation.wrapper'; -import { Observable } from 'rxjs'; -import { TranslateService } from '@ngx-translate/core'; -import { getFirstRelevantTextPart } from '@utils/functions'; -import { AnnotationPermissions } from '@models/file/annotation.permissions'; -import { DossiersDialogService } from '../../../services/dossiers-dialog.service'; -import { BASE_HREF } from '../../../../../tokens'; -import { UserService } from '@services/user.service'; -import { Core } from '@pdftron/webviewer'; -import { Dossier, IAddRedactionRequest, ILegalBasisChangeRequest, IRectangle, IResizeRequest } from '@red/domain'; -import { toPosition } from '../../../utils/pdf-calculation.utils'; -import { AnnotationDrawService } from './annotation-draw.service'; -import { translateQuads } from '@utils/pdf-coordinates'; -import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; -import { - AcceptRecommendationData, - AcceptRecommendationDialogComponent, - AcceptRecommendationReturnType, -} from '../dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component'; -import { defaultDialogConfig } from '@iqser/common-ui'; -import { filter } from 'rxjs/operators'; -import { MatDialog } from '@angular/material/dialog'; -import { FilePreviewStateService } from './file-preview-state.service'; -import { PdfViewer } from './pdf-viewer.service'; -import Annotation = Core.Annotations.Annotation; - -@Injectable() -export class AnnotationActionsService { - constructor( - @Inject(BASE_HREF) private readonly _baseHref: string, - private readonly _ngZone: NgZone, - private readonly _userService: UserService, - private readonly _permissionsService: PermissionsService, - private readonly _manualAnnotationService: ManualAnnotationService, - private readonly _translateService: TranslateService, - private readonly _dialogService: DossiersDialogService, - private readonly _dialog: MatDialog, - private readonly _pdf: PdfViewer, - private readonly _annotationDrawService: AnnotationDrawService, - private readonly _activeDossiersService: ActiveDossiersService, - private readonly _screenStateService: FilePreviewStateService, - ) {} - - private get _dossier(): Dossier { - return this._activeDossiersService.find(this._screenStateService.dossierId); - } - - acceptSuggestion($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter) { - $event?.stopPropagation(); - const { dossierId, fileId } = this._screenStateService; - annotations.forEach(annotation => { - this._processObsAndEmit( - this._manualAnnotationService.approve(annotation.id, dossierId, fileId, annotation.isModifyDictionary), - annotation, - annotationsChanged, - ); - }); - } - - rejectSuggestion($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter) { - $event?.stopPropagation(); - const { dossierId, fileId } = this._screenStateService; - annotations.forEach(annotation => { - this._processObsAndEmit( - this._manualAnnotationService.declineOrRemoveRequest(annotation, dossierId, fileId), - annotation, - annotationsChanged, - ); - }); - } - - forceAnnotation( - $event: MouseEvent, - annotations: AnnotationWrapper[], - annotationsChanged: EventEmitter, - hint: boolean = false, - ) { - const { dossierId, fileId } = this._screenStateService; - const data = { dossier: this._dossier, annotations, hint }; - this._dialogService.openDialog('forceAnnotation', $event, data, (request: ILegalBasisChangeRequest) => { - annotations.forEach(annotation => { - this._processObsAndEmit( - this._manualAnnotationService.force( - { - ...request, - annotationId: annotation.id, - }, - dossierId, - fileId, - ), - annotation, - annotationsChanged, - ); - }); - }); - } - - changeLegalBasis($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter) { - const { dossierId, fileId } = this._screenStateService; - this._dialogService.openDialog( - 'changeLegalBasis', - $event, - { annotations, dossier: this._dossier }, - (data: { comment: string; legalBasis: string; section: string; value: string }) => { - annotations.forEach(annotation => { - this._processObsAndEmit( - this._manualAnnotationService.changeLegalBasis( - annotation.annotationId, - dossierId, - fileId, - data.section, - data.value, - data.legalBasis, - data.comment, - ), - annotation, - annotationsChanged, - ); - }); - }, - ); - } - - removeOrSuggestRemoveAnnotation( - $event: MouseEvent, - annotations: AnnotationWrapper[], - removeFromDictionary: boolean, - annotationsChanged: EventEmitter, - ) { - const data = { - annotationsToRemove: annotations, - removeFromDictionary, - dossier: this._dossier, - hint: annotations[0].hintDictionary, - }; - const { dossierId, fileId } = this._screenStateService; - this._dialogService.openDialog('removeAnnotations', $event, data, (result: { comment: string }) => { - annotations.forEach(annotation => { - this._processObsAndEmit( - this._manualAnnotationService.removeOrSuggestRemoveAnnotation( - annotation, - dossierId, - fileId, - result.comment, - removeFromDictionary, - ), - annotation, - annotationsChanged, - ); - }); - }); - } - - markAsFalsePositive($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter) { - annotations.forEach(annotation => { - this._markAsFalsePositive($event, annotation, this._getFalsePositiveText(annotation), annotationsChanged); - }); - } - - recategorizeImages($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter) { - const data = { annotations, dossier: this._dossier }; - const { dossierId, fileId } = this._screenStateService; - this._dialogService.openDialog('recategorizeImage', $event, data, (res: { type: string; comment: string }) => { - annotations.forEach(annotation => { - this._processObsAndEmit( - this._manualAnnotationService.recategorizeImg(annotation.annotationId, dossierId, fileId, res.type, res.comment), - annotation, - annotationsChanged, - ); - }); - }); - } - - undoDirectAction($event: MouseEvent, annotations: AnnotationWrapper[], annotationsChanged: EventEmitter) { - $event?.stopPropagation(); - - const { dossierId, fileId } = this._screenStateService; - annotations.forEach(annotation => { - this._processObsAndEmit( - this._manualAnnotationService.undoRequest(annotation, dossierId, fileId), - annotation, - annotationsChanged, - ); - }); - } - - convertRecommendationToAnnotation( - $event: any, - recommendations: AnnotationWrapper[], - annotationsChanged: EventEmitter, - ) { - $event?.stopPropagation(); - - const { dossierId, fileId } = this._screenStateService; - const dialogRef = this._dialog.open( - AcceptRecommendationDialogComponent, - { ...defaultDialogConfig, autoFocus: true, data: { annotations: recommendations, dossierId } }, - ); - const dialogClosed = dialogRef.afterClosed().pipe(filter(value => !!value && !!value.annotations)); - dialogClosed.subscribe(({ annotations, comment: commentText }) => { - const comment = commentText ? { text: commentText } : undefined; - annotations.forEach(annotation => { - this._processObsAndEmit( - this._manualAnnotationService.addRecommendation(annotation, dossierId, fileId, comment), - annotation, - annotationsChanged, - ); - }); - }); - } - - getViewerAvailableActions( - dossier: Dossier, - annotations: AnnotationWrapper[], - annotationsChanged: EventEmitter, - ): Record[] { - const availableActions = []; - const annotationPermissions = annotations.map(annotation => ({ - annotation, - permissions: AnnotationPermissions.forUser( - this._permissionsService.isApprover(dossier), - this._userService.currentUser, - annotation, - ), - })); - - // you can only resize one annotation at a time - const canResize = annotationPermissions.length === 1 && annotationPermissions[0].permissions.canResizeAnnotation; - if (canResize) { - const firstAnnotation = annotations[0]; - // if we already entered resize-mode previously - if (firstAnnotation.resizing) { - availableActions.push({ - type: 'actionButton', - img: this._convertPath('/assets/icons/general/check.svg'), - title: this._translateService.instant('annotation-actions.resize-accept.label'), - onClick: () => - this._ngZone.run(() => { - this.acceptResize(null, firstAnnotation, annotationsChanged); - }), - }); - availableActions.push({ - type: 'actionButton', - img: this._convertPath('/assets/icons/general/close.svg'), - title: this._translateService.instant('annotation-actions.resize-cancel.label'), - onClick: () => - this._ngZone.run(() => { - this.cancelResize(null, firstAnnotation, annotationsChanged); - }), - }); - return availableActions; - } - - availableActions.push({ - type: 'actionButton', - img: this._convertPath('/assets/icons/general/resize.svg'), - title: this._translateService.instant('annotation-actions.resize.label'), - onClick: () => this._ngZone.run(() => this.resize(null, annotations[0])), - }); - } - - const canChangeLegalBasis = annotationPermissions.reduce((acc, next) => acc && next.permissions.canChangeLegalBasis, true); - if (canChangeLegalBasis) { - availableActions.push({ - type: 'actionButton', - img: this._convertPath('/assets/icons/general/edit.svg'), - title: this._translateService.instant('annotation-actions.edit-reason.label'), - onClick: () => - this._ngZone.run(() => { - this.changeLegalBasis(null, annotations, annotationsChanged); - }), - }); - } - - const canRecategorizeImage = annotationPermissions.reduce((acc, next) => acc && next.permissions.canRecategorizeImage, true); - if (canRecategorizeImage) { - availableActions.push({ - type: 'actionButton', - img: this._convertPath('/assets/icons/general/thumb-down.svg'), - title: this._translateService.instant('annotation-actions.recategorize-image'), - onClick: () => - this._ngZone.run(() => { - this.recategorizeImages(null, annotations, annotationsChanged); - }), - }); - } - - const canRemoveOrSuggestToRemoveFromDictionary = annotationPermissions.reduce( - (acc, next) => acc && next.permissions.canRemoveOrSuggestToRemoveFromDictionary, - true, - ); - if (canRemoveOrSuggestToRemoveFromDictionary) { - availableActions.push({ - type: 'actionButton', - img: this._convertPath('/assets/icons/general/remove-from-dict.svg'), - title: this._translateService.instant('annotation-actions.remove-annotation.remove-from-dict'), - onClick: () => - this._ngZone.run(() => { - this.removeOrSuggestRemoveAnnotation(null, annotations, true, annotationsChanged); - }), - }); - } - - const canAcceptRecommendation = annotationPermissions.reduce((acc, next) => acc && next.permissions.canAcceptRecommendation, true); - if (canAcceptRecommendation) { - availableActions.push({ - type: 'actionButton', - img: this._convertPath('/assets/icons/general/check.svg'), - title: this._translateService.instant('annotation-actions.accept-recommendation.label'), - onClick: () => - this._ngZone.run(() => { - this.convertRecommendationToAnnotation(null, annotations, annotationsChanged); - }), - }); - } - - const canAcceptSuggestion = annotationPermissions.reduce((acc, next) => acc && next.permissions.canAcceptSuggestion, true); - if (canAcceptSuggestion) { - availableActions.push({ - type: 'actionButton', - img: this._convertPath('/assets/icons/general/check.svg'), - title: this._translateService.instant('annotation-actions.accept-suggestion.label'), - onClick: () => - this._ngZone.run(() => { - this.acceptSuggestion(null, annotations, annotationsChanged); - }), - }); - } - - const canUndo = annotationPermissions.reduce((acc, next) => acc && next.permissions.canUndo, true); - if (canUndo) { - availableActions.push({ - type: 'actionButton', - img: this._convertPath('/assets/icons/general/undo.svg'), - title: this._translateService.instant('annotation-actions.undo'), - onClick: () => - this._ngZone.run(() => { - this.undoDirectAction(null, annotations, annotationsChanged); - }), - }); - } - - const canMarkAsFalsePositive = annotationPermissions.reduce((acc, next) => acc && next.permissions.canMarkAsFalsePositive, true); - if (canMarkAsFalsePositive) { - availableActions.push({ - type: 'actionButton', - img: this._convertPath('/assets/icons/general/thumb-down.svg'), - title: this._translateService.instant('annotation-actions.remove-annotation.false-positive'), - onClick: () => - this._ngZone.run(() => { - this.markAsFalsePositive(null, annotations, annotationsChanged); - }), - }); - } - - const canForceRedaction = annotationPermissions.reduce((acc, next) => acc && next.permissions.canForceRedaction, true); - if (canForceRedaction) { - availableActions.push({ - type: 'actionButton', - img: this._convertPath('/assets/icons/general/thumb-up.svg'), - title: this._translateService.instant('annotation-actions.force-redaction.label'), - onClick: () => - this._ngZone.run(() => { - this.forceAnnotation(null, annotations, annotationsChanged); - }), - }); - } - - const canForceHint = annotationPermissions.reduce((acc, next) => acc && next.permissions.canForceHint, true); - if (canForceHint) { - availableActions.push({ - type: 'actionButton', - img: this._convertPath('/assets/icons/general/thumb-up.svg'), - title: this._translateService.instant('annotation-actions.force-hint.label'), - onClick: () => - this._ngZone.run(() => { - this.forceAnnotation(null, annotations, annotationsChanged, true); - }), - }); - } - - const canRejectSuggestion = annotationPermissions.reduce((acc, next) => acc && next.permissions.canRejectSuggestion, true); - if (canRejectSuggestion) { - availableActions.push({ - type: 'actionButton', - img: this._convertPath('/assets/icons/general/close.svg'), - title: this._translateService.instant('annotation-actions.reject-suggestion'), - onClick: () => - this._ngZone.run(() => { - this.rejectSuggestion(null, annotations, annotationsChanged); - }), - }); - } - - const canRemoveOrSuggestToRemoveOnlyHere = annotationPermissions.reduce( - (acc, next) => acc && next.permissions.canRemoveOrSuggestToRemoveOnlyHere, - true, - ); - if (canRemoveOrSuggestToRemoveOnlyHere) { - availableActions.push({ - type: 'actionButton', - img: this._convertPath('/assets/icons/general/trash.svg'), - title: this._translateService.instant('annotation-actions.remove-annotation.only-here'), - onClick: () => - this._ngZone.run(() => { - this.removeOrSuggestRemoveAnnotation(null, annotations, false, annotationsChanged); - }), - }); - } - - return availableActions; - } - - updateHiddenAnnotation(annotations: AnnotationWrapper[], viewerAnnotations: Annotation[], hidden: boolean) { - const annotationId = viewerAnnotations[0].Id; - const annotationToBeUpdated = annotations.find((a: AnnotationWrapper) => a.annotationId === annotationId); - annotationToBeUpdated.hidden = hidden; - } - - resize($event: MouseEvent, annotationWrapper: AnnotationWrapper) { - $event?.stopPropagation(); - - annotationWrapper.resizing = true; - - const viewerAnnotation = this._pdf.annotationManager.getAnnotationById(annotationWrapper.id); - viewerAnnotation.ReadOnly = false; - viewerAnnotation.Hidden = false; - viewerAnnotation.disableRotationControl(); - this._pdf.annotationManager.redrawAnnotation(viewerAnnotation); - this._pdf.annotationManager.selectAnnotation(viewerAnnotation); - - this._annotationDrawService.annotationToQuads(viewerAnnotation); - } - - acceptResize($event: MouseEvent, annotationWrapper: AnnotationWrapper, annotationsChanged?: EventEmitter) { - const data = { dossier: this._dossier }; - const fileId = this._screenStateService.fileId; - this._dialogService.openDialog('resizeAnnotation', $event, data, async (result: { comment: string }) => { - const textAndPositions = await this._extractTextAndPositions(annotationWrapper.id); - const text = - annotationWrapper.value === 'Rectangle' ? 'Rectangle' : annotationWrapper.isImage ? 'Image' : textAndPositions.text; - - const resizeRequest: IResizeRequest = { - annotationId: annotationWrapper.id, - comment: result.comment, - positions: textAndPositions.positions, - value: text, - }; - - this._processObsAndEmit( - this._manualAnnotationService.resizeOrSuggestToResize(annotationWrapper, data.dossier.dossierId, fileId, resizeRequest), - annotationWrapper, - annotationsChanged, - ); - }); - } - - cancelResize($event: MouseEvent, annotationWrapper: AnnotationWrapper, annotationsChanged: EventEmitter) { - $event?.stopPropagation(); - - annotationWrapper.resizing = false; - - const viewerAnnotation = this._pdf.annotationManager.getAnnotationById(annotationWrapper.id); - viewerAnnotation.ReadOnly = false; - this._pdf.annotationManager.redrawAnnotation(viewerAnnotation); - this._pdf.annotationManager.deselectAllAnnotations(); - annotationsChanged.emit(annotationWrapper); - } - - private _processObsAndEmit( - obs: Observable, - annotation: AnnotationWrapper, - annotationsChanged: EventEmitter, - ) { - obs.subscribe({ - next: () => { - annotationsChanged.emit(annotation); - }, - error: () => { - annotationsChanged.emit(); - }, - }); - } - - private _getFalsePositiveText(annotation: AnnotationWrapper) { - if (annotation.canBeMarkedAsFalsePositive) { - let text: string; - if (annotation.hasTextAfter) { - text = getFirstRelevantTextPart(annotation.textAfter, 'FORWARD'); - return text ? (annotation.value + text).trim() : annotation.value; - } - if (annotation.hasTextAfter) { - text = getFirstRelevantTextPart(annotation.textBefore, 'BACKWARD'); - return text ? (text + annotation.value).trim() : annotation.value; - } else { - return annotation.value; - } - } - } - - private _markAsFalsePositive( - $event: MouseEvent, - annotation: AnnotationWrapper, - text: string, - annotationsChanged: EventEmitter, - ) { - $event?.stopPropagation(); - - const falsePositiveRequest: IAddRedactionRequest = {}; - falsePositiveRequest.reason = annotation.id; - falsePositiveRequest.value = text; - falsePositiveRequest.type = 'false_positive'; - falsePositiveRequest.positions = annotation.positions; - falsePositiveRequest.addToDictionary = true; - falsePositiveRequest.comment = { text: 'False Positive' }; - const { dossierId, fileId } = this._screenStateService; - - this._processObsAndEmit( - this._manualAnnotationService.addAnnotation(falsePositiveRequest, dossierId, fileId), - annotation, - annotationsChanged, - ); - } - - private _convertPath(path: string): string { - return this._baseHref + path; - } - - private async _extractTextAndPositions(annotationId: string) { - const viewerAnnotation = this._pdf.annotationManager.getAnnotationById(annotationId); - - const document = await this._pdf.documentViewer.getDocument().getPDFDoc(); - const page = await document.getPage(viewerAnnotation.getPageNumber()); - if (viewerAnnotation instanceof this._pdf.Annotations.TextHighlightAnnotation) { - const words = []; - const rectangles: IRectangle[] = []; - for (const quad of viewerAnnotation.Quads) { - const rect = toPosition( - viewerAnnotation.getPageNumber(), - this._pdf.documentViewer.getPageHeight(viewerAnnotation.getPageNumber()), - this._translateQuads(viewerAnnotation.getPageNumber(), quad), - ); - rectangles.push(rect); - - // TODO: this is an educated guess for lines that are close together - // TODO: so that we do not extract text from line above/line below - const percentHeightOffset = rect.height / 10; - - const pdfNetRect = new this._pdf.instance.Core.PDFNet.Rect( - rect.topLeft.x, - rect.topLeft.y + percentHeightOffset, - rect.topLeft.x + rect.width, - rect.topLeft.y + rect.height - percentHeightOffset, - ); - const quadWords = await this._extractTextFromRect(page, pdfNetRect); - words.push(...quadWords); - } - - console.log(words.join(' ')); - - return { - text: words.join(' '), - positions: rectangles, - }; - } else { - const rect = toPosition( - viewerAnnotation.getPageNumber(), - this._pdf.documentViewer.getPageHeight(viewerAnnotation.getPageNumber()), - this._annotationDrawService.annotationToQuads(viewerAnnotation), - ); - return { - positions: [rect], - text: null, - }; - } - } - - private _translateQuads(page: number, quads: any) { - const rotation = this._pdf.documentViewer.getCompleteRotation(page); - return translateQuads(page, rotation, quads); - } - - private async _extractTextFromRect(page: Core.PDFNet.Page, rect: Core.PDFNet.Rect) { - const txt = await this._pdf.PDFNet.TextExtractor.create(); - txt.begin(page, rect); // Read the page. - - const words: string[] = []; - // Extract words one by one. - let line = await txt.getFirstLine(); - for (; await line.isValid(); line = await line.getNextLine()) { - for (let word = await line.getFirstWord(); await word.isValid(); word = await word.getNextWord()) { - words.push(await word.getString()); - } - } - return words; - } -} diff --git a/apps/red-ui/src/app/modules/shared/shared.module.ts b/apps/red-ui/src/app/modules/shared/shared.module.ts index 6098db04e..addd82cf7 100644 --- a/apps/red-ui/src/app/modules/shared/shared.module.ts +++ b/apps/red-ui/src/app/modules/shared/shared.module.ts @@ -28,9 +28,9 @@ import { EditorComponent } from './components/editor/editor.component'; import { ExpandableFileActionsComponent } from './components/expandable-file-actions/expandable-file-actions.component'; import { ProcessingIndicatorComponent } from '@shared/components/processing-indicator/processing-indicator.component'; import { DossierStateComponent } from '@shared/components/dossier-state/dossier-state.component'; -import { DossiersListingDossierNameComponent } from '@shared/components/dossiers-listing-dossier-name/dossiers-listing-dossier-name.component'; import { FileStatsComponent } from './components/file-stats/file-stats.component'; import { FileNameColumnComponent } from '@shared/components/file-name-column/file-name-column.component'; +import { DossierNameColumnComponent } from '@shared/components/dossier-name-column/dossier-name-column.component'; const buttons = [FileDownloadBtnComponent, UserButtonComponent]; @@ -48,7 +48,7 @@ const components = [ ExpandableFileActionsComponent, ProcessingIndicatorComponent, DossierStateComponent, - DossiersListingDossierNameComponent, + DossierNameColumnComponent, FileStatsComponent, FileNameColumnComponent,