Merge remote-tracking branch 'origin/master' into RED-3313
This commit is contained in:
commit
d4cbf647ad
@ -1,9 +1,10 @@
|
||||
import { annotationTypesTranslations } from '../../translations/annotation-types-translations';
|
||||
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';
|
||||
|
||||
export type AnnotationSuperType =
|
||||
| 'text-highlight'
|
||||
| 'suggestion-change-legal-basis'
|
||||
| 'suggestion-recategorize-image'
|
||||
| 'suggestion-add-dictionary'
|
||||
@ -121,6 +122,10 @@ export class AnnotationWrapper {
|
||||
}
|
||||
|
||||
get filterKey() {
|
||||
if (this.isHighlight) {
|
||||
return this.color;
|
||||
}
|
||||
|
||||
return this.topLevelFilter ? this.superType : this.superType + this.type;
|
||||
}
|
||||
|
||||
@ -154,6 +159,10 @@ export class AnnotationWrapper {
|
||||
return this.superType === 'hint';
|
||||
}
|
||||
|
||||
get isHighlight() {
|
||||
return this.superType === 'text-highlight';
|
||||
}
|
||||
|
||||
get isIgnoredHint() {
|
||||
return this.superType === 'ignored-hint';
|
||||
}
|
||||
@ -235,6 +244,34 @@ export class AnnotationWrapper {
|
||||
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() {
|
||||
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) {
|
||||
const annotationWrapper = new AnnotationWrapper();
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
IViewedPage,
|
||||
LogEntryStatus,
|
||||
ManualRedactionType,
|
||||
TextHighlightResponse,
|
||||
ViewMode,
|
||||
} from '@red/domain';
|
||||
import { AnnotationWrapper } from './annotation.wrapper';
|
||||
@ -19,6 +20,8 @@ export class FileDataModel {
|
||||
allAnnotations: AnnotationWrapper[] = [];
|
||||
readonly hasChangeLog$ = new BehaviorSubject<boolean>(false);
|
||||
missingTypes = new Set<string>();
|
||||
_textHighlightResponse: TextHighlightResponse;
|
||||
textHighlightAnnotations: AnnotationWrapper[] = [];
|
||||
|
||||
constructor(
|
||||
private readonly _file: File,
|
||||
@ -39,14 +42,31 @@ export class FileDataModel {
|
||||
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) {
|
||||
if (viewMode === 'TEXT_HIGHLIGHTS') {
|
||||
return this.textHighlightAnnotations;
|
||||
}
|
||||
return this.allAnnotations.filter(annotation => {
|
||||
if (viewMode === 'STANDARD') {
|
||||
return !annotation.isChangeLogRemoved;
|
||||
} else if (viewMode === 'DELTA') {
|
||||
return annotation.isChangeLogEntry;
|
||||
} else {
|
||||
return annotation.isRedacted;
|
||||
return annotation.previewAnnotation;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -76,7 +76,6 @@ const routes: Routes = [
|
||||
path: 'rules',
|
||||
component: BaseDossierTemplateScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
canDeactivate: [PendingChangesGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard],
|
||||
},
|
||||
|
||||
@ -41,7 +41,7 @@ export class AddEditDictionaryDialogComponent extends BaseDialogComponent {
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
private readonly _data: { readonly dictionary: Dictionary; readonly dossierTemplateId: string },
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
super(_injector, _dialogRef, !!_data.dictionary);
|
||||
this.form = this._getForm(this.dictionary);
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
this.hasColor$ = this._colorEmpty$;
|
||||
|
||||
@ -27,7 +27,7 @@ export class AddEditDossierAttributeDialogComponent extends BaseDialogComponent
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
readonly data: { readonly dossierAttribute: IDossierAttributeConfig; dossierTemplateId: string },
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
super(_injector, _dialogRef, !!data.dossierAttribute);
|
||||
this.form = this._getForm(this.dossierAttribute);
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ export class AddEditDossierStateDialogComponent extends BaseDialogComponent {
|
||||
protected readonly _dialogRef: MatDialogRef<AddEditDossierStateDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) readonly data: DialogData,
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
super(_injector, _dialogRef, !!data.dossierState);
|
||||
this.form = this.#getForm();
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent {
|
||||
private readonly _loadingService: LoadingService,
|
||||
@Inject(MAT_DIALOG_DATA) readonly dossierTemplateId: string,
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
super(_injector, _dialogRef, !!dossierTemplateId);
|
||||
this.dossierTemplate = this._dossierTemplatesService.find(this.dossierTemplateId);
|
||||
this.form = this._getForm();
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
|
||||
@ -34,7 +34,7 @@ export class AddEditFileAttributeDialogComponent extends BaseDialogComponent {
|
||||
numberOfFilterableAttrs: number;
|
||||
},
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
super(_injector, _dialogRef, !!data.fileAttribute);
|
||||
this.canSetDisplayed = data.numberOfDisplayedAttrs < this.DISPLAYED_FILTERABLE_LIMIT || data.fileAttribute?.displayedInFileList;
|
||||
this.canSetFilterable = data.numberOfFilterableAttrs < this.DISPLAYED_FILTERABLE_LIMIT || data.fileAttribute?.filterable;
|
||||
this.form = this._getForm(this.fileAttribute);
|
||||
|
||||
@ -10,24 +10,15 @@ import { BaseDialogComponent } from '@iqser/common-ui';
|
||||
styleUrls: ['./add-edit-user-dialog.component.scss'],
|
||||
})
|
||||
export class AddEditUserDialogComponent extends BaseDialogComponent {
|
||||
@ViewChild(UserDetailsComponent) private readonly _userDetailsComponent: UserDetailsComponent;
|
||||
|
||||
resettingPassword = false;
|
||||
@ViewChild(UserDetailsComponent) private readonly _userDetailsComponent: UserDetailsComponent;
|
||||
|
||||
constructor(
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<AddEditUserDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) readonly user: User,
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
}
|
||||
|
||||
toggleResetPassword() {
|
||||
this.resettingPassword = !this.resettingPassword;
|
||||
}
|
||||
|
||||
async save(): Promise<void> {
|
||||
await this._userDetailsComponent.save();
|
||||
super(_injector, _dialogRef, !!user);
|
||||
}
|
||||
|
||||
get changed(): boolean {
|
||||
@ -38,6 +29,14 @@ export class AddEditUserDialogComponent extends BaseDialogComponent {
|
||||
return this._userDetailsComponent.valid;
|
||||
}
|
||||
|
||||
toggleResetPassword() {
|
||||
this.resettingPassword = !this.resettingPassword;
|
||||
}
|
||||
|
||||
async save(): Promise<void> {
|
||||
await this._userDetailsComponent.save();
|
||||
}
|
||||
|
||||
closeDialog(event) {
|
||||
this._dialogRef.close(event);
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ export class EditColorDialogComponent extends BaseDialogComponent {
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
readonly data: IEditColorData,
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
super(_injector, _dialogRef, true);
|
||||
this._dossierTemplateId = data.dossierTemplateId;
|
||||
|
||||
this.form = this._getForm();
|
||||
|
||||
@ -28,7 +28,7 @@ export class FileAttributesConfigurationsDialogComponent extends BaseDialogCompo
|
||||
protected readonly _dialogRef: MatDialogRef<FileAttributesConfigurationsDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) private _data: { config: IFileAttributesConfig; dossierTemplateId: string },
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
super(_injector, _dialogRef, true);
|
||||
this.form = this._getForm();
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ export class AddEditJustificationDialogComponent extends BaseDialogComponent {
|
||||
protected readonly _dialogRef: MatDialogRef<AddEditJustificationDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: { justification?: Justification; dossierTemplateId: string },
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
super(_injector, _dialogRef, !!data.justification);
|
||||
|
||||
this.form = this._getForm();
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
|
||||
@ -4,8 +4,9 @@ import { RouterModule } from '@angular/router';
|
||||
import { SharedModule } from '../../../shared/shared.module';
|
||||
import { RulesScreenComponent } from './rules-screen/rules-screen.component';
|
||||
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
|
||||
import { PendingChangesGuard } from '../../../../guards/can-deactivate.guard';
|
||||
|
||||
const routes = [{ path: '', component: RulesScreenComponent }];
|
||||
const routes = [{ path: '', component: RulesScreenComponent, canDeactivate: [PendingChangesGuard] }];
|
||||
|
||||
@NgModule({
|
||||
declarations: [RulesScreenComponent],
|
||||
|
||||
@ -31,7 +31,7 @@ export class ChangeLegalBasisDialogComponent extends BaseDialogComponent impleme
|
||||
protected readonly _dialogRef: MatDialogRef<ChangeLegalBasisDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) private readonly _data: { annotations: AnnotationWrapper[]; dossier: Dossier },
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
super(_injector, _dialogRef, true);
|
||||
this.form = this._getForm();
|
||||
}
|
||||
|
||||
|
||||
@ -55,7 +55,7 @@ export class EditDossierDialogComponent extends BaseDialogComponent implements A
|
||||
section?: Section;
|
||||
},
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
super(_injector, _dialogRef, true);
|
||||
this.navItems = [
|
||||
{
|
||||
key: 'dossierInfo',
|
||||
|
||||
@ -12,6 +12,8 @@ import { SearchScreenComponent } from './screens/search-screen/search-screen.com
|
||||
import { OverlayModule } from '@angular/cdk/overlay';
|
||||
import { SharedDossiersModule } from './shared/shared-dossiers.module';
|
||||
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';
|
||||
import { ColorPickerModule } from 'ngx-color-picker';
|
||||
|
||||
const screens = [SearchScreenComponent];
|
||||
|
||||
@ -22,12 +24,21 @@ const dialogs = [
|
||||
ResizeAnnotationDialogComponent,
|
||||
ChangeLegalBasisDialogComponent,
|
||||
RecategorizeImageDialogComponent,
|
||||
HighlightActionDialogComponent,
|
||||
];
|
||||
|
||||
const components = [...screens, ...dialogs];
|
||||
|
||||
@NgModule({
|
||||
declarations: [...components],
|
||||
imports: [CommonModule, SharedModule, SharedDossiersModule, FileUploadDownloadModule, DossiersRoutingModule, OverlayModule],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
SharedDossiersModule,
|
||||
FileUploadDownloadModule,
|
||||
DossiersRoutingModule,
|
||||
OverlayModule,
|
||||
ColorPickerModule,
|
||||
],
|
||||
})
|
||||
export class DossiersModule {}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<div>
|
||||
<strong>{{ annotation.typeLabel | translate }}</strong>
|
||||
</div>
|
||||
<div *ngIf="annotation?.type !== 'manual'">
|
||||
<div *ngIf="annotation.type !== 'manual' && !annotation.isHighlight">
|
||||
<strong>
|
||||
<span>{{ annotation.descriptor | translate }}</span
|
||||
>: </strong
|
||||
@ -14,6 +14,12 @@
|
||||
<div *ngIf="annotation.shortContent && !annotation.isHint">
|
||||
<strong><span translate="content"></span>: </strong>{{ annotation.shortContent }}
|
||||
</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 class="active-icon-marker-container">
|
||||
|
||||
@ -1,46 +1,51 @@
|
||||
<div
|
||||
(click)="annotationClicked(annotation, $event)"
|
||||
*ngFor="let annotation of annotations"
|
||||
[attr.annotation-id]="annotation.id"
|
||||
[attr.annotation-page]="activeViewerPage"
|
||||
[class.active]="isSelected(annotation.annotationId)"
|
||||
[class.multi-select-active]="multiSelectService.active$ | async"
|
||||
class="annotation-wrapper"
|
||||
>
|
||||
<div class="active-bar-marker"></div>
|
||||
|
||||
<div [class.removed]="annotation.isChangeLogRemoved" class="annotation">
|
||||
<redaction-annotation-card
|
||||
[annotation]="annotation"
|
||||
[isSelected]="isSelected(annotation.annotationId)"
|
||||
[matTooltip]="annotation.content"
|
||||
matTooltipPosition="above"
|
||||
></redaction-annotation-card>
|
||||
|
||||
<div class="actions-wrapper">
|
||||
<div
|
||||
(click)="comments.toggleExpandComments($event)"
|
||||
[matTooltip]="'comments.comments' | translate: { count: annotation.comments?.length }"
|
||||
class="comments-counter"
|
||||
matTooltipPosition="above"
|
||||
>
|
||||
<mat-icon svgIcon="red:comment"></mat-icon>
|
||||
{{ annotation.comments.length }}
|
||||
</div>
|
||||
|
||||
<div *ngIf="multiSelectService.inactive$ | async" class="actions">
|
||||
<ng-container
|
||||
[ngTemplateOutletContext]="{ annotation: annotation }"
|
||||
[ngTemplateOutlet]="annotationActionsTemplate"
|
||||
></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<redaction-comments #comments [annotation]="annotation"></redaction-comments>
|
||||
<ng-container *ngFor="let annotation of annotations; let idx = index">
|
||||
<div *ngIf="showHighlightGroup(idx) as highlightGroup" class="workload-separator">
|
||||
<redaction-highlights-separator [annotation]="annotation" [highlightGroup]="highlightGroup"></redaction-highlights-separator>
|
||||
</div>
|
||||
|
||||
<redaction-annotation-details [annotation]="annotation" [isSelected]="isSelected(annotation.id)"></redaction-annotation-details>
|
||||
</div>
|
||||
<div
|
||||
(click)="annotationClicked(annotation, $event)"
|
||||
[attr.annotation-id]="annotation.id"
|
||||
[attr.annotation-page]="activeViewerPage"
|
||||
[class.active]="isSelected(annotation.annotationId)"
|
||||
[class.multi-select-active]="multiSelectService.active$ | async"
|
||||
class="annotation-wrapper"
|
||||
>
|
||||
<div class="active-bar-marker"></div>
|
||||
|
||||
<div [class.removed]="annotation.isChangeLogRemoved" class="annotation">
|
||||
<redaction-annotation-card
|
||||
[annotation]="annotation"
|
||||
[isSelected]="isSelected(annotation.annotationId)"
|
||||
[matTooltip]="annotation.content"
|
||||
matTooltipPosition="above"
|
||||
></redaction-annotation-card>
|
||||
|
||||
<div *ngIf="!annotation.isHighlight" class="actions-wrapper">
|
||||
<div
|
||||
(click)="comments.toggleExpandComments($event)"
|
||||
[matTooltip]="'comments.comments' | translate: { count: annotation.comments?.length }"
|
||||
class="comments-counter"
|
||||
matTooltipPosition="above"
|
||||
>
|
||||
<mat-icon svgIcon="red:comment"></mat-icon>
|
||||
{{ annotation.comments.length }}
|
||||
</div>
|
||||
|
||||
<div *ngIf="multiSelectService.inactive$ | async" class="actions">
|
||||
<ng-container
|
||||
[ngTemplateOutletContext]="{ annotation: annotation }"
|
||||
[ngTemplateOutlet]="annotationActionsTemplate"
|
||||
></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<redaction-comments #comments [annotation]="annotation"></redaction-comments>
|
||||
</div>
|
||||
|
||||
<redaction-annotation-details [annotation]="annotation" [isSelected]="isSelected(annotation.id)"></redaction-annotation-details>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="annotationReferencesService.annotation$ | async">
|
||||
<redaction-annotation-references-list
|
||||
|
||||
@ -3,9 +3,11 @@ import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { FilterService, IqserEventTarget } from '@iqser/common-ui';
|
||||
import { MultiSelectService } from '../../services/multi-select.service';
|
||||
import { AnnotationReferencesService } from '../../services/annotation-references.service';
|
||||
import { ViewModeService } from '../../services/view-mode.service';
|
||||
import { FilePreviewStateService } from '../../services/file-preview-state.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 { TextHighlightsGroup } from '@red/domain';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-annotations-list',
|
||||
@ -23,19 +25,25 @@ export class AnnotationsListComponent implements OnChanges {
|
||||
@Output() readonly selectAnnotations = new EventEmitter<AnnotationWrapper[]>();
|
||||
@Output() readonly deselectAnnotations = new EventEmitter<AnnotationWrapper[]>();
|
||||
|
||||
highlightGroups$ = new BehaviorSubject<TextHighlightsGroup[]>([]);
|
||||
|
||||
constructor(
|
||||
readonly multiSelectService: MultiSelectService,
|
||||
readonly viewModeService: ViewModeService,
|
||||
readonly annotationReferencesService: AnnotationReferencesService,
|
||||
private readonly _filterService: FilterService,
|
||||
private readonly _state: FilePreviewStateService,
|
||||
private readonly _userPreferenceService: UserPreferenceService,
|
||||
private readonly _viewModeService: ViewModeService,
|
||||
private readonly _dialogService: DossiersDialogService,
|
||||
) {}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.annotations && this.annotations) {
|
||||
if (changes.annotations && this.annotations && !this._viewModeService.isTextHighlights) {
|
||||
this.annotations = this.annotations.sort(this.annotationsPositionCompare);
|
||||
}
|
||||
|
||||
if (this._viewModeService.isTextHighlights) {
|
||||
this._updateHighlightGroups();
|
||||
}
|
||||
}
|
||||
|
||||
annotationClicked(annotation: AnnotationWrapper, $event: MouseEvent): void {
|
||||
@ -80,4 +88,28 @@ export class AnnotationsListComponent implements OnChanges {
|
||||
return first.x < second.y ? -1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
showHighlightGroup(idx: number): TextHighlightsGroup {
|
||||
return this._viewModeService.isTextHighlights && this.highlightGroups$.value.find(h => h.startIdx === idx);
|
||||
}
|
||||
|
||||
private _updateHighlightGroups(): void {
|
||||
if (!this.annotations?.length) {
|
||||
return;
|
||||
}
|
||||
const highlightGroups: TextHighlightsGroup[] = [];
|
||||
let lastGroup: TextHighlightsGroup;
|
||||
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>
|
||||
|
||||
<ng-template #selectAndFilter>
|
||||
<div class="right-title heading" translate="file-preview.tabs.annotations.label">
|
||||
<div class="right-title heading">
|
||||
{{ title$ | async | translate }}
|
||||
<div>
|
||||
<div
|
||||
(click)="multiSelectService.activate()"
|
||||
@ -111,16 +112,18 @@
|
||||
|
||||
<div style="overflow: hidden; width: 100%">
|
||||
<div
|
||||
*ngIf="(isHighlights$ | async) === false"
|
||||
[attr.anotation-page-header]="activeViewerPage"
|
||||
[class.padding-left-0]="currentPageIsExcluded"
|
||||
[hidden]="excludedPagesService.shown$ | async"
|
||||
class="page-separator"
|
||||
class="workload-separator"
|
||||
>
|
||||
<span *ngIf="!!activeViewerPage" class="flex-align-items-center">
|
||||
<iqser-circle-button
|
||||
(action)="excludedPagesService.toggle()"
|
||||
*ngIf="currentPageIsExcluded"
|
||||
[tooltip]="'file-preview.excluded-from-redaction' | translate | capitalize"
|
||||
class="excluded"
|
||||
icon="red:exclude-pages"
|
||||
tooltipPosition="above"
|
||||
></iqser-circle-button>
|
||||
@ -160,7 +163,7 @@
|
||||
<ng-container *ngIf="activeViewerPage && !displayedAnnotations.get(activeViewerPage)?.length">
|
||||
<iqser-empty-state
|
||||
[horizontalPadding]="24"
|
||||
[text]="(displayedPages.length ? noDataI18NKey : resetFiltersI18NKey) | translate"
|
||||
[text]="'file-preview.no-data.title' | translate"
|
||||
[verticalPadding]="40"
|
||||
icon="iqser:document"
|
||||
>
|
||||
|
||||
@ -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 {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
@ -157,7 +138,7 @@
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@ -34,6 +34,7 @@ import { MultiSelectService } from '../../services/multi-select.service';
|
||||
import { DocumentInfoService } from '../../services/document-info.service';
|
||||
import { SkippedService } from '../../services/skipped.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'];
|
||||
@ -48,8 +49,6 @@ const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
|
||||
export class FileWorkloadComponent {
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly noDataI18NKey = _('file-preview.no-data.title');
|
||||
readonly resetFiltersI18NKey = _('file-preview.reset-filters');
|
||||
|
||||
displayedAnnotations = new Map<number, AnnotationWrapper[]>();
|
||||
@Input() selectedAnnotations: AnnotationWrapper[];
|
||||
@ -70,6 +69,8 @@ export class FileWorkloadComponent {
|
||||
readonly multiSelectActive$: Observable<boolean>;
|
||||
readonly multiSelectInactive$: Observable<boolean>;
|
||||
readonly showExcludedPages$: Observable<boolean>;
|
||||
readonly title$: Observable<string>;
|
||||
readonly isHighlights$: Observable<boolean>;
|
||||
private _annotations$ = new BehaviorSubject<AnnotationWrapper[]>([]);
|
||||
@ViewChild('annotationsElement') private readonly _annotationsElement: ElementRef;
|
||||
@ViewChild('quickNavigation') private readonly _quickNavigationElement: ElementRef;
|
||||
@ -81,6 +82,7 @@ export class FileWorkloadComponent {
|
||||
readonly multiSelectService: MultiSelectService,
|
||||
readonly documentInfoService: DocumentInfoService,
|
||||
readonly excludedPagesService: ExcludedPagesService,
|
||||
private readonly _viewModeService: ViewModeService,
|
||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
private readonly _annotationProcessingService: AnnotationProcessingService,
|
||||
@ -89,6 +91,8 @@ export class FileWorkloadComponent {
|
||||
this.multiSelectActive$ = this._multiSelectActive$;
|
||||
this.multiSelectInactive$ = this._multiSelectInactive$;
|
||||
this.showExcludedPages$ = this._showExcludedPages$;
|
||||
this.isHighlights$ = this._isHighlights$;
|
||||
this.title$ = this._title$;
|
||||
}
|
||||
|
||||
@Input()
|
||||
@ -115,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$() {
|
||||
return this.multiSelectService.inactive$.pipe(
|
||||
tap(value => {
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
<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="isWritable$ | async">
|
||||
<iqser-circle-button
|
||||
(action)="convertHighlights(highlightGroup)"
|
||||
[size]="28"
|
||||
[tooltip]="'file-preview.highlights.convert' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
class="mr-2"
|
||||
icon="red:convert"
|
||||
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>
|
||||
@ -0,0 +1,7 @@
|
||||
:host {
|
||||
display: contents;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import { CircleButtonTypes } from '@iqser/common-ui';
|
||||
import { TextHighlightOperation, TextHighlightsGroup } from '@red/domain';
|
||||
import { DossiersDialogService } from '../../../../services/dossiers-dialog.service';
|
||||
import { FilePreviewStateService } from '../../services/file-preview-state.service';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-highlights-separator [highlightGroup] [annotation]',
|
||||
templateUrl: './highlights-separator.component.html',
|
||||
styleUrls: ['./highlights-separator.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class HighlightsSeparatorComponent {
|
||||
@Input() highlightGroup: TextHighlightsGroup;
|
||||
@Input() annotation: AnnotationWrapper;
|
||||
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly isWritable$ = this._state.isWritable$;
|
||||
|
||||
constructor(private readonly _dialogService: DossiersDialogService, private readonly _state: FilePreviewStateService) {}
|
||||
|
||||
convertHighlights(highlightGroup: TextHighlightsGroup): void {
|
||||
const data = this._getActionData(highlightGroup, TextHighlightOperation.CONVERT);
|
||||
this._dialogService.openDialog('highlightAction', null, data);
|
||||
}
|
||||
|
||||
removeHighlights(highlightGroup: TextHighlightsGroup): void {
|
||||
const data = this._getActionData(highlightGroup, TextHighlightOperation.REMOVE);
|
||||
this._dialogService.openDialog('highlightAction', null, data);
|
||||
}
|
||||
|
||||
private _getActionData(highlightGroup: TextHighlightsGroup, operation: TextHighlightOperation) {
|
||||
return {
|
||||
dossierId: this._state.dossierId,
|
||||
fileId: this._state.fileId,
|
||||
color: highlightGroup.color,
|
||||
operation,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -26,9 +26,11 @@ export class TypeAnnotationIconComponent implements OnChanges {
|
||||
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);
|
||||
} else {
|
||||
this.color = this._dictionariesMapService.getDictionaryColor(this.annotation.type, this._dossierTemplateId);
|
||||
@ -36,6 +38,7 @@ export class TypeAnnotationIconComponent implements OnChanges {
|
||||
|
||||
this.type =
|
||||
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 }}
|
||||
</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>
|
||||
|
||||
@ -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],
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -12,6 +12,7 @@ import { AnnotationReferencesService } from './services/annotation-references.se
|
||||
import { FilterService } from '@iqser/common-ui';
|
||||
import { ManualAnnotationService } from '../../services/manual-annotation.service';
|
||||
import { AnnotationProcessingService } from '../../services/annotation-processing.service';
|
||||
import { dossiersServiceProvider } from '../../../../services/entity-services/dossiers.service.provider';
|
||||
|
||||
export const filePreviewScreenProviders = [
|
||||
FilterService,
|
||||
@ -28,4 +29,5 @@ export const filePreviewScreenProviders = [
|
||||
AnnotationReferencesService,
|
||||
ManualAnnotationService,
|
||||
AnnotationProcessingService,
|
||||
dossiersServiceProvider,
|
||||
];
|
||||
|
||||
@ -34,7 +34,6 @@ 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 { ActiveDossiersService } from '@services/entity-services/active-dossiers.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';
|
||||
@ -50,6 +49,7 @@ 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/entity-services/dossiers.service';
|
||||
import Annotation = Core.Annotations.Annotation;
|
||||
import PDFNet = Core.PDFNet;
|
||||
|
||||
@ -104,7 +104,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
private readonly _filterService: FilterService,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _filesMapService: FilesMapService,
|
||||
private readonly _activeDossiersService: ActiveDossiersService,
|
||||
private readonly _dossiersService: DossiersService,
|
||||
private readonly _reanalysisService: ReanalysisService,
|
||||
private readonly _errorService: ErrorService,
|
||||
private readonly _skippedService: SkippedService,
|
||||
@ -152,6 +152,16 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
return;
|
||||
}
|
||||
|
||||
const textHighlightAnnotationIds = this._fileData.textHighlightAnnotations.map(a => a.id);
|
||||
const textHighlightAnnotations = this._getAnnotations((a: Core.Annotations.Annotation) =>
|
||||
textHighlightAnnotationIds.includes(a.Id),
|
||||
);
|
||||
|
||||
this._instance.Core.annotationManager.deleteAnnotations(textHighlightAnnotations, {
|
||||
imported: true,
|
||||
force: true,
|
||||
});
|
||||
|
||||
const ocrAnnotationIds = this._fileData.allAnnotations.filter(a => a.isOCR).map(a => a.id);
|
||||
const annotations = this._getAnnotations(a => a.getCustomData('redact-manager'));
|
||||
const redactions = annotations.filter(a => a.getCustomData('redaction'));
|
||||
@ -185,6 +195,20 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
this._hide(nonRedactionEntries);
|
||||
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();
|
||||
@ -200,7 +224,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
async ngOnAttach(previousRoute: ActivatedRouteSnapshot): Promise<boolean> {
|
||||
const file = await this.stateService.file;
|
||||
if (!file.canBeOpened) {
|
||||
return this._router.navigate([this._activeDossiersService.find(this.dossierId)?.routerLink]);
|
||||
return this._router.navigate([this._dossiersService.find(this.dossierId)?.routerLink]);
|
||||
}
|
||||
this.viewModeService.compareMode = false;
|
||||
this.viewModeService.switchToStandard();
|
||||
@ -491,7 +515,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
await clearStamps(pdfDoc, pdfNet, allPages);
|
||||
|
||||
if (this.viewModeService.isRedacted) {
|
||||
const dossier = this._activeDossiersService.find(this.dossierId);
|
||||
const dossier = this._dossiersService.find(this.dossierId);
|
||||
if (dossier.watermarkPreviewEnabled) {
|
||||
await this._stampPreview(pdfDoc, dossier.dossierTemplateId);
|
||||
}
|
||||
@ -548,7 +572,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
this.addActiveScreenSubscription = this._activeDossiersService
|
||||
this.addActiveScreenSubscription = this._dossiersService
|
||||
.getEntityDeleted$(this.dossierId)
|
||||
.pipe(tap(() => this._handleDeletedDossier()))
|
||||
.subscribe();
|
||||
@ -577,7 +601,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
|
||||
private async _loadFileData(file: File): Promise<void | boolean> {
|
||||
if (!file || file.isError) {
|
||||
return this._router.navigate([this._activeDossiersService.find(this.dossierId).routerLink]);
|
||||
return this._router.navigate([this._dossiersService.find(this.dossierId).routerLink]);
|
||||
}
|
||||
|
||||
if (file.isUnprocessed) {
|
||||
@ -705,7 +729,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
}
|
||||
}
|
||||
|
||||
private _getAnnotations(predicate: (value) => unknown) {
|
||||
private _getAnnotations(predicate: (value) => boolean) {
|
||||
const annotations = this._instance.Core.annotationManager.getAnnotationsList();
|
||||
return predicate ? annotations.filter(predicate) : annotations;
|
||||
}
|
||||
@ -726,7 +750,9 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
|
||||
private _setAnnotationsColor(annotations: Annotation[], customData: string) {
|
||||
annotations.forEach(annotation => {
|
||||
annotation['StrokeColor'] = this._annotationDrawService.convertColor(this._instance, annotation.getCustomData(customData));
|
||||
const color = this._annotationDrawService.convertColor(this._instance, annotation.getCustomData(customData));
|
||||
annotation['StrokeColor'] = color;
|
||||
annotation['FillColor'] = color;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@ import { AnnotationReferencesListComponent } from './components/annotation-refer
|
||||
import { AcceptRecommendationDialogComponent } from './dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component';
|
||||
import { AnnotationCardComponent } from './components/annotation-card/annotation-card.component';
|
||||
import { AnnotationReferencesPageIndicatorComponent } from './components/annotation-references-page-indicator/annotation-references-page-indicator.component';
|
||||
import { HighlightsSeparatorComponent } from './components/highlights-separator/highlights-separator.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@ -33,26 +34,28 @@ const routes: Routes = [
|
||||
},
|
||||
];
|
||||
|
||||
const components = [
|
||||
FileWorkloadComponent,
|
||||
AnnotationDetailsComponent,
|
||||
AnnotationsListComponent,
|
||||
PageIndicatorComponent,
|
||||
PageExclusionComponent,
|
||||
PdfViewerComponent,
|
||||
AnnotationActionsComponent,
|
||||
CommentsComponent,
|
||||
DocumentInfoComponent,
|
||||
TypeAnnotationIconComponent,
|
||||
ViewSwitchComponent,
|
||||
UserManagementComponent,
|
||||
AcceptRecommendationDialogComponent,
|
||||
AnnotationReferencesListComponent,
|
||||
AnnotationCardComponent,
|
||||
AnnotationReferencesPageIndicatorComponent,
|
||||
HighlightsSeparatorComponent,
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
FilePreviewScreenComponent,
|
||||
FileWorkloadComponent,
|
||||
AnnotationDetailsComponent,
|
||||
AnnotationsListComponent,
|
||||
PageIndicatorComponent,
|
||||
PageExclusionComponent,
|
||||
PdfViewerComponent,
|
||||
AnnotationActionsComponent,
|
||||
CommentsComponent,
|
||||
DocumentInfoComponent,
|
||||
TypeAnnotationIconComponent,
|
||||
ViewSwitchComponent,
|
||||
UserManagementComponent,
|
||||
AcceptRecommendationDialogComponent,
|
||||
AnnotationReferencesListComponent,
|
||||
AnnotationCardComponent,
|
||||
AnnotationReferencesPageIndicatorComponent,
|
||||
],
|
||||
declarations: [FilePreviewScreenComponent, ...components],
|
||||
imports: [
|
||||
RouterModule.forChild(routes),
|
||||
CommonModule,
|
||||
|
||||
@ -3,7 +3,6 @@ import { Core, WebViewerInstance } from '@pdftron/webviewer';
|
||||
import { hexToRgb } from '@utils/functions';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { ActiveDossiersService } from '@services/entity-services/active-dossiers.service';
|
||||
import { RedactionLogService } from '../../../services/redaction-log.service';
|
||||
import { environment } from '@environments/environment';
|
||||
|
||||
@ -11,6 +10,7 @@ import { IRectangle, ISectionGrid, ISectionRectangle } from '@red/domain';
|
||||
import { SkippedService } from './skipped.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
import { DossiersService } from '../../../../../services/entity-services/dossiers.service';
|
||||
import Annotation = Core.Annotations.Annotation;
|
||||
|
||||
@Injectable()
|
||||
@ -21,7 +21,7 @@ export class AnnotationDrawService {
|
||||
|
||||
constructor(
|
||||
private readonly _dictionariesMapService: DictionariesMapService,
|
||||
private readonly _activeDossiersService: ActiveDossiersService,
|
||||
private readonly _dossiersService: DossiersService,
|
||||
private readonly _redactionLogService: RedactionLogService,
|
||||
private readonly _userPreferenceService: UserPreferenceService,
|
||||
private readonly _skippedService: SkippedService,
|
||||
@ -128,7 +128,7 @@ export class AnnotationDrawService {
|
||||
}
|
||||
|
||||
private _computeSection(activeViewer: WebViewerInstance, pageNumber: number, sectionRectangle: ISectionRectangle, dossierId: string) {
|
||||
const dossierTemplateId = this._activeDossiersService.find(dossierId).dossierTemplateId;
|
||||
const dossierTemplateId = this._dossiersService.find(dossierId).dossierTemplateId;
|
||||
const rectangleAnnot = new activeViewer.Core.Annotations.RectangleAnnotation();
|
||||
const pageHeight = activeViewer.Core.documentViewer.getPageHeight(pageNumber);
|
||||
const rectangle: IRectangle = {
|
||||
@ -156,66 +156,84 @@ export class AnnotationDrawService {
|
||||
compareMode: boolean,
|
||||
) {
|
||||
const pageNumber = compareMode ? annotationWrapper.pageNumber * 2 - 1 : annotationWrapper.pageNumber;
|
||||
const dossierTemplateId = this._activeDossiersService.find(dossierId).dossierTemplateId;
|
||||
|
||||
let annotation: Core.Annotations.RectangleAnnotation | Core.Annotations.TextHighlightAnnotation;
|
||||
if (annotationWrapper.rectangle || annotationWrapper.isImage) {
|
||||
annotation = new activeViewer.Core.Annotations.RectangleAnnotation();
|
||||
if (annotationWrapper.superType === 'text-highlight') {
|
||||
const rectangleAnnot = new activeViewer.Core.Annotations.RectangleAnnotation();
|
||||
const pageHeight = activeViewer.Core.documentViewer.getPageHeight(pageNumber);
|
||||
const firstPosition = annotationWrapper.positions[0];
|
||||
annotation.X = firstPosition.topLeft.x;
|
||||
annotation.Y = pageHeight - (firstPosition.topLeft.y + firstPosition.height);
|
||||
annotation.Width = firstPosition.width;
|
||||
annotation.FillColor = this.getAndConvertColor(
|
||||
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;
|
||||
|
||||
let annotation: Core.Annotations.RectangleAnnotation | Core.Annotations.TextHighlightAnnotation;
|
||||
if (annotationWrapper.rectangle || annotationWrapper.isImage) {
|
||||
annotation = new activeViewer.Core.Annotations.RectangleAnnotation();
|
||||
const pageHeight = activeViewer.Core.documentViewer.getPageHeight(pageNumber);
|
||||
const firstPosition = annotationWrapper.positions[0];
|
||||
annotation.X = firstPosition.topLeft.x;
|
||||
annotation.Y = pageHeight - (firstPosition.topLeft.y + firstPosition.height);
|
||||
annotation.Width = firstPosition.width;
|
||||
annotation.FillColor = this.getAndConvertColor(
|
||||
activeViewer,
|
||||
dossierTemplateId,
|
||||
annotationWrapper.superType,
|
||||
annotationWrapper.type,
|
||||
);
|
||||
annotation.Opacity = annotationWrapper.isChangeLogRemoved
|
||||
? AnnotationDrawService.DEFAULT_REMOVED_ANNOTATION_OPACITY
|
||||
: AnnotationDrawService.DEFAULT_RECTANGLE_ANNOTATION_OPACITY;
|
||||
annotation.Height = firstPosition.height;
|
||||
annotation.Intensity = 100;
|
||||
} else {
|
||||
annotation = new activeViewer.Core.Annotations.TextHighlightAnnotation();
|
||||
annotation.Quads = this._rectanglesToQuads(annotationWrapper.positions, activeViewer, pageNumber);
|
||||
annotation.Opacity = annotationWrapper.isChangeLogRemoved
|
||||
? AnnotationDrawService.DEFAULT_REMOVED_ANNOTATION_OPACITY
|
||||
: AnnotationDrawService.DEFAULT_TEXT_ANNOTATION_OPACITY;
|
||||
}
|
||||
|
||||
annotation.setContents(annotationWrapper.content);
|
||||
|
||||
annotation.PageNumber = pageNumber;
|
||||
annotation.StrokeColor = this.getAndConvertColor(
|
||||
activeViewer,
|
||||
dossierTemplateId,
|
||||
annotationWrapper.superType,
|
||||
annotationWrapper.type,
|
||||
);
|
||||
annotation.Opacity = annotationWrapper.isChangeLogRemoved
|
||||
? AnnotationDrawService.DEFAULT_REMOVED_ANNOTATION_OPACITY
|
||||
: AnnotationDrawService.DEFAULT_RECTANGLE_ANNOTATION_OPACITY;
|
||||
annotation.Height = firstPosition.height;
|
||||
annotation.Intensity = 100;
|
||||
} else {
|
||||
annotation = new activeViewer.Core.Annotations.TextHighlightAnnotation();
|
||||
annotation.Quads = this._rectanglesToQuads(annotationWrapper.positions, activeViewer, pageNumber);
|
||||
annotation.Opacity = annotationWrapper.isChangeLogRemoved
|
||||
? AnnotationDrawService.DEFAULT_REMOVED_ANNOTATION_OPACITY
|
||||
: AnnotationDrawService.DEFAULT_TEXT_ANNOTATION_OPACITY;
|
||||
annotation.Id = annotationWrapper.id;
|
||||
annotation.ReadOnly = true;
|
||||
// change log entries are drawn lighter
|
||||
|
||||
annotation.Hidden =
|
||||
annotationWrapper.isChangeLogRemoved ||
|
||||
(this._skippedService.hideSkipped && annotationWrapper.isSkipped) ||
|
||||
annotationWrapper.isOCR ||
|
||||
annotationWrapper.hidden;
|
||||
annotation.setCustomData('redact-manager', 'true');
|
||||
annotation.setCustomData('redaction', String(annotationWrapper.previewAnnotation));
|
||||
annotation.setCustomData('skipped', String(annotationWrapper.isSkipped));
|
||||
annotation.setCustomData('changeLog', String(annotationWrapper.isChangeLogEntry));
|
||||
annotation.setCustomData('changeLogRemoved', String(annotationWrapper.isChangeLogRemoved));
|
||||
annotation.setCustomData('opacity', String(annotation.Opacity));
|
||||
annotation.setCustomData('redactionColor', String(this.getColor(activeViewer, dossierTemplateId, 'redaction', 'redaction')));
|
||||
annotation.setCustomData(
|
||||
'annotationColor',
|
||||
String(this.getColor(activeViewer, dossierTemplateId, annotationWrapper.superType, annotationWrapper.type)),
|
||||
);
|
||||
|
||||
return annotation;
|
||||
}
|
||||
|
||||
annotation.setContents(annotationWrapper.content);
|
||||
|
||||
annotation.PageNumber = pageNumber;
|
||||
annotation.StrokeColor = this.getAndConvertColor(
|
||||
activeViewer,
|
||||
dossierTemplateId,
|
||||
annotationWrapper.superType,
|
||||
annotationWrapper.type,
|
||||
);
|
||||
annotation.Id = annotationWrapper.id;
|
||||
annotation.ReadOnly = true;
|
||||
// change log entries are drawn lighter
|
||||
|
||||
annotation.Hidden =
|
||||
annotationWrapper.isChangeLogRemoved ||
|
||||
(this._skippedService.hideSkipped && annotationWrapper.isSkipped) ||
|
||||
annotationWrapper.isOCR ||
|
||||
annotationWrapper.hidden;
|
||||
annotation.setCustomData('redact-manager', 'true');
|
||||
annotation.setCustomData('redaction', String(annotationWrapper.isRedacted));
|
||||
annotation.setCustomData('skipped', String(annotationWrapper.isSkipped));
|
||||
annotation.setCustomData('changeLog', String(annotationWrapper.isChangeLogEntry));
|
||||
annotation.setCustomData('changeLogRemoved', String(annotationWrapper.isChangeLogRemoved));
|
||||
annotation.setCustomData('opacity', String(annotation.Opacity));
|
||||
annotation.setCustomData('redactionColor', String(this.getColor(activeViewer, dossierTemplateId, 'redaction', 'redaction')));
|
||||
annotation.setCustomData(
|
||||
'annotationColor',
|
||||
String(this.getColor(activeViewer, dossierTemplateId, annotationWrapper.superType, annotationWrapper.type)),
|
||||
);
|
||||
|
||||
return annotation;
|
||||
}
|
||||
|
||||
private _rectanglesToQuads(positions: IRectangle[], activeViewer: WebViewerInstance, pageNumber: number): any[] {
|
||||
|
||||
@ -43,6 +43,10 @@ export class ViewModeService {
|
||||
return this._viewMode$.value === 'REDACTED';
|
||||
}
|
||||
|
||||
get isTextHighlights() {
|
||||
return this._viewMode$.value === 'TEXT_HIGHLIGHTS';
|
||||
}
|
||||
|
||||
get isCompare() {
|
||||
return this._compareMode$.value;
|
||||
}
|
||||
@ -63,6 +67,10 @@ export class ViewModeService {
|
||||
this._switchTo('REDACTED');
|
||||
}
|
||||
|
||||
switchToHighlights() {
|
||||
this._switchTo('TEXT_HIGHLIGHTS');
|
||||
}
|
||||
|
||||
private _switchTo(mode: ViewMode) {
|
||||
this._viewMode$.next(mode);
|
||||
}
|
||||
|
||||
@ -58,7 +58,13 @@ export class AnnotationProcessingService {
|
||||
} else {
|
||||
// top level filter
|
||||
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 {
|
||||
let parentFilter = filterMap.get(a.superType);
|
||||
if (!parentFilter) {
|
||||
@ -124,18 +130,28 @@ export class AnnotationProcessingService {
|
||||
}
|
||||
|
||||
obj.forEach((values, page) => {
|
||||
obj.set(page, this._sortAnnotations(values));
|
||||
if (!values[0].isHighlight) {
|
||||
obj.set(page, this._sortAnnotations(values));
|
||||
}
|
||||
});
|
||||
|
||||
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({
|
||||
id: key,
|
||||
topLevelFilter: true,
|
||||
matches: 1,
|
||||
label: annotationTypesTranslations[key],
|
||||
label: skipTranslation ? key : annotationTypesTranslations[key],
|
||||
skipTranslation,
|
||||
color,
|
||||
});
|
||||
filterMap.set(key, 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 { ConfirmationDialogComponent, DialogConfig, DialogService, largeDialogConfig } from '@iqser/common-ui';
|
||||
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';
|
||||
import { ConfirmArchiveDossierDialogComponent } from '../dialogs/confirm-archive-dossier-dialog/confirm-archive-dossier-dialog.component';
|
||||
|
||||
type DialogType =
|
||||
@ -25,7 +26,8 @@ type DialogType =
|
||||
| 'removeAnnotations'
|
||||
| 'resizeAnnotation'
|
||||
| 'forceAnnotation'
|
||||
| 'manualAnnotation';
|
||||
| 'manualAnnotation'
|
||||
| 'highlightAction';
|
||||
|
||||
@Injectable()
|
||||
export class DossiersDialogService extends DialogService<DialogType> {
|
||||
@ -72,6 +74,9 @@ export class DossiersDialogService extends DialogService<DialogType> {
|
||||
component: ManualAnnotationDialogComponent,
|
||||
dialogConfig: { autoFocus: true },
|
||||
},
|
||||
highlightAction: {
|
||||
component: HighlightActionDialogComponent,
|
||||
},
|
||||
};
|
||||
|
||||
constructor(protected readonly _dialog: MatDialog) {
|
||||
|
||||
@ -3,7 +3,7 @@ import { forkJoin, Observable, of } from 'rxjs';
|
||||
import { catchError, map, tap } from 'rxjs/operators';
|
||||
import { FileDataModel } from '@models/file/file-data.model';
|
||||
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 { ViewedPagesService } from '@services/entity-services/viewed-pages.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 { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
import { TextHighlightService } from './text-highlight.service';
|
||||
|
||||
@Injectable()
|
||||
export class PdfViewerDataService {
|
||||
constructor(
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
private readonly _redactionLogService: RedactionLogService,
|
||||
private readonly _textHighlightService: TextHighlightService,
|
||||
private readonly _viewedPagesService: ViewedPagesService,
|
||||
private readonly _userPreferenceService: UserPreferenceService,
|
||||
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> {
|
||||
const redactionLog$ = this.loadRedactionLogFor(newFile.dossierId, newFile.fileId);
|
||||
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 } });
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -23,6 +23,7 @@ export class IconsModule {
|
||||
'color-picker',
|
||||
'comment',
|
||||
'comment-fill',
|
||||
'convert',
|
||||
'csv',
|
||||
'dictionary',
|
||||
'denied',
|
||||
|
||||
@ -6,5 +6,5 @@
|
||||
[class.request]="isRequest"
|
||||
class="icon"
|
||||
>
|
||||
<span>{{ label || dictionary.label.charAt(0) }}</span>
|
||||
<span>{{ label || dictionary?.label?.charAt(0) }}</span>
|
||||
</div>
|
||||
|
||||
@ -60,10 +60,13 @@
|
||||
<div *ngIf="filter.id === 'comment'">
|
||||
<mat-icon svgIcon="red:comment"></mat-icon>
|
||||
</div>
|
||||
|
||||
<redaction-annotation-icon *ngIf="filter.color" [color]="filter.color" [label]="''" type="square"></redaction-annotation-icon>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="filter.icon">
|
||||
<mat-icon [svgIcon]="filter.icon"></mat-icon>
|
||||
</ng-container>
|
||||
|
||||
{{ filter.label | translate }}
|
||||
<ng-container *ngIf="filter.skipTranslation; else translate"> {{ filter.label }}</ng-container>
|
||||
<ng-template #translate>{{ filter.label | translate }} </ng-template>
|
||||
|
||||
@ -2,12 +2,23 @@ import { Injectable, Injector } from '@angular/core';
|
||||
import { StatsService } from '@iqser/common-ui';
|
||||
import { DossierStats, IDossierStats } from '@red/domain';
|
||||
import { DOSSIER_ID } from '@utils/constants';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { UserService } from '@services/user.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class DossierStatsService extends StatsService<DossierStats, IDossierStats> {
|
||||
constructor(protected readonly _injector: Injector) {
|
||||
constructor(protected readonly _injector: Injector, private readonly _userService: UserService) {
|
||||
super(_injector, DOSSIER_ID, DossierStats, 'dossier-stats');
|
||||
}
|
||||
|
||||
getFor(ids: string[]): Observable<DossierStats[]> {
|
||||
const isUserAdminOnly = this._userService.currentUser.roles.length === 1 && this._userService.currentUser.isUserAdmin;
|
||||
if (isUserAdminOnly) {
|
||||
return of([]);
|
||||
}
|
||||
|
||||
return super.getFor(ids);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import { Dossier, File, IComment, IDossier } from '@red/domain';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { dossiersServiceResolver } from '@services/entity-services/dossiers.service.provider';
|
||||
import { FilesMapService } from '@services/entity-services/files-map.service';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PermissionsService {
|
||||
@ -11,6 +12,7 @@ export class PermissionsService {
|
||||
private readonly _userService: UserService,
|
||||
private readonly _route: ActivatedRoute,
|
||||
private readonly _injector: Injector,
|
||||
private readonly _filesMapService: FilesMapService,
|
||||
) {}
|
||||
|
||||
private get _dossiersService(): DossiersService {
|
||||
@ -23,7 +25,7 @@ export class PermissionsService {
|
||||
}
|
||||
|
||||
displayReanalyseBtn(dossier: Dossier): boolean {
|
||||
return dossier.isActive && this.isApprover(dossier);
|
||||
return dossier.isActive && this.isApprover(dossier) && this._filesMapService.get(dossier.dossierId).length > 0;
|
||||
}
|
||||
|
||||
canUploadFiles(dossier: Dossier): boolean {
|
||||
|
||||
@ -37,6 +37,10 @@ export class UserService extends EntitiesService<User, IUser> {
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
if (!this.currentUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.currentUser.isUserAdmin || this.currentUser.isUser || this.currentUser.isAdmin) {
|
||||
await firstValueFrom(this.loadAll());
|
||||
}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
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 } = {
|
||||
'text-highlight': _('annotation-type.text-highlight'),
|
||||
'declined-suggestion': _('annotation-type.declined-suggestion'),
|
||||
hint: _('annotation-type.hint'),
|
||||
'ignored-hint': _('annotation-type.ignored-hint'),
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { AnnotationSuperType } from '../../models/file/annotation.wrapper';
|
||||
|
||||
export const SuperTypeSorter: { [key in AnnotationSuperType]: number } = {
|
||||
'text-highlight': 100,
|
||||
'suggestion-change-legal-basis': 14,
|
||||
'suggestion-force-redaction': 15,
|
||||
'suggestion-force-hint': 16,
|
||||
|
||||
@ -221,13 +221,25 @@
|
||||
"it": "",
|
||||
"fr": ""
|
||||
},
|
||||
"dossiers_scroll_up_and_down": {
|
||||
"dossiers_scroll_up_button": {
|
||||
"en": "/en/index-en.html?contextId=dossiers_scroll_up_and_down",
|
||||
"de": "",
|
||||
"it": "",
|
||||
"fr": ""
|
||||
},
|
||||
"documents_scroll_up_and_down": {
|
||||
"dossiers_scroll_down_button": {
|
||||
"en": "/en/index-en.html?contextId=dossiers_scroll_up_and_down",
|
||||
"de": "",
|
||||
"it": "",
|
||||
"fr": ""
|
||||
},
|
||||
"documents_scroll_up_button": {
|
||||
"en": "/en/index-en.html?contextId=documents_scroll_up_and_down",
|
||||
"de": "",
|
||||
"it": "",
|
||||
"fr": ""
|
||||
},
|
||||
"documents_scroll_down_button": {
|
||||
"en": "/en/index-en.html?contextId=documents_scroll_up_and_down",
|
||||
"de": "",
|
||||
"it": "",
|
||||
|
||||
@ -310,7 +310,8 @@
|
||||
"suggestion-recategorize-image": "Suggested recategorize image",
|
||||
"suggestion-remove": "Suggested local removal",
|
||||
"suggestion-remove-dictionary": "Suggested dictionary removal",
|
||||
"suggestion-resize": "Suggested Resize"
|
||||
"suggestion-resize": "Suggested Resize",
|
||||
"text-highlight": "Highlight"
|
||||
},
|
||||
"annotations": "Annotations",
|
||||
"archived-dossiers-listing": {
|
||||
@ -410,6 +411,7 @@
|
||||
},
|
||||
"header": "Edit Redaction Reason"
|
||||
},
|
||||
"color": "Color",
|
||||
"comments": {
|
||||
"add-comment": "Enter comment",
|
||||
"comments": "{count} {count, plural, one{comment} other{comments}}",
|
||||
@ -1240,6 +1242,10 @@
|
||||
"exclude-pages": "Exclude pages from redaction",
|
||||
"excluded-from-redaction": "excluded",
|
||||
"fullscreen": "Full Screen (F)",
|
||||
"highlights": {
|
||||
"convert": "Convert highlights",
|
||||
"remove": "Remove highlights"
|
||||
},
|
||||
"last-reviewer": "Last Reviewed by:",
|
||||
"no-data": {
|
||||
"title": "There have been no changes to this page."
|
||||
@ -1288,8 +1294,13 @@
|
||||
"put-back": "Undo",
|
||||
"removed-from-redaction": "Removed from redaction"
|
||||
},
|
||||
"highlights": {
|
||||
"label": "Highlights"
|
||||
},
|
||||
"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": {
|
||||
"disable": "Disable redaction",
|
||||
"enable": "Enable for redaction",
|
||||
@ -1397,6 +1408,30 @@
|
||||
"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>"
|
||||
},
|
||||
"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",
|
||||
"image-category": {
|
||||
"formula": "Formula",
|
||||
@ -1732,6 +1767,7 @@
|
||||
"placeholder": "Search documents...",
|
||||
"this-dossier": "in this dossier"
|
||||
},
|
||||
"size": "Size",
|
||||
"smtp-auth-config": {
|
||||
"actions": {
|
||||
"cancel": "Cancel",
|
||||
|
||||
9
apps/red-ui/src/assets/icons/general/convert.svg
Normal file
9
apps/red-ui/src/assets/icons/general/convert.svg
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="100px" version="1.1" viewBox="0 0 100 100" width="100px" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd" id="Symbols" stroke="none" stroke-width="1">
|
||||
<g fill="currentColor" fill-rule="nonzero" id="convert">
|
||||
<path
|
||||
d="M83.25,2.5 C91.3253393,2.5 97.5,8.67466071 97.5,16.75 L97.5,16.75 L97.5,83.25 C97.5,91.3253393 91.3253393,97.5 83.25,97.5 L83.25,97.5 L16.75,97.5 C8.67466071,97.5 2.5,91.3253393 2.5,83.25 L2.5,83.25 L2.5,16.75 C2.5,8.67466071 8.67466071,2.5 16.75,2.5 L16.75,2.5 Z M83.25,12 L16.75,12 C13.8998304,12 12,13.8998304 12,16.75 L12,16.75 L12,83.25 C12,86.1001696 13.8998304,88 16.75,88 L16.75,88 L83.25,88 C86.1001696,88 88,86.1001696 88,83.25 L88,83.25 L88,16.75 C88,13.8998304 86.1001696,12 83.25,12 L83.25,12 Z M59.4998304,26.2501696 L78.4998304,45.2501696 L59.4998304,64.2501696 L52.85,57.6003393 L60.4501696,50.0001696 L38.1251696,50.0001696 C34.3255089,50.0001696 31.0001696,53.3255089 31.0001696,57.1251696 C31.0001696,60.9248304 34.3255089,64.2501696 38.1251696,64.2501696 L38.1251696,64.2501696 L45.2501696,64.2501696 L45.2501696,73.7501696 L38.1251696,73.7501696 C29.1003393,73.7501696 21.5001696,66.15 21.5001696,57.1251696 C21.5001696,48.1003393 29.1003393,40.5001696 38.1251696,40.5001696 L38.1251696,40.5001696 L60.4501696,40.5001696 L52.85,32.9 L59.4998304,26.2501696 Z"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@ -2,13 +2,13 @@
|
||||
@use 'common-mixins';
|
||||
|
||||
.NEW {
|
||||
stroke: variables.$grey-5;
|
||||
background-color: variables.$grey-5;
|
||||
stroke: var(--iqser-grey-5);
|
||||
background-color: var(--iqser-grey-5);
|
||||
}
|
||||
|
||||
.UNPROCESSED {
|
||||
stroke: variables.$grey-3;
|
||||
background-color: variables.$grey-3;
|
||||
stroke: var(--iqser-grey-3);
|
||||
background-color: var(--iqser-grey-3);
|
||||
}
|
||||
|
||||
.UNDER_REVIEW,
|
||||
@ -70,8 +70,8 @@
|
||||
}
|
||||
|
||||
.INACTIVE {
|
||||
stroke: variables.$grey-5;
|
||||
background-color: variables.$grey-5;
|
||||
stroke: var(--iqser-grey-5);
|
||||
background-color: var(--iqser-grey-5);
|
||||
}
|
||||
|
||||
.MANAGER,
|
||||
@ -79,3 +79,22 @@
|
||||
stroke: 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,3 +21,4 @@ export * from './lib/legal-basis';
|
||||
export * from './lib/dossier-stats';
|
||||
export * from './lib/dossier-state';
|
||||
export * from './lib/trash-dossier';
|
||||
export * from './lib/text-highlight';
|
||||
|
||||
@ -40,6 +40,7 @@ export class File extends Entity<IFile> implements IFile, IRouterPath {
|
||||
readonly hasSuggestions: boolean;
|
||||
readonly processingStatus: ProcessingFileStatus;
|
||||
readonly workflowStatus: WorkflowFileStatus;
|
||||
readonly fileManipulationDate: string;
|
||||
|
||||
readonly statusSort: number;
|
||||
readonly cacheIdentifier?: string;
|
||||
@ -96,9 +97,10 @@ export class File extends Entity<IFile> implements IFile, IRouterPath {
|
||||
this.uploader = file.uploader;
|
||||
this.excludedPages = file.excludedPages || [];
|
||||
this.hasSuggestions = !!file.hasSuggestions;
|
||||
this.fileManipulationDate = file.fileManipulationDate;
|
||||
|
||||
this.statusSort = StatusSorter[this.workflowStatus];
|
||||
this.cacheIdentifier = btoa((this.lastUploaded ?? '') + (this.lastOCRTime ?? ''));
|
||||
this.cacheIdentifier = btoa(this.fileManipulationDate ?? '');
|
||||
this.hintsOnly = this.hasHints && !this.hasRedactions;
|
||||
this.hasNone = !this.hasRedactions && !this.hasHints && !this.hasSuggestions;
|
||||
this.isProcessing = isProcessingStatuses.includes(this.processingStatus);
|
||||
|
||||
@ -147,4 +147,9 @@ export interface IFile {
|
||||
readonly processingStatus: ProcessingFileStatus;
|
||||
|
||||
readonly workflowStatus: WorkflowFileStatus;
|
||||
|
||||
/**
|
||||
* Last time the actual file was touched
|
||||
*/
|
||||
readonly fileManipulationDate: string;
|
||||
}
|
||||
|
||||
@ -1 +1 @@
|
||||
export type ViewMode = 'STANDARD' | 'DELTA' | 'REDACTED';
|
||||
export type ViewMode = 'STANDARD' | 'DELTA' | 'REDACTED' | 'TEXT_HIGHLIGHTS';
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
import { IRectangle } from '../geometry';
|
||||
|
||||
export interface ImportedRedaction {
|
||||
id: string;
|
||||
positions: IRectangle[];
|
||||
}
|
||||
5
libs/red-domain/src/lib/text-highlight/index.ts
Normal file
5
libs/red-domain/src/lib/text-highlight/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from './imported-redaction';
|
||||
export * from './text-highlight-operation';
|
||||
export * from './text-highlight.response';
|
||||
export * from './text-highlight.request';
|
||||
export * from './text-highlights-group';
|
||||
@ -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[] };
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
export interface TextHighlightsGroup {
|
||||
startIdx: number;
|
||||
color: string;
|
||||
length: number;
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "redaction",
|
||||
"version": "3.261.0",
|
||||
"version": "3.270.0",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user