text-highlights
This commit is contained in:
parent
06c3fd3444
commit
5612fc01ea
@ -1,9 +1,10 @@
|
|||||||
import { annotationTypesTranslations } from '../../translations/annotation-types-translations';
|
import { annotationTypesTranslations } from '../../translations/annotation-types-translations';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { IComment, IManualChange, IPoint, IRectangle, LogEntryStatus, ManualRedactionType } from '@red/domain';
|
import { IComment, IManualChange, ImportedRedaction, IPoint, IRectangle, LogEntryStatus, ManualRedactionType } from '@red/domain';
|
||||||
import { RedactionLogEntry } from '@models/file/redaction-log.entry';
|
import { RedactionLogEntry } from '@models/file/redaction-log.entry';
|
||||||
|
|
||||||
export type AnnotationSuperType =
|
export type AnnotationSuperType =
|
||||||
|
| 'text-highlight'
|
||||||
| 'suggestion-change-legal-basis'
|
| 'suggestion-change-legal-basis'
|
||||||
| 'suggestion-recategorize-image'
|
| 'suggestion-recategorize-image'
|
||||||
| 'suggestion-add-dictionary'
|
| 'suggestion-add-dictionary'
|
||||||
@ -121,6 +122,10 @@ export class AnnotationWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get filterKey() {
|
get filterKey() {
|
||||||
|
if (this.isHighlight) {
|
||||||
|
return this.color;
|
||||||
|
}
|
||||||
|
|
||||||
return this.topLevelFilter ? this.superType : this.superType + this.type;
|
return this.topLevelFilter ? this.superType : this.superType + this.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,6 +159,10 @@ export class AnnotationWrapper {
|
|||||||
return this.superType === 'hint';
|
return this.superType === 'hint';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isHighlight() {
|
||||||
|
return this.superType === 'text-highlight';
|
||||||
|
}
|
||||||
|
|
||||||
get isIgnoredHint() {
|
get isIgnoredHint() {
|
||||||
return this.superType === 'ignored-hint';
|
return this.superType === 'ignored-hint';
|
||||||
}
|
}
|
||||||
@ -235,10 +244,34 @@ export class AnnotationWrapper {
|
|||||||
return this.legalBasisChangeValue || this.legalBasisValue;
|
return this.legalBasisChangeValue || this.legalBasisValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get width(): number {
|
||||||
|
return Math.floor(this.positions[0].width);
|
||||||
|
}
|
||||||
|
|
||||||
|
get height(): number {
|
||||||
|
return Math.floor(this.positions[0].height);
|
||||||
|
}
|
||||||
|
|
||||||
get previewAnnotation() {
|
get previewAnnotation() {
|
||||||
return this.isRedacted || this.isSuggestionAdd;
|
return this.isRedacted || this.isSuggestionAdd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static fromHighlight(color: string, entry: ImportedRedaction) {
|
||||||
|
const annotationWrapper = new AnnotationWrapper();
|
||||||
|
|
||||||
|
annotationWrapper.annotationId = entry.id;
|
||||||
|
annotationWrapper.pageNumber = entry.positions[0].page;
|
||||||
|
annotationWrapper.superType = 'text-highlight';
|
||||||
|
annotationWrapper.typeValue = 'text-highlight';
|
||||||
|
annotationWrapper.value = 'Imported';
|
||||||
|
annotationWrapper.color = color;
|
||||||
|
annotationWrapper.positions = entry.positions;
|
||||||
|
annotationWrapper.firstTopLeftPoint = entry.positions[0]?.topLeft;
|
||||||
|
annotationWrapper.typeLabel = annotationTypesTranslations[annotationWrapper.superType];
|
||||||
|
|
||||||
|
return annotationWrapper;
|
||||||
|
}
|
||||||
|
|
||||||
static fromData(redactionLogEntry?: RedactionLogEntry) {
|
static fromData(redactionLogEntry?: RedactionLogEntry) {
|
||||||
const annotationWrapper = new AnnotationWrapper();
|
const annotationWrapper = new AnnotationWrapper();
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
IViewedPage,
|
IViewedPage,
|
||||||
LogEntryStatus,
|
LogEntryStatus,
|
||||||
ManualRedactionType,
|
ManualRedactionType,
|
||||||
|
TextHighlightResponse,
|
||||||
ViewMode,
|
ViewMode,
|
||||||
} from '@red/domain';
|
} from '@red/domain';
|
||||||
import { AnnotationWrapper } from './annotation.wrapper';
|
import { AnnotationWrapper } from './annotation.wrapper';
|
||||||
@ -19,6 +20,8 @@ export class FileDataModel {
|
|||||||
allAnnotations: AnnotationWrapper[] = [];
|
allAnnotations: AnnotationWrapper[] = [];
|
||||||
readonly hasChangeLog$ = new BehaviorSubject<boolean>(false);
|
readonly hasChangeLog$ = new BehaviorSubject<boolean>(false);
|
||||||
missingTypes = new Set<string>();
|
missingTypes = new Set<string>();
|
||||||
|
_textHighlightResponse: TextHighlightResponse;
|
||||||
|
textHighlightAnnotations: AnnotationWrapper[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _file: File,
|
private readonly _file: File,
|
||||||
@ -39,7 +42,24 @@ export class FileDataModel {
|
|||||||
this._buildAllAnnotations();
|
this._buildAllAnnotations();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set textHighlights(textHighlightResponse: TextHighlightResponse) {
|
||||||
|
this._textHighlightResponse = textHighlightResponse;
|
||||||
|
|
||||||
|
const highlights = [];
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||||
|
for (const color of Object.keys(textHighlightResponse.redactionPerColor)) {
|
||||||
|
for (const entry of textHighlightResponse.redactionPerColor[color]) {
|
||||||
|
const annotation = AnnotationWrapper.fromHighlight(color, entry);
|
||||||
|
highlights.push(annotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.textHighlightAnnotations = highlights;
|
||||||
|
}
|
||||||
|
|
||||||
getVisibleAnnotations(viewMode: ViewMode) {
|
getVisibleAnnotations(viewMode: ViewMode) {
|
||||||
|
if (viewMode === 'TEXT_HIGHLIGHTS') {
|
||||||
|
return this.textHighlightAnnotations;
|
||||||
|
}
|
||||||
return this.allAnnotations.filter(annotation => {
|
return this.allAnnotations.filter(annotation => {
|
||||||
if (viewMode === 'STANDARD') {
|
if (viewMode === 'STANDARD') {
|
||||||
return !annotation.isChangeLogRemoved;
|
return !annotation.isChangeLogRemoved;
|
||||||
|
|||||||
@ -25,6 +25,8 @@ import { OverlayModule } from '@angular/cdk/overlay';
|
|||||||
import { SharedDossiersModule } from './shared/shared-dossiers.module';
|
import { SharedDossiersModule } from './shared/shared-dossiers.module';
|
||||||
import { ResizeAnnotationDialogComponent } from './dialogs/resize-annotation-dialog/resize-annotation-dialog.component';
|
import { ResizeAnnotationDialogComponent } from './dialogs/resize-annotation-dialog/resize-annotation-dialog.component';
|
||||||
import { EditDossierTeamComponent } from './dialogs/edit-dossier-dialog/edit-dossier-team/edit-dossier-team.component';
|
import { EditDossierTeamComponent } from './dialogs/edit-dossier-dialog/edit-dossier-team/edit-dossier-team.component';
|
||||||
|
import { HighlightActionDialogComponent } from './screens/file-preview-screen/dialogs/highlight-action-dialog/highlight-action-dialog.component';
|
||||||
|
import { ColorPickerModule } from 'ngx-color-picker';
|
||||||
|
|
||||||
const screens = [SearchScreenComponent];
|
const screens = [SearchScreenComponent];
|
||||||
|
|
||||||
@ -39,6 +41,7 @@ const dialogs = [
|
|||||||
AssignReviewerApproverDialogComponent,
|
AssignReviewerApproverDialogComponent,
|
||||||
ChangeLegalBasisDialogComponent,
|
ChangeLegalBasisDialogComponent,
|
||||||
RecategorizeImageDialogComponent,
|
RecategorizeImageDialogComponent,
|
||||||
|
HighlightActionDialogComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
const components = [
|
const components = [
|
||||||
@ -58,6 +61,14 @@ const services = [DossiersDialogService, ManualAnnotationService, AnnotationProc
|
|||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [...components],
|
declarations: [...components],
|
||||||
providers: [...services],
|
providers: [...services],
|
||||||
imports: [CommonModule, SharedModule, SharedDossiersModule, FileUploadDownloadModule, DossiersRoutingModule, OverlayModule],
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
SharedModule,
|
||||||
|
SharedDossiersModule,
|
||||||
|
FileUploadDownloadModule,
|
||||||
|
DossiersRoutingModule,
|
||||||
|
OverlayModule,
|
||||||
|
ColorPickerModule,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class DossiersModule {}
|
export class DossiersModule {}
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<strong>{{ annotation.typeLabel | translate }}</strong>
|
<strong>{{ annotation.typeLabel | translate }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="annotation?.type !== 'manual'">
|
<div *ngIf="annotation.type !== 'manual' && !annotation.isHighlight">
|
||||||
<strong>
|
<strong>
|
||||||
<span>{{ annotation.descriptor | translate }}</span
|
<span>{{ annotation.descriptor | translate }}</span
|
||||||
>: </strong
|
>: </strong
|
||||||
@ -14,6 +14,12 @@
|
|||||||
<div *ngIf="annotation.shortContent && !annotation.isHint">
|
<div *ngIf="annotation.shortContent && !annotation.isHint">
|
||||||
<strong><span translate="content"></span>: </strong>{{ annotation.shortContent }}
|
<strong><span translate="content"></span>: </strong>{{ annotation.shortContent }}
|
||||||
</div>
|
</div>
|
||||||
|
<div *ngIf="annotation.isHighlight">
|
||||||
|
<strong><span translate="color"></span>: </strong>{{ annotation.color }}
|
||||||
|
</div>
|
||||||
|
<div *ngIf="annotation.isHighlight">
|
||||||
|
<strong><span translate="size"></span>: </strong>{{ annotation.width }}x{{ annotation.height }} px
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="active-icon-marker-container">
|
<div class="active-icon-marker-container">
|
||||||
|
|||||||
@ -1,6 +1,34 @@
|
|||||||
|
<ng-container *ngFor="let annotation of annotations; let idx = index">
|
||||||
|
<div *ngIf="showHighlightGroup(idx) as highlightGroup" class="workload-separator">
|
||||||
|
<div>
|
||||||
|
<redaction-type-annotation-icon [annotation]="annotation" class="mr-8"></redaction-type-annotation-icon>
|
||||||
|
<span [translateParams]="highlightGroup" [translate]="'highlights'" class="all-caps-label"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="state.isWritable$ | async">
|
||||||
|
<iqser-circle-button
|
||||||
|
(action)="convertHighlights(highlightGroup)"
|
||||||
|
[size]="28"
|
||||||
|
[tooltip]="'file-preview.highlights.convert' | translate"
|
||||||
|
[type]="circleButtonTypes.dark"
|
||||||
|
class="mr-2"
|
||||||
|
icon="iqser:plus"
|
||||||
|
tooltipPosition="above"
|
||||||
|
></iqser-circle-button>
|
||||||
|
|
||||||
|
<iqser-circle-button
|
||||||
|
(action)="removeHighlights(highlightGroup)"
|
||||||
|
[size]="28"
|
||||||
|
[tooltip]="'file-preview.highlights.remove' | translate"
|
||||||
|
[type]="circleButtonTypes.dark"
|
||||||
|
icon="iqser:trash"
|
||||||
|
tooltipPosition="above"
|
||||||
|
></iqser-circle-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
(click)="annotationClicked(annotation, $event)"
|
(click)="annotationClicked(annotation, $event)"
|
||||||
*ngFor="let annotation of annotations"
|
|
||||||
[attr.annotation-id]="annotation.id"
|
[attr.annotation-id]="annotation.id"
|
||||||
[attr.annotation-page]="activeViewerPage"
|
[attr.annotation-page]="activeViewerPage"
|
||||||
[class.active]="isSelected(annotation.annotationId)"
|
[class.active]="isSelected(annotation.annotationId)"
|
||||||
@ -17,7 +45,7 @@
|
|||||||
matTooltipPosition="above"
|
matTooltipPosition="above"
|
||||||
></redaction-annotation-card>
|
></redaction-annotation-card>
|
||||||
|
|
||||||
<div class="actions-wrapper">
|
<div *ngIf="!annotation.isHighlight" class="actions-wrapper">
|
||||||
<div
|
<div
|
||||||
(click)="comments.toggleExpandComments($event)"
|
(click)="comments.toggleExpandComments($event)"
|
||||||
[matTooltip]="'comments.comments' | translate: { count: annotation.comments?.length }"
|
[matTooltip]="'comments.comments' | translate: { count: annotation.comments?.length }"
|
||||||
@ -41,6 +69,7 @@
|
|||||||
|
|
||||||
<redaction-annotation-details [annotation]="annotation" [isSelected]="isSelected(annotation.id)"></redaction-annotation-details>
|
<redaction-annotation-details [annotation]="annotation" [isSelected]="isSelected(annotation.id)"></redaction-annotation-details>
|
||||||
</div>
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="annotationReferencesService.annotation$ | async">
|
<ng-container *ngIf="annotationReferencesService.annotation$ | async">
|
||||||
<redaction-annotation-references-list
|
<redaction-annotation-references-list
|
||||||
|
|||||||
@ -76,3 +76,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.workload-separator > div {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,11 +1,20 @@
|
|||||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, TemplateRef } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, TemplateRef } from '@angular/core';
|
||||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||||
import { FilterService, IqserEventTarget } from '@iqser/common-ui';
|
import { CircleButtonTypes, FilterService, IqserEventTarget } from '@iqser/common-ui';
|
||||||
import { MultiSelectService } from '../../services/multi-select.service';
|
import { MultiSelectService } from '../../services/multi-select.service';
|
||||||
import { AnnotationReferencesService } from '../../services/annotation-references.service';
|
import { AnnotationReferencesService } from '../../services/annotation-references.service';
|
||||||
import { ViewModeService } from '../../services/view-mode.service';
|
|
||||||
import { FilePreviewStateService } from '../../services/file-preview-state.service';
|
import { FilePreviewStateService } from '../../services/file-preview-state.service';
|
||||||
import { UserPreferenceService } from '../../../../../../services/user-preference.service';
|
import { UserPreferenceService } from '@services/user-preference.service';
|
||||||
|
import { ViewModeService } from '../../services/view-mode.service';
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
import { DossiersDialogService } from '../../../../services/dossiers-dialog.service';
|
||||||
|
import { TextHighlightOperation } from '@red/domain';
|
||||||
|
|
||||||
|
interface HighlightGroup {
|
||||||
|
startIdx: number;
|
||||||
|
color: string;
|
||||||
|
length: number;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-annotations-list',
|
selector: 'redaction-annotations-list',
|
||||||
@ -14,6 +23,8 @@ import { UserPreferenceService } from '../../../../../../services/user-preferenc
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class AnnotationsListComponent implements OnChanges {
|
export class AnnotationsListComponent implements OnChanges {
|
||||||
|
readonly circleButtonTypes = CircleButtonTypes;
|
||||||
|
|
||||||
@Input() annotations: AnnotationWrapper[];
|
@Input() annotations: AnnotationWrapper[];
|
||||||
@Input() selectedAnnotations: AnnotationWrapper[];
|
@Input() selectedAnnotations: AnnotationWrapper[];
|
||||||
@Input() annotationActionsTemplate: TemplateRef<unknown>;
|
@Input() annotationActionsTemplate: TemplateRef<unknown>;
|
||||||
@ -23,19 +34,36 @@ export class AnnotationsListComponent implements OnChanges {
|
|||||||
@Output() readonly selectAnnotations = new EventEmitter<AnnotationWrapper[]>();
|
@Output() readonly selectAnnotations = new EventEmitter<AnnotationWrapper[]>();
|
||||||
@Output() readonly deselectAnnotations = new EventEmitter<AnnotationWrapper[]>();
|
@Output() readonly deselectAnnotations = new EventEmitter<AnnotationWrapper[]>();
|
||||||
|
|
||||||
|
highlightGroups$ = new BehaviorSubject<HighlightGroup[]>([]);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly multiSelectService: MultiSelectService,
|
readonly multiSelectService: MultiSelectService,
|
||||||
readonly viewModeService: ViewModeService,
|
|
||||||
readonly annotationReferencesService: AnnotationReferencesService,
|
readonly annotationReferencesService: AnnotationReferencesService,
|
||||||
|
readonly state: FilePreviewStateService,
|
||||||
private readonly _filterService: FilterService,
|
private readonly _filterService: FilterService,
|
||||||
private readonly _state: FilePreviewStateService,
|
|
||||||
private readonly _userPreferenceService: UserPreferenceService,
|
private readonly _userPreferenceService: UserPreferenceService,
|
||||||
|
private readonly _viewModeService: ViewModeService,
|
||||||
|
private readonly _dialogService: DossiersDialogService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
convertHighlights(highlightGroup: HighlightGroup): void {
|
||||||
|
const data = this._getActionData(highlightGroup, TextHighlightOperation.CONVERT);
|
||||||
|
this._dialogService.openDialog('highlightAction', null, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeHighlights(highlightGroup: HighlightGroup): void {
|
||||||
|
const data = this._getActionData(highlightGroup, TextHighlightOperation.REMOVE);
|
||||||
|
this._dialogService.openDialog('highlightAction', null, data);
|
||||||
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
if (changes.annotations && this.annotations) {
|
if (changes.annotations && this.annotations && !this._viewModeService.isTextHighlights) {
|
||||||
this.annotations = this.annotations.sort(this.annotationsPositionCompare);
|
this.annotations = this.annotations.sort(this.annotationsPositionCompare);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._viewModeService.isTextHighlights) {
|
||||||
|
this._updateHighlightGroups();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
annotationClicked(annotation: AnnotationWrapper, $event: MouseEvent): void {
|
annotationClicked(annotation: AnnotationWrapper, $event: MouseEvent): void {
|
||||||
@ -80,4 +108,37 @@ export class AnnotationsListComponent implements OnChanges {
|
|||||||
return first.x < second.y ? -1 : 1;
|
return first.x < second.y ? -1 : 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showHighlightGroup(idx: number): HighlightGroup {
|
||||||
|
return this._viewModeService.isTextHighlights && this.highlightGroups$.value.find(h => h.startIdx === idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getActionData(highlightGroup: HighlightGroup, operation: TextHighlightOperation) {
|
||||||
|
return {
|
||||||
|
dossierId: this.state.dossierId,
|
||||||
|
fileId: this.state.fileId,
|
||||||
|
color: highlightGroup.color,
|
||||||
|
operation,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private _updateHighlightGroups(): void {
|
||||||
|
if (!this.annotations?.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const highlightGroups: HighlightGroup[] = [];
|
||||||
|
let lastGroup: HighlightGroup;
|
||||||
|
for (let idx = 0; idx < this.annotations.length; ++idx) {
|
||||||
|
if (idx === 0 || this.annotations[idx].color !== this.annotations[idx - 1].color) {
|
||||||
|
if (lastGroup) {
|
||||||
|
highlightGroups.push(lastGroup);
|
||||||
|
}
|
||||||
|
lastGroup = { startIdx: idx, length: 1, color: this.annotations[idx].color };
|
||||||
|
} else {
|
||||||
|
lastGroup.length += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
highlightGroups.push(lastGroup);
|
||||||
|
this.highlightGroups$.next(highlightGroups);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #selectAndFilter>
|
<ng-template #selectAndFilter>
|
||||||
<div class="right-title heading" translate="file-preview.tabs.annotations.label">
|
<div class="right-title heading">
|
||||||
|
{{ title$ | async | translate }}
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
(click)="multiSelectService.activate()"
|
(click)="multiSelectService.activate()"
|
||||||
@ -111,16 +112,18 @@
|
|||||||
|
|
||||||
<div style="overflow: hidden; width: 100%">
|
<div style="overflow: hidden; width: 100%">
|
||||||
<div
|
<div
|
||||||
|
*ngIf="(isHighlights$ | async) === false"
|
||||||
[attr.anotation-page-header]="activeViewerPage"
|
[attr.anotation-page-header]="activeViewerPage"
|
||||||
[class.padding-left-0]="currentPageIsExcluded"
|
[class.padding-left-0]="currentPageIsExcluded"
|
||||||
[hidden]="excludedPagesService.shown$ | async"
|
[hidden]="excludedPagesService.shown$ | async"
|
||||||
class="page-separator"
|
class="workload-separator"
|
||||||
>
|
>
|
||||||
<span *ngIf="!!activeViewerPage" class="flex-align-items-center">
|
<span *ngIf="!!activeViewerPage" class="flex-align-items-center">
|
||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
(action)="excludedPagesService.toggle()"
|
(action)="excludedPagesService.toggle()"
|
||||||
*ngIf="currentPageIsExcluded"
|
*ngIf="currentPageIsExcluded"
|
||||||
[tooltip]="'file-preview.excluded-from-redaction' | translate | capitalize"
|
[tooltip]="'file-preview.excluded-from-redaction' | translate | capitalize"
|
||||||
|
class="excluded"
|
||||||
icon="red:exclude-pages"
|
icon="red:exclude-pages"
|
||||||
tooltipPosition="above"
|
tooltipPosition="above"
|
||||||
></iqser-circle-button>
|
></iqser-circle-button>
|
||||||
|
|||||||
@ -115,25 +115,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-separator {
|
|
||||||
border-bottom: 1px solid variables.$separator;
|
|
||||||
height: 32px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 0 10px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
background-color: variables.$grey-6;
|
|
||||||
|
|
||||||
> div {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
> div:not(:last-child) {
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.annotations {
|
.annotations {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -157,7 +138,7 @@
|
|||||||
padding-left: 0 !important;
|
padding-left: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::ng-deep .page-separator iqser-circle-button mat-icon {
|
::ng-deep .workload-separator iqser-circle-button.excluded mat-icon {
|
||||||
color: var(--iqser-primary);
|
color: var(--iqser-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,6 +34,8 @@ import { MultiSelectService } from '../../services/multi-select.service';
|
|||||||
import { DocumentInfoService } from '../../services/document-info.service';
|
import { DocumentInfoService } from '../../services/document-info.service';
|
||||||
import { SkippedService } from '../../services/skipped.service';
|
import { SkippedService } from '../../services/skipped.service';
|
||||||
import { FilePreviewStateService } from '../../services/file-preview-state.service';
|
import { FilePreviewStateService } from '../../services/file-preview-state.service';
|
||||||
|
import { ViewModeService } from '../../services/view-mode.service';
|
||||||
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
|
|
||||||
const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape'];
|
const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape'];
|
||||||
const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
|
const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
|
||||||
@ -67,6 +69,8 @@ export class FileWorkloadComponent {
|
|||||||
readonly multiSelectActive$: Observable<boolean>;
|
readonly multiSelectActive$: Observable<boolean>;
|
||||||
readonly multiSelectInactive$: Observable<boolean>;
|
readonly multiSelectInactive$: Observable<boolean>;
|
||||||
readonly showExcludedPages$: Observable<boolean>;
|
readonly showExcludedPages$: Observable<boolean>;
|
||||||
|
readonly title$: Observable<string>;
|
||||||
|
readonly isHighlights$: Observable<boolean>;
|
||||||
private _annotations$ = new BehaviorSubject<AnnotationWrapper[]>([]);
|
private _annotations$ = new BehaviorSubject<AnnotationWrapper[]>([]);
|
||||||
@ViewChild('annotationsElement') private readonly _annotationsElement: ElementRef;
|
@ViewChild('annotationsElement') private readonly _annotationsElement: ElementRef;
|
||||||
@ViewChild('quickNavigation') private readonly _quickNavigationElement: ElementRef;
|
@ViewChild('quickNavigation') private readonly _quickNavigationElement: ElementRef;
|
||||||
@ -78,6 +82,7 @@ export class FileWorkloadComponent {
|
|||||||
readonly multiSelectService: MultiSelectService,
|
readonly multiSelectService: MultiSelectService,
|
||||||
readonly documentInfoService: DocumentInfoService,
|
readonly documentInfoService: DocumentInfoService,
|
||||||
readonly excludedPagesService: ExcludedPagesService,
|
readonly excludedPagesService: ExcludedPagesService,
|
||||||
|
private readonly _viewModeService: ViewModeService,
|
||||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
private readonly _changeDetectorRef: ChangeDetectorRef,
|
||||||
private readonly _permissionsService: PermissionsService,
|
private readonly _permissionsService: PermissionsService,
|
||||||
private readonly _annotationProcessingService: AnnotationProcessingService,
|
private readonly _annotationProcessingService: AnnotationProcessingService,
|
||||||
@ -86,6 +91,8 @@ export class FileWorkloadComponent {
|
|||||||
this.multiSelectActive$ = this._multiSelectActive$;
|
this.multiSelectActive$ = this._multiSelectActive$;
|
||||||
this.multiSelectInactive$ = this._multiSelectInactive$;
|
this.multiSelectInactive$ = this._multiSelectInactive$;
|
||||||
this.showExcludedPages$ = this._showExcludedPages$;
|
this.showExcludedPages$ = this._showExcludedPages$;
|
||||||
|
this.isHighlights$ = this._isHighlights$;
|
||||||
|
this.title$ = this._title$;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
@ -112,6 +119,16 @@ export class FileWorkloadComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private get _title$(): Observable<string> {
|
||||||
|
return this.isHighlights$.pipe(
|
||||||
|
map(isHighlights => (isHighlights ? _('file-preview.tabs.highlights.label') : _('file-preview.tabs.annotations.label'))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _isHighlights$(): Observable<boolean> {
|
||||||
|
return this._viewModeService.viewMode$.pipe(map(() => this._viewModeService.isTextHighlights));
|
||||||
|
}
|
||||||
|
|
||||||
private get _multiSelectInactive$() {
|
private get _multiSelectInactive$() {
|
||||||
return this.multiSelectService.inactive$.pipe(
|
return this.multiSelectService.inactive$.pipe(
|
||||||
tap(value => {
|
tap(value => {
|
||||||
|
|||||||
@ -26,9 +26,11 @@ export class TypeAnnotationIconComponent implements OnChanges {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { isSuggestion, isRecommendation, isSkipped, isDeclinedSuggestion, isHint, isIgnoredHint } = this.annotation;
|
const { isHighlight, isSuggestion, isRecommendation, isSkipped, isDeclinedSuggestion, isHint, isIgnoredHint } = this.annotation;
|
||||||
|
|
||||||
if (this.annotation.isSuperTypeBasedColor) {
|
if (isHighlight) {
|
||||||
|
this.color = this.annotation.color;
|
||||||
|
} else if (this.annotation.isSuperTypeBasedColor) {
|
||||||
this.color = this._dictionariesMapService.getDictionaryColor(this.annotation.superType, this._dossierTemplateId);
|
this.color = this._dictionariesMapService.getDictionaryColor(this.annotation.superType, this._dossierTemplateId);
|
||||||
} else {
|
} else {
|
||||||
this.color = this._dictionariesMapService.getDictionaryColor(this.annotation.type, this._dossierTemplateId);
|
this.color = this._dictionariesMapService.getDictionaryColor(this.annotation.type, this._dossierTemplateId);
|
||||||
@ -36,6 +38,7 @@ export class TypeAnnotationIconComponent implements OnChanges {
|
|||||||
|
|
||||||
this.type =
|
this.type =
|
||||||
isSuggestion || isDeclinedSuggestion ? 'rhombus' : isHint || isIgnoredHint ? 'circle' : isRecommendation ? 'hexagon' : 'square';
|
isSuggestion || isDeclinedSuggestion ? 'rhombus' : isHint || isIgnoredHint ? 'circle' : isRecommendation ? 'hexagon' : 'square';
|
||||||
this.label = isSuggestion || isDeclinedSuggestion ? 'S' : isSkipped ? 'S' : this.annotation.type[0].toUpperCase();
|
|
||||||
|
this.label = isHighlight ? '' : isSuggestion || isDeclinedSuggestion || isSkipped ? 'S' : this.annotation.type[0].toUpperCase();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,4 +30,15 @@
|
|||||||
>
|
>
|
||||||
{{ 'file-preview.redacted' | translate }}
|
{{ 'file-preview.redacted' | translate }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
(click)="switchView.emit('TEXT_HIGHLIGHTS')"
|
||||||
|
[class.active]="viewModeService.isTextHighlights"
|
||||||
|
[disabled]="(canSwitchToRedactedView$ | async) === false"
|
||||||
|
[matTooltip]="'file-preview.text-highlights-tooltip' | translate"
|
||||||
|
class="red-tab"
|
||||||
|
>
|
||||||
|
<!-- iqserHelpMode="text_highlights_view"-->
|
||||||
|
{{ 'file-preview.text-highlights' | translate }}
|
||||||
|
</button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@ -0,0 +1,35 @@
|
|||||||
|
<section class="dialog">
|
||||||
|
<form (submit)="save()" [formGroup]="form">
|
||||||
|
<div [translate]="title" class="dialog-header heading-l"></div>
|
||||||
|
|
||||||
|
<div class="dialog-content">
|
||||||
|
<div [translate]="details" class="mb-24"></div>
|
||||||
|
|
||||||
|
<div class="iqser-input-group required w-150">
|
||||||
|
<label translate="highlight-action-dialog.form.color.label"></label>
|
||||||
|
<input class="hex-color-input" formControlName="color" name="color" type="text" />
|
||||||
|
<div
|
||||||
|
[colorPicker]="form.get('color').value"
|
||||||
|
[cpDisabled]="true"
|
||||||
|
[style.background]="form.get('color').value"
|
||||||
|
class="input-icon"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="iqser-input-group">
|
||||||
|
<mat-checkbox color="primary" formControlName="confirmation" name="confirmation">
|
||||||
|
{{ confirmationMessage | translate }}
|
||||||
|
</mat-checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dialog-actions">
|
||||||
|
<button [disabled]="form.invalid" color="primary" mat-flat-button type="submit">
|
||||||
|
{{ saveMessage | translate }}
|
||||||
|
</button>
|
||||||
|
<div class="all-caps-label cancel" mat-dialog-close translate="highlight-action-dialog.actions.cancel"></div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||||
|
</section>
|
||||||
@ -0,0 +1,70 @@
|
|||||||
|
import { Component, Inject, Injector } from '@angular/core';
|
||||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
|
import { TextHighlightOperation } from '@red/domain';
|
||||||
|
import { BaseDialogComponent, LoadingService } from '@iqser/common-ui';
|
||||||
|
import { TextHighlightService } from '../../../../services/text-highlight.service';
|
||||||
|
import { firstValueFrom } from 'rxjs';
|
||||||
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
|
|
||||||
|
export interface HighlightActionData {
|
||||||
|
readonly operation: TextHighlightOperation;
|
||||||
|
readonly color: string;
|
||||||
|
readonly dossierId: string;
|
||||||
|
readonly fileId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: './highlight-action-dialog.component.html',
|
||||||
|
})
|
||||||
|
export class HighlightActionDialogComponent extends BaseDialogComponent {
|
||||||
|
readonly title: string;
|
||||||
|
readonly details: string;
|
||||||
|
readonly confirmationMessage: string;
|
||||||
|
readonly saveMessage: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly _formBuilder: FormBuilder,
|
||||||
|
protected readonly _injector: Injector,
|
||||||
|
protected readonly _dialogRef: MatDialogRef<HighlightActionDialogComponent>,
|
||||||
|
private readonly _textHighlightService: TextHighlightService,
|
||||||
|
private readonly _loadingService: LoadingService,
|
||||||
|
@Inject(MAT_DIALOG_DATA) readonly data: HighlightActionData,
|
||||||
|
) {
|
||||||
|
super(_injector, _dialogRef);
|
||||||
|
this.form = this._getForm();
|
||||||
|
this.initialFormValue = this.form.getRawValue();
|
||||||
|
|
||||||
|
this.title =
|
||||||
|
data.operation === TextHighlightOperation.CONVERT
|
||||||
|
? _('highlight-action-dialog.convert.title')
|
||||||
|
: _('highlight-action-dialog.remove.title');
|
||||||
|
this.details =
|
||||||
|
data.operation === TextHighlightOperation.CONVERT
|
||||||
|
? _('highlight-action-dialog.convert.details')
|
||||||
|
: _('highlight-action-dialog.remove.details');
|
||||||
|
this.confirmationMessage =
|
||||||
|
data.operation === TextHighlightOperation.CONVERT
|
||||||
|
? _('highlight-action-dialog.convert.confirmation')
|
||||||
|
: _('highlight-action-dialog.remove.confirmation');
|
||||||
|
this.saveMessage =
|
||||||
|
data.operation === TextHighlightOperation.CONVERT
|
||||||
|
? _('highlight-action-dialog.convert.save')
|
||||||
|
: _('highlight-action-dialog.remove.save');
|
||||||
|
}
|
||||||
|
|
||||||
|
async save(): Promise<void> {
|
||||||
|
this._loadingService.start();
|
||||||
|
const { dossierId, fileId, color, operation } = this.data;
|
||||||
|
await firstValueFrom(this._textHighlightService.performHighlightsAction(dossierId, fileId, [color], operation));
|
||||||
|
this._loadingService.stop();
|
||||||
|
this._dialogRef.close(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getForm(): FormGroup {
|
||||||
|
return this._formBuilder.group({
|
||||||
|
color: [{ value: this.data.color, disabled: true }, Validators.required],
|
||||||
|
confirmation: [false, Validators.requiredTrue],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -155,6 +155,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
const ocrAnnotationIds = this._fileData.allAnnotations.filter(a => a.isOCR).map(a => a.id);
|
const ocrAnnotationIds = this._fileData.allAnnotations.filter(a => a.isOCR).map(a => a.id);
|
||||||
const annotations = this._getAnnotations(a => a.getCustomData('redact-manager'));
|
const annotations = this._getAnnotations(a => a.getCustomData('redact-manager'));
|
||||||
const redactions = annotations.filter(a => a.getCustomData('redaction'));
|
const redactions = annotations.filter(a => a.getCustomData('redaction'));
|
||||||
|
const highlights = annotations.filter(a => a.getCustomData('highlight'));
|
||||||
|
|
||||||
switch (this.viewModeService.viewMode) {
|
switch (this.viewModeService.viewMode) {
|
||||||
case 'STANDARD': {
|
case 'STANDARD': {
|
||||||
@ -165,7 +166,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
const nonStandardEntries = annotations.filter(a => a.getCustomData('changeLogRemoved') === 'true');
|
const nonStandardEntries = annotations.filter(a => a.getCustomData('changeLogRemoved') === 'true');
|
||||||
this._setAnnotationsOpacity(standardEntries, true);
|
this._setAnnotationsOpacity(standardEntries, true);
|
||||||
this._show(standardEntries);
|
this._show(standardEntries);
|
||||||
this._hide(nonStandardEntries);
|
this._hide([...nonStandardEntries, ...highlights]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'DELTA': {
|
case 'DELTA': {
|
||||||
@ -174,7 +175,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
this._setAnnotationsColor(redactions, 'annotationColor');
|
this._setAnnotationsColor(redactions, 'annotationColor');
|
||||||
this._setAnnotationsOpacity(changeLogEntries, true);
|
this._setAnnotationsOpacity(changeLogEntries, true);
|
||||||
this._show(changeLogEntries);
|
this._show(changeLogEntries);
|
||||||
this._hide(nonChangeLogEntries);
|
this._hide([...nonChangeLogEntries, ...highlights]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'REDACTED': {
|
case 'REDACTED': {
|
||||||
@ -182,9 +183,23 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
this._setAnnotationsOpacity(redactions);
|
this._setAnnotationsOpacity(redactions);
|
||||||
this._setAnnotationsColor(redactions, 'redactionColor');
|
this._setAnnotationsColor(redactions, 'redactionColor');
|
||||||
this._show(redactions);
|
this._show(redactions);
|
||||||
this._hide(nonRedactionEntries);
|
this._hide([...nonRedactionEntries, ...highlights]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'TEXT_HIGHLIGHTS': {
|
||||||
|
this._loadingService.start();
|
||||||
|
const textHighlights = await firstValueFrom(this._pdfViewerDataService.loadTextHighlightsFor(this.dossierId, this.fileId));
|
||||||
|
this._hide(annotations);
|
||||||
|
this._fileData.textHighlights = textHighlights;
|
||||||
|
await this._annotationDrawService.drawAnnotations(
|
||||||
|
this.activeViewer,
|
||||||
|
this._fileData.textHighlightAnnotations,
|
||||||
|
this.dossierId,
|
||||||
|
this.fileId,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
this._loadingService.stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this._stampPDF();
|
await this._stampPDF();
|
||||||
|
|||||||
@ -156,6 +156,23 @@ export class AnnotationDrawService {
|
|||||||
compareMode: boolean,
|
compareMode: boolean,
|
||||||
) {
|
) {
|
||||||
const pageNumber = compareMode ? annotationWrapper.pageNumber * 2 - 1 : annotationWrapper.pageNumber;
|
const pageNumber = compareMode ? annotationWrapper.pageNumber * 2 - 1 : annotationWrapper.pageNumber;
|
||||||
|
|
||||||
|
if (annotationWrapper.superType === 'text-highlight') {
|
||||||
|
const rectangleAnnot = new activeViewer.Core.Annotations.RectangleAnnotation();
|
||||||
|
const pageHeight = activeViewer.Core.documentViewer.getPageHeight(pageNumber);
|
||||||
|
const rectangle: IRectangle = annotationWrapper.positions[0];
|
||||||
|
rectangleAnnot.PageNumber = pageNumber;
|
||||||
|
rectangleAnnot.X = rectangle.topLeft.x;
|
||||||
|
rectangleAnnot.Y = pageHeight - (rectangle.topLeft.y + rectangle.height);
|
||||||
|
rectangleAnnot.Width = rectangle.width;
|
||||||
|
rectangleAnnot.Height = rectangle.height;
|
||||||
|
rectangleAnnot.ReadOnly = true;
|
||||||
|
rectangleAnnot.StrokeColor = this.convertColor(activeViewer, annotationWrapper.color);
|
||||||
|
rectangleAnnot.StrokeThickness = 1;
|
||||||
|
rectangleAnnot.Id = annotationWrapper.id;
|
||||||
|
|
||||||
|
return rectangleAnnot;
|
||||||
|
} else {
|
||||||
const dossierTemplateId = this._dossiersService.find(dossierId).dossierTemplateId;
|
const dossierTemplateId = this._dossiersService.find(dossierId).dossierTemplateId;
|
||||||
|
|
||||||
let annotation: Core.Annotations.RectangleAnnotation | Core.Annotations.TextHighlightAnnotation;
|
let annotation: Core.Annotations.RectangleAnnotation | Core.Annotations.TextHighlightAnnotation;
|
||||||
@ -205,6 +222,7 @@ export class AnnotationDrawService {
|
|||||||
annotationWrapper.hidden;
|
annotationWrapper.hidden;
|
||||||
annotation.setCustomData('redact-manager', 'true');
|
annotation.setCustomData('redact-manager', 'true');
|
||||||
annotation.setCustomData('redaction', String(annotationWrapper.previewAnnotation));
|
annotation.setCustomData('redaction', String(annotationWrapper.previewAnnotation));
|
||||||
|
annotation.setCustomData('highlight', String(annotationWrapper.isHighlight));
|
||||||
annotation.setCustomData('skipped', String(annotationWrapper.isSkipped));
|
annotation.setCustomData('skipped', String(annotationWrapper.isSkipped));
|
||||||
annotation.setCustomData('changeLog', String(annotationWrapper.isChangeLogEntry));
|
annotation.setCustomData('changeLog', String(annotationWrapper.isChangeLogEntry));
|
||||||
annotation.setCustomData('changeLogRemoved', String(annotationWrapper.isChangeLogRemoved));
|
annotation.setCustomData('changeLogRemoved', String(annotationWrapper.isChangeLogRemoved));
|
||||||
@ -217,6 +235,7 @@ export class AnnotationDrawService {
|
|||||||
|
|
||||||
return annotation;
|
return annotation;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _rectanglesToQuads(positions: IRectangle[], activeViewer: WebViewerInstance, pageNumber: number): any[] {
|
private _rectanglesToQuads(positions: IRectangle[], activeViewer: WebViewerInstance, pageNumber: number): any[] {
|
||||||
const pageHeight = activeViewer.Core.documentViewer.getPageHeight(pageNumber);
|
const pageHeight = activeViewer.Core.documentViewer.getPageHeight(pageNumber);
|
||||||
|
|||||||
@ -43,6 +43,10 @@ export class ViewModeService {
|
|||||||
return this._viewMode$.value === 'REDACTED';
|
return this._viewMode$.value === 'REDACTED';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isTextHighlights() {
|
||||||
|
return this._viewMode$.value === 'TEXT_HIGHLIGHTS';
|
||||||
|
}
|
||||||
|
|
||||||
get isCompare() {
|
get isCompare() {
|
||||||
return this._compareMode$.value;
|
return this._compareMode$.value;
|
||||||
}
|
}
|
||||||
@ -63,6 +67,10 @@ export class ViewModeService {
|
|||||||
this._switchTo('REDACTED');
|
this._switchTo('REDACTED');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switchToHighlights() {
|
||||||
|
this._switchTo('TEXT_HIGHLIGHTS');
|
||||||
|
}
|
||||||
|
|
||||||
private _switchTo(mode: ViewMode) {
|
private _switchTo(mode: ViewMode) {
|
||||||
this._viewMode$.next(mode);
|
this._viewMode$.next(mode);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,7 +58,13 @@ export class AnnotationProcessingService {
|
|||||||
} else {
|
} else {
|
||||||
// top level filter
|
// top level filter
|
||||||
if (topLevelFilter) {
|
if (topLevelFilter) {
|
||||||
this._createParentFilter(a.superType, filterMap, filters);
|
this._createParentFilter(
|
||||||
|
a.isHighlight ? a.filterKey : a.superType,
|
||||||
|
filterMap,
|
||||||
|
filters,
|
||||||
|
a.isHighlight,
|
||||||
|
a.isHighlight ? a.color : null,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
let parentFilter = filterMap.get(a.superType);
|
let parentFilter = filterMap.get(a.superType);
|
||||||
if (!parentFilter) {
|
if (!parentFilter) {
|
||||||
@ -124,18 +130,28 @@ export class AnnotationProcessingService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
obj.forEach((values, page) => {
|
obj.forEach((values, page) => {
|
||||||
|
if (!values[0].isHighlight) {
|
||||||
obj.set(page, this._sortAnnotations(values));
|
obj.set(page, this._sortAnnotations(values));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createParentFilter(key: string, filterMap: Map<string, INestedFilter>, filters: INestedFilter[]) {
|
private _createParentFilter(
|
||||||
|
key: string,
|
||||||
|
filterMap: Map<string, INestedFilter>,
|
||||||
|
filters: INestedFilter[],
|
||||||
|
skipTranslation = false,
|
||||||
|
color?: string,
|
||||||
|
) {
|
||||||
const filter: INestedFilter = new NestedFilter({
|
const filter: INestedFilter = new NestedFilter({
|
||||||
id: key,
|
id: key,
|
||||||
topLevelFilter: true,
|
topLevelFilter: true,
|
||||||
matches: 1,
|
matches: 1,
|
||||||
label: annotationTypesTranslations[key],
|
label: skipTranslation ? key : annotationTypesTranslations[key],
|
||||||
|
skipTranslation,
|
||||||
|
color,
|
||||||
});
|
});
|
||||||
filterMap.set(key, filter);
|
filterMap.set(key, filter);
|
||||||
filters.push(filter);
|
filters.push(filter);
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { ChangeLegalBasisDialogComponent } from '../dialogs/change-legal-basis-d
|
|||||||
import { RecategorizeImageDialogComponent } from '../dialogs/recategorize-image-dialog/recategorize-image-dialog.component';
|
import { RecategorizeImageDialogComponent } from '../dialogs/recategorize-image-dialog/recategorize-image-dialog.component';
|
||||||
import { ConfirmationDialogComponent, DialogConfig, DialogService, largeDialogConfig } from '@iqser/common-ui';
|
import { ConfirmationDialogComponent, DialogConfig, DialogService, largeDialogConfig } from '@iqser/common-ui';
|
||||||
import { ResizeAnnotationDialogComponent } from '../dialogs/resize-annotation-dialog/resize-annotation-dialog.component';
|
import { ResizeAnnotationDialogComponent } from '../dialogs/resize-annotation-dialog/resize-annotation-dialog.component';
|
||||||
|
import { HighlightActionDialogComponent } from '../screens/file-preview-screen/dialogs/highlight-action-dialog/highlight-action-dialog.component';
|
||||||
|
|
||||||
type DialogType =
|
type DialogType =
|
||||||
| 'confirm'
|
| 'confirm'
|
||||||
@ -23,7 +24,8 @@ type DialogType =
|
|||||||
| 'removeAnnotations'
|
| 'removeAnnotations'
|
||||||
| 'resizeAnnotation'
|
| 'resizeAnnotation'
|
||||||
| 'forceAnnotation'
|
| 'forceAnnotation'
|
||||||
| 'manualAnnotation';
|
| 'manualAnnotation'
|
||||||
|
| 'highlightAction';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DossiersDialogService extends DialogService<DialogType> {
|
export class DossiersDialogService extends DialogService<DialogType> {
|
||||||
@ -67,6 +69,9 @@ export class DossiersDialogService extends DialogService<DialogType> {
|
|||||||
component: ManualAnnotationDialogComponent,
|
component: ManualAnnotationDialogComponent,
|
||||||
dialogConfig: { autoFocus: true },
|
dialogConfig: { autoFocus: true },
|
||||||
},
|
},
|
||||||
|
highlightAction: {
|
||||||
|
component: HighlightActionDialogComponent,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(protected readonly _dialog: MatDialog) {
|
constructor(protected readonly _dialog: MatDialog) {
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { forkJoin, Observable, of } from 'rxjs';
|
|||||||
import { catchError, map, tap } from 'rxjs/operators';
|
import { catchError, map, tap } from 'rxjs/operators';
|
||||||
import { FileDataModel } from '@models/file/file-data.model';
|
import { FileDataModel } from '@models/file/file-data.model';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import { Dictionary, File, IRedactionLog, IViewedPage } from '@red/domain';
|
import { Dictionary, File, IRedactionLog, IViewedPage, TextHighlightResponse } from '@red/domain';
|
||||||
import { RedactionLogService } from './redaction-log.service';
|
import { RedactionLogService } from './redaction-log.service';
|
||||||
import { ViewedPagesService } from '@services/entity-services/viewed-pages.service';
|
import { ViewedPagesService } from '@services/entity-services/viewed-pages.service';
|
||||||
import { UserPreferenceService } from '@services/user-preference.service';
|
import { UserPreferenceService } from '@services/user-preference.service';
|
||||||
@ -11,12 +11,14 @@ import { FilePreviewStateService } from '../screens/file-preview-screen/services
|
|||||||
import { Toaster } from '@iqser/common-ui';
|
import { Toaster } from '@iqser/common-ui';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||||
|
import { TextHighlightService } from './text-highlight.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PdfViewerDataService {
|
export class PdfViewerDataService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _permissionsService: PermissionsService,
|
private readonly _permissionsService: PermissionsService,
|
||||||
private readonly _redactionLogService: RedactionLogService,
|
private readonly _redactionLogService: RedactionLogService,
|
||||||
|
private readonly _textHighlightService: TextHighlightService,
|
||||||
private readonly _viewedPagesService: ViewedPagesService,
|
private readonly _viewedPagesService: ViewedPagesService,
|
||||||
private readonly _userPreferenceService: UserPreferenceService,
|
private readonly _userPreferenceService: UserPreferenceService,
|
||||||
private readonly _stateService: FilePreviewStateService,
|
private readonly _stateService: FilePreviewStateService,
|
||||||
@ -31,6 +33,10 @@ export class PdfViewerDataService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadTextHighlightsFor(dossierId: string, fileId: string): Observable<TextHighlightResponse> {
|
||||||
|
return this._textHighlightService.getTextHighlights(dossierId, fileId).pipe(catchError(() => of({})));
|
||||||
|
}
|
||||||
|
|
||||||
loadDataFor(newFile: File): Observable<FileDataModel> {
|
loadDataFor(newFile: File): Observable<FileDataModel> {
|
||||||
const redactionLog$ = this.loadRedactionLogFor(newFile.dossierId, newFile.fileId);
|
const redactionLog$ = this.loadRedactionLogFor(newFile.dossierId, newFile.fileId);
|
||||||
const viewedPages$ = this.getViewedPagesFor(newFile);
|
const viewedPages$ = this.getViewedPagesFor(newFile);
|
||||||
|
|||||||
@ -0,0 +1,40 @@
|
|||||||
|
import { Injectable, Injector } from '@angular/core';
|
||||||
|
import { GenericService, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
|
||||||
|
import { TextHighlightOperation, TextHighlightRequest, TextHighlightResponse } from '@red/domain';
|
||||||
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
|
import { tap } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class TextHighlightService extends GenericService<unknown> {
|
||||||
|
constructor(protected readonly _injector: Injector, private readonly _toaster: Toaster) {
|
||||||
|
super(_injector, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
@Validate()
|
||||||
|
getTextHighlights(@RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
|
||||||
|
const request: TextHighlightRequest = {
|
||||||
|
dossierId,
|
||||||
|
fileId,
|
||||||
|
operation: TextHighlightOperation.INFO,
|
||||||
|
};
|
||||||
|
|
||||||
|
return this._post<TextHighlightResponse>(request, 'texthighlights-conversion');
|
||||||
|
}
|
||||||
|
|
||||||
|
@Validate()
|
||||||
|
performHighlightsAction(
|
||||||
|
@RequiredParam() dossierId: string,
|
||||||
|
@RequiredParam() fileId: string,
|
||||||
|
@RequiredParam() colors: string[],
|
||||||
|
@RequiredParam() operation: TextHighlightOperation,
|
||||||
|
) {
|
||||||
|
const request: TextHighlightRequest = { dossierId, fileId, colors, operation };
|
||||||
|
return this._post<TextHighlightResponse>(request, 'texthighlights-conversion').pipe(
|
||||||
|
tap(() => {
|
||||||
|
this._toaster.success(_('highlight-action-dialog.success'), { params: { operation } });
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,5 +6,5 @@
|
|||||||
[class.request]="isRequest"
|
[class.request]="isRequest"
|
||||||
class="icon"
|
class="icon"
|
||||||
>
|
>
|
||||||
<span>{{ label || dictionary.label.charAt(0) }}</span>
|
<span>{{ label || dictionary?.label?.charAt(0) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -60,10 +60,13 @@
|
|||||||
<div *ngIf="filter.id === 'comment'">
|
<div *ngIf="filter.id === 'comment'">
|
||||||
<mat-icon svgIcon="red:comment"></mat-icon>
|
<mat-icon svgIcon="red:comment"></mat-icon>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<redaction-annotation-icon *ngIf="filter.color" [color]="filter.color" [label]="''" type="square"></redaction-annotation-icon>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="filter.icon">
|
<ng-container *ngIf="filter.icon">
|
||||||
<mat-icon [svgIcon]="filter.icon"></mat-icon>
|
<mat-icon [svgIcon]="filter.icon"></mat-icon>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
{{ filter.label | translate }}
|
<ng-container *ngIf="filter.skipTranslation; else translate"> {{ filter.label }}</ng-container>
|
||||||
|
<ng-template #translate>{{ filter.label | translate }} </ng-template>
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { AnnotationSuperType } from '../models/file/annotation.wrapper';
|
import { AnnotationSuperType } from '@models/file/annotation.wrapper';
|
||||||
|
|
||||||
export const annotationTypesTranslations: { [key in AnnotationSuperType]: string } = {
|
export const annotationTypesTranslations: { [key in AnnotationSuperType]: string } = {
|
||||||
|
'text-highlight': _('annotation-type.text-highlight'),
|
||||||
'declined-suggestion': _('annotation-type.declined-suggestion'),
|
'declined-suggestion': _('annotation-type.declined-suggestion'),
|
||||||
hint: _('annotation-type.hint'),
|
hint: _('annotation-type.hint'),
|
||||||
'ignored-hint': _('annotation-type.ignored-hint'),
|
'ignored-hint': _('annotation-type.ignored-hint'),
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { AnnotationSuperType } from '../../models/file/annotation.wrapper';
|
import { AnnotationSuperType } from '../../models/file/annotation.wrapper';
|
||||||
|
|
||||||
export const SuperTypeSorter: { [key in AnnotationSuperType]: number } = {
|
export const SuperTypeSorter: { [key in AnnotationSuperType]: number } = {
|
||||||
|
'text-highlight': 100,
|
||||||
'suggestion-change-legal-basis': 14,
|
'suggestion-change-legal-basis': 14,
|
||||||
'suggestion-force-redaction': 15,
|
'suggestion-force-redaction': 15,
|
||||||
'suggestion-force-hint': 16,
|
'suggestion-force-hint': 16,
|
||||||
|
|||||||
@ -310,7 +310,8 @@
|
|||||||
"suggestion-recategorize-image": "Suggested recategorize image",
|
"suggestion-recategorize-image": "Suggested recategorize image",
|
||||||
"suggestion-remove": "Suggested local removal",
|
"suggestion-remove": "Suggested local removal",
|
||||||
"suggestion-remove-dictionary": "Suggested dictionary removal",
|
"suggestion-remove-dictionary": "Suggested dictionary removal",
|
||||||
"suggestion-resize": "Suggested Resize"
|
"suggestion-resize": "Suggested Resize",
|
||||||
|
"text-highlight": "Highlight"
|
||||||
},
|
},
|
||||||
"annotations": "Annotations",
|
"annotations": "Annotations",
|
||||||
"assign-dossier-owner": {
|
"assign-dossier-owner": {
|
||||||
@ -393,6 +394,7 @@
|
|||||||
},
|
},
|
||||||
"header": "Edit Redaction Reason"
|
"header": "Edit Redaction Reason"
|
||||||
},
|
},
|
||||||
|
"color": "Color",
|
||||||
"comments": {
|
"comments": {
|
||||||
"add-comment": "Enter comment",
|
"add-comment": "Enter comment",
|
||||||
"comments": "{count} {count, plural, one{comment} other{comments}}",
|
"comments": "{count} {count, plural, one{comment} other{comments}}",
|
||||||
@ -1207,6 +1209,10 @@
|
|||||||
"exclude-pages": "Exclude pages from redaction",
|
"exclude-pages": "Exclude pages from redaction",
|
||||||
"excluded-from-redaction": "excluded",
|
"excluded-from-redaction": "excluded",
|
||||||
"fullscreen": "Full Screen (F)",
|
"fullscreen": "Full Screen (F)",
|
||||||
|
"highlights": {
|
||||||
|
"convert": "Convert highlights",
|
||||||
|
"remove": "Remove highlights"
|
||||||
|
},
|
||||||
"last-reviewer": "Last Reviewed by:",
|
"last-reviewer": "Last Reviewed by:",
|
||||||
"no-data": {
|
"no-data": {
|
||||||
"title": "There have been no changes to this page."
|
"title": "There have been no changes to this page."
|
||||||
@ -1254,8 +1260,13 @@
|
|||||||
"put-back": "Undo",
|
"put-back": "Undo",
|
||||||
"removed-from-redaction": "Removed from redaction"
|
"removed-from-redaction": "Removed from redaction"
|
||||||
},
|
},
|
||||||
|
"highlights": {
|
||||||
|
"label": "Highlights"
|
||||||
|
},
|
||||||
"is-excluded": "Redaction is disabled for this document."
|
"is-excluded": "Redaction is disabled for this document."
|
||||||
},
|
},
|
||||||
|
"text-highlights": "Highlights",
|
||||||
|
"text-highlights-tooltip": "Shows all text-highlights and allows removing or importing them as redactions",
|
||||||
"toggle-analysis": {
|
"toggle-analysis": {
|
||||||
"disable": "Disable redaction",
|
"disable": "Disable redaction",
|
||||||
"enable": "Enable for redaction",
|
"enable": "Enable for redaction",
|
||||||
@ -1363,6 +1374,30 @@
|
|||||||
"text": "Help Mode",
|
"text": "Help Mode",
|
||||||
"welcome-to-help-mode": "<b> Welcome to Help Mode! <br> Clicking on interactive elements will open info about them in new tab. </b>"
|
"welcome-to-help-mode": "<b> Welcome to Help Mode! <br> Clicking on interactive elements will open info about them in new tab. </b>"
|
||||||
},
|
},
|
||||||
|
"highlight-action-dialog": {
|
||||||
|
"actions": {
|
||||||
|
"cancel": "Cancel"
|
||||||
|
},
|
||||||
|
"convert": {
|
||||||
|
"confirmation": "All highlights in the document will be converted",
|
||||||
|
"details": "All highlights from the document will be converted to Imported Redactions, using the color set up in the Default Colors section of the app.",
|
||||||
|
"save": "Convert Highlights",
|
||||||
|
"title": "Convert highlights to imported redactions"
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"color": {
|
||||||
|
"label": "Highlight HEX Color"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"remove": {
|
||||||
|
"confirmation": "All highlights in this HEX Color will be removed from the document",
|
||||||
|
"details": "Removing highlights from the document will delete all the rectangles and leave a white background behind the highlighted text.",
|
||||||
|
"save": "Remove Highlights",
|
||||||
|
"title": "Remove highlights"
|
||||||
|
},
|
||||||
|
"success": "{operation, select, CONVERT{Converting} REMOVE{Removing} other{}} highlights in progress..."
|
||||||
|
},
|
||||||
|
"highlights": "{color} - {length} {length, plural, one{highlight} other{highlights}}",
|
||||||
"hint": "Hint",
|
"hint": "Hint",
|
||||||
"image-category": {
|
"image-category": {
|
||||||
"formula": "Formula",
|
"formula": "Formula",
|
||||||
@ -1697,6 +1732,7 @@
|
|||||||
"placeholder": "Search documents...",
|
"placeholder": "Search documents...",
|
||||||
"this-dossier": "in this dossier"
|
"this-dossier": "in this dossier"
|
||||||
},
|
},
|
||||||
|
"size": "Size",
|
||||||
"smtp-auth-config": {
|
"smtp-auth-config": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
|||||||
@ -2,13 +2,13 @@
|
|||||||
@use 'common-mixins';
|
@use 'common-mixins';
|
||||||
|
|
||||||
.NEW {
|
.NEW {
|
||||||
stroke: variables.$grey-5;
|
stroke: var(--iqser-grey-5);
|
||||||
background-color: variables.$grey-5;
|
background-color: var(--iqser-grey-5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.UNPROCESSED {
|
.UNPROCESSED {
|
||||||
stroke: variables.$grey-3;
|
stroke: var(--iqser-grey-3);
|
||||||
background-color: variables.$grey-3;
|
background-color: var(--iqser-grey-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.UNDER_REVIEW,
|
.UNDER_REVIEW,
|
||||||
@ -70,8 +70,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.INACTIVE {
|
.INACTIVE {
|
||||||
stroke: variables.$grey-5;
|
stroke: var(--iqser-grey-5);
|
||||||
background-color: variables.$grey-5;
|
background-color: var(--iqser-grey-5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.MANAGER,
|
.MANAGER,
|
||||||
@ -79,3 +79,22 @@
|
|||||||
stroke: variables.$primary;
|
stroke: variables.$primary;
|
||||||
background-color: variables.$primary;
|
background-color: variables.$primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.workload-separator {
|
||||||
|
border-bottom: 1px solid var(--iqser-separator);
|
||||||
|
height: 32px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background-color: var(--iqser-grey-6);
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> div:not(:last-child) {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
Subproject commit dd87dd6821ecc8d591b03191964e77fb5fc6e8f8
|
Subproject commit 17008f44757a29ae3d09581a94520c2a05670432
|
||||||
@ -20,3 +20,4 @@ export * from './lib/signature';
|
|||||||
export * from './lib/legal-basis';
|
export * from './lib/legal-basis';
|
||||||
export * from './lib/dossier-stats';
|
export * from './lib/dossier-stats';
|
||||||
export * from './lib/dossier-state';
|
export * from './lib/dossier-state';
|
||||||
|
export * from './lib/text-highlight';
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
export type ViewMode = 'STANDARD' | 'DELTA' | 'REDACTED';
|
export type ViewMode = 'STANDARD' | 'DELTA' | 'REDACTED' | 'TEXT_HIGHLIGHTS';
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { IRectangle } from '../geometry/rectangle';
|
||||||
|
|
||||||
|
export interface ImportedRedaction {
|
||||||
|
id: string;
|
||||||
|
positions: IRectangle[];
|
||||||
|
}
|
||||||
4
libs/red-domain/src/lib/text-highlight/index.ts
Normal file
4
libs/red-domain/src/lib/text-highlight/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from './imported-redaction';
|
||||||
|
export * from './text-highlight-operation';
|
||||||
|
export * from './text-highlight.response';
|
||||||
|
export * from './text-highlight.request';
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
export enum TextHighlightOperation {
|
||||||
|
REMOVE = 'REMOVE',
|
||||||
|
CONVERT = 'CONVERT',
|
||||||
|
INFO = 'INFO',
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
import { TextHighlightOperation } from './text-highlight-operation';
|
||||||
|
|
||||||
|
export interface TextHighlightRequest {
|
||||||
|
dossierId: string;
|
||||||
|
fileId: string;
|
||||||
|
operation: TextHighlightOperation;
|
||||||
|
colors?: string[];
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { ImportedRedaction } from './imported-redaction';
|
||||||
|
import { TextHighlightOperation } from './text-highlight-operation';
|
||||||
|
|
||||||
|
export interface TextHighlightResponse {
|
||||||
|
dossierId?: string;
|
||||||
|
fileId?: string;
|
||||||
|
operation?: TextHighlightOperation;
|
||||||
|
redactionPerColor?: { [key: string]: ImportedRedaction[] };
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user