Merge branch 'dan' into 'master'

RED-7393 update logo color

See merge request redactmanager/red-ui!37
This commit is contained in:
Dan Percic 2023-08-30 20:25:51 +02:00
commit c0084258b0
19 changed files with 180 additions and 305 deletions

View File

@ -131,7 +131,7 @@ export const appModuleFactory = (config: AppConfig) => {
features: {
ANNOTATIONS: {
color: 'aqua',
enabled: false,
enabled: true,
level: NgxLoggerLevel.DEBUG,
},
FILTERS: {
@ -147,7 +147,7 @@ export const appModuleFactory = (config: AppConfig) => {
enabled: true,
},
FILE: {
enabled: true,
enabled: false,
},
CHANGES: {
enabled: false,

View File

@ -1,5 +1,5 @@
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { AnnotationPermissions } from '@models/file/annotation.permissions';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { Dictionary } from '@red/domain';
export const canUndo = (annotation: AnnotationWrapper, isApprover: boolean) =>
@ -39,7 +39,9 @@ export const canRecategorizeAnnotation = (annotation: AnnotationWrapper, canReca
export const canResizeAnnotation = (annotation: AnnotationWrapper, canAddRedaction: boolean) =>
canAddRedaction &&
!annotation.isSkipped &&
!annotation.pending &&
(((annotation.isRedacted || annotation.isImage) && !annotation.isSuggestion) ||
annotation.isSuggestionResize ||
annotation.isRecommendation) &&
!annotation.pending;
annotation.isDictBasedHint ||
annotation.isRecommendation);

View File

@ -1,8 +1,7 @@
import { AnnotationWrapper } from './annotation.wrapper';
import { Dictionary } from '@red/domain';
import { isArray } from 'lodash-es';
import { IqserPermissionsService } from '@iqser/common-ui';
import { Dictionary } from '@red/domain';
import { Roles } from '@users/roles';
import { isArray } from 'lodash-es';
import {
canAcceptRecommendation,
canChangeLegalBasis,
@ -16,6 +15,7 @@ import {
canResizeAnnotation,
canUndo,
} from './annotation-permissions.utils';
import { AnnotationWrapper } from './annotation.wrapper';
export class AnnotationPermissions {
canUndo = true;
@ -64,6 +64,22 @@ export class AnnotationPermissions {
return summedPermissions;
}
static reduce(permissions: AnnotationPermissions[]): AnnotationPermissions {
const result = new AnnotationPermissions();
result.canResizeAnnotation = permissions.length === 1 && permissions[0].canResizeAnnotation;
result.canChangeLegalBasis = permissions.reduce((acc, next) => acc && next.canChangeLegalBasis, true);
result.canRecategorizeAnnotation = permissions.reduce((acc, next) => acc && next.canRecategorizeAnnotation, true);
result.canRemoveFromDictionary = permissions.reduce((acc, next) => acc && next.canRemoveFromDictionary, true);
result.canAcceptRecommendation = permissions.reduce((acc, next) => acc && next.canAcceptRecommendation, true);
result.canMarkAsFalsePositive = permissions.reduce((acc, next) => acc && next.canMarkAsFalsePositive, true);
result.canForceRedaction = permissions.reduce((acc, next) => acc && next.canForceRedaction, true);
result.canForceHint = permissions.reduce((acc, next) => acc && next.canForceHint, true);
result.canRemoveOnlyHere = permissions.reduce((acc, next) => acc && next.canRemoveOnlyHere, true);
result.canRemoveRedaction = permissions.reduce((acc, next) => acc && next.canRemoveRedaction, true);
result.canUndo = permissions.reduce((acc, next) => acc && next.canUndo, true);
return result;
}
private _merge(permissions: AnnotationPermissions) {
for (const key of Object.keys(this)) {
if (typeof this[key] === 'boolean') {

View File

@ -17,6 +17,7 @@ import {
IRectangle,
IRedactionLogEntry,
LogEntryEngine,
LogEntryEngines,
LogEntryStatuses,
LowLevelFilterTypes,
ManualRedactionTypes,
@ -180,6 +181,10 @@ export class AnnotationWrapper implements IListable {
return 'square';
}
get isDictBasedHint() {
return this.isHint && this.engines.includes(LogEntryEngines.DICTIONARY);
}
get isIgnoredHint() {
return this.superType === SuperTypes.IgnoredHint;
}

View File

@ -1,16 +1,16 @@
import { Component, OnInit } from '@angular/core';
import { Component, inject, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { DetailsRadioOption, IconButtonTypes, IqserDialogComponent } from '@iqser/common-ui';
import { Dictionary, Dossier, SuperTypes } from '@red/domain';
import { FormBuilder, UntypedFormGroup } from '@angular/forms';
import { Dictionary, SuperTypes } from '@red/domain';
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
import { DictionaryService } from '@services/entity-services/dictionary.service';
import { JustificationsService } from '@services/entity-services/justifications.service';
import { Roles } from '@users/roles';
import { firstValueFrom } from 'rxjs';
import { JustificationsService } from '@services/entity-services/justifications.service';
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
import { LegalBasisOption } from '../manual-redaction-dialog/manual-annotation-dialog.component';
import { DictionaryService } from '@services/entity-services/dictionary.service';
import { IMAGE_CATEGORIES } from '../../utils/constants';
import { getEditRedactionOptions, RedactOrHintOption } from '../../utils/dialog-options';
import { EditRedactionData, EditRedactResult } from '../../utils/dialog-types';
import { IMAGE_CATEGORIES } from '../../utils/constants';
import { LegalBasisOption } from '../manual-redaction-dialog/manual-annotation-dialog.component';
@Component({
templateUrl: './edit-redaction-dialog.component.html',
@ -19,6 +19,8 @@ export class EditRedactionDialogComponent
extends IqserDialogComponent<EditRedactionDialogComponent, EditRedactionData, EditRedactResult>
implements OnInit
{
readonly #dossier = inject(ActiveDossiersService).find(this.data.dossierId);
readonly #applyToAllDossiers = this.data.applyToAllDossiers;
readonly roles = Roles;
readonly iconButtonTypes = IconButtonTypes;
readonly redactedText: string;
@ -28,24 +30,17 @@ export class EditRedactionDialogComponent
readonly showLegalReason: boolean;
readonly isHint: boolean;
readonly showExtras: boolean;
options: DetailsRadioOption<RedactOrHintOption>[] | undefined;
legalOptions: LegalBasisOption[] = [];
dictionaries: Dictionary[] = [];
form: UntypedFormGroup;
readonly #dossier: Dossier;
#applyToAllDossiers: boolean;
readonly form;
constructor(
private readonly _justificationsService: JustificationsService,
private readonly _activeDossiersService: ActiveDossiersService,
private readonly _dictionaryService: DictionaryService,
private readonly _formBuilder: FormBuilder,
) {
super();
this.#dossier = this._activeDossiersService.find(this.data.dossierId);
this.#applyToAllDossiers = this.data.applyToAllDossiers;
const annotations = this.data.annotations;
const firstEntry = annotations[0];
this.isImage = [...IMAGE_CATEGORIES, 'ocr'].includes(firstEntry.type);
@ -126,7 +121,7 @@ export class EditRedactionDialogComponent
);
}
#getForm(): UntypedFormGroup {
#getForm() {
return this._formBuilder.group({
reason: [null],
comment: [null],

View File

@ -1,40 +0,0 @@
<section class="dialog">
<form (submit)="save()" [formGroup]="form">
<div class="dialog-header heading-l" [translate]="'recategorize-image-dialog.header'"></div>
<div class="dialog-content">
<div class="iqser-input-group required w-400">
<label [translate]="'recategorize-image-dialog.content.type'"></label>
<mat-form-field>
<mat-select
[placeholder]="'recategorize-image-dialog.content.type-placeholder' | translate"
class="full-width"
formControlName="type"
>
<mat-option *ngFor="let option of typeOptions" [value]="option">
{{ translations[option] | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="iqser-input-group w-300">
<label [translate]="'recategorize-image-dialog.content.comment'"></label>
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
</div>
</div>
<div class="dialog-actions">
<iqser-icon-button
[disabled]="disabled"
[label]="'recategorize-image-dialog.actions.save' | translate"
[submit]="true"
[type]="iconButtonTypes.primary"
></iqser-icon-button>
<div class="all-caps-label cancel" mat-dialog-close [translate]="'recategorize-image-dialog.actions.cancel'"></div>
</div>
</form>
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
</section>

View File

@ -1,41 +0,0 @@
import { Component, Inject, OnInit } from '@angular/core';
import { Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { imageCategoriesTranslations } from '@translations/image-categories-translations';
import { Dossier, ImageCategory } from '@red/domain';
import { BaseDialogComponent } from '@iqser/common-ui';
@Component({
templateUrl: './recategorize-image-dialog.component.html',
})
export class RecategorizeImageDialogComponent extends BaseDialogComponent implements OnInit {
readonly typeOptions: ImageCategory[] = ['signature', 'logo', 'formula', 'image'];
readonly translations = imageCategoriesTranslations;
constructor(
protected readonly _dialogRef: MatDialogRef<RecategorizeImageDialogComponent>,
@Inject(MAT_DIALOG_DATA) readonly data: { annotations: AnnotationWrapper[]; dossier: Dossier },
) {
super(_dialogRef);
}
get changed(): boolean {
return this.form.get('type').value !== this.data.annotations[0].type;
}
ngOnInit() {
this.form = this._formBuilder.group({
type: [this.data.annotations[0].type, Validators.required],
comment: [null],
});
this.initialFormValue = this.form.getRawValue();
}
save() {
this._dialogRef.close({
type: this.form.get('type').value,
comment: this.form.get('comment').value,
});
}
}

View File

@ -1,19 +1,19 @@
<section class="dialog">
<form (submit)="save()" [formGroup]="form">
<div
[innerHTML]="'resize-redaction.dialog.header' | translate : { type: redaction.hint ? 'hint' : 'redaction' }"
[innerHTML]="'resize-redaction.dialog.header' | translate: { type: redaction.hint ? 'hint' : 'redaction' }"
class="dialog-header heading-l"
></div>
<div class="dialog-content redaction">
<div class="iqser-input-group w-450">
<div *ngIf="!redaction.isImage" class="iqser-input-group w-450">
<label [translate]="'resize-redaction.dialog.content.original-text'" class="selected-text"></label>
{{ redaction.value }}
<span class="pl-20">{{ redaction.value }}</span>
</div>
<div class="iqser-input-group w-450">
<div *ngIf="!redaction.isImage" class="iqser-input-group w-450">
<label [translate]="'resize-redaction.dialog.content.resized-text'" class="selected-text"></label>
{{ data.text }}
<span class="pl-20">{{ data.text }}</span>
</div>
<iqser-details-radio [options]="options" formControlName="option"></iqser-details-radio>

View File

@ -1,5 +1,8 @@
import { NgModule } from '@angular/core';
import { OverlayModule } from '@angular/cdk/overlay';
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
import {
CapitalizePipe,
CircleButtonComponent,
@ -20,58 +23,54 @@ import {
RoundCheckboxComponent,
StopPropagationDirective,
} from '@iqser/common-ui';
import { TranslateModule } from '@ngx-translate/core';
import { RouterModule } from '@angular/router';
import { FilePreviewScreenComponent } from './file-preview-screen.component';
import { FileWorkloadComponent } from './components/file-workload/file-workload.component';
import { AnnotationDetailsComponent } from './components/annotation-details/annotation-details.component';
import { AnnotationsListComponent } from './components/annotations-list/annotations-list.component';
import { PageIndicatorComponent } from './components/page-indicator/page-indicator.component';
import { PageExclusionComponent } from './components/page-exclusion/page-exclusion.component';
import { AnnotationActionsComponent } from './components/annotation-actions/annotation-actions.component';
import { CommentsComponent } from './components/comments/comments.component';
import { DocumentInfoComponent } from './components/document-info/document-info.component';
import { OverlayModule } from '@angular/cdk/overlay';
import { ViewSwitchComponent } from './components/view-switch/view-switch.component';
import { UserManagementComponent } from './components/user-management/user-management.component';
import { AnnotationReferencesListComponent } from './components/annotation-references-list/annotation-references-list.component';
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';
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
import { ManualAnnotationDialogComponent } from './dialogs/manual-redaction-dialog/manual-annotation-dialog.component';
import { ForceAnnotationDialogComponent } from './dialogs/force-redaction-dialog/force-annotation-dialog.component';
import { ResizeRedactionDialogComponent } from './dialogs/resize-redaction-dialog/resize-redaction-dialog.component';
import { ChangeLegalBasisDialogComponent } from './dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component';
import { RecategorizeImageDialogComponent } from './dialogs/recategorize-image-dialog/recategorize-image-dialog.component';
import { HighlightActionDialogComponent } from './dialogs/highlight-action-dialog/highlight-action-dialog.component';
import { FilePreviewDialogService } from './services/file-preview-dialog.service';
import { DocumentInfoDialogComponent } from './dialogs/document-info-dialog/document-info-dialog.component';
import { ManualRedactionService } from './services/manual-redaction.service';
import { AnnotationWrapperComponent } from './components/annotation-wrapper/annotation-wrapper.component';
import { AnnotationReferenceComponent } from './components/annotation-reference/annotation-reference.component';
import { ImportRedactionsDialogComponent } from './dialogs/import-redactions-dialog/import-redactions-dialog';
import { DocumentUnloadedGuard } from './services/document-unloaded.guard';
import { FilePreviewRightContainerComponent } from './components/right-container/file-preview-right-container.component';
import { ReadonlyBannerComponent } from './components/readonly-banner/readonly-banner.component';
import { PagesComponent } from './components/pages/pages.component';
import { SharedModule } from '@shared/shared.module';
import { SharedDossiersModule } from '../shared-dossiers/shared-dossiers.module';
import { RedactTextDialogComponent } from './dialogs/redact-text-dialog/redact-text-dialog.component';
import { RemoveRedactionDialogComponent } from './dialogs/remove-redaction-dialog/remove-redaction-dialog.component';
import { IqserUsersModule } from '@iqser/common-ui/lib/users';
import { IqserFiltersModule } from '@iqser/common-ui/lib/filtering';
import { StatusBarComponent } from '@iqser/common-ui/lib/shared';
import { TenantPipe } from '@iqser/common-ui/lib/tenants';
import { IqserUsersModule } from '@iqser/common-ui/lib/users';
import { TranslateModule } from '@ngx-translate/core';
import { SharedModule } from '@shared/shared.module';
import { SharedDossiersModule } from '../shared-dossiers/shared-dossiers.module';
import { AnnotationActionsComponent } from './components/annotation-actions/annotation-actions.component';
import { AnnotationCardComponent } from './components/annotation-card/annotation-card.component';
import { AnnotationDetailsComponent } from './components/annotation-details/annotation-details.component';
import { AnnotationReferenceComponent } from './components/annotation-reference/annotation-reference.component';
import { AnnotationReferencesListComponent } from './components/annotation-references-list/annotation-references-list.component';
import { AnnotationReferencesPageIndicatorComponent } from './components/annotation-references-page-indicator/annotation-references-page-indicator.component';
import { AnnotationWrapperComponent } from './components/annotation-wrapper/annotation-wrapper.component';
import { AnnotationsListComponent } from './components/annotations-list/annotations-list.component';
import { CommentsComponent } from './components/comments/comments.component';
import { DocumentInfoComponent } from './components/document-info/document-info.component';
import { FileWorkloadComponent } from './components/file-workload/file-workload.component';
import { HighlightsSeparatorComponent } from './components/highlights-separator/highlights-separator.component';
import { PageExclusionComponent } from './components/page-exclusion/page-exclusion.component';
import { PageIndicatorComponent } from './components/page-indicator/page-indicator.component';
import { PagesComponent } from './components/pages/pages.component';
import { ReadonlyBannerComponent } from './components/readonly-banner/readonly-banner.component';
import { FilePreviewRightContainerComponent } from './components/right-container/file-preview-right-container.component';
import { UserManagementComponent } from './components/user-management/user-management.component';
import { ViewSwitchComponent } from './components/view-switch/view-switch.component';
import { AcceptRecommendationDialogComponent } from './dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component';
import { AddHintDialogComponent } from './dialogs/add-hint-dialog/add-hint-dialog.component';
import { ChangeLegalBasisDialogComponent } from './dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component';
import { AddAnnotationDialogComponent } from './dialogs/docu-mine/add-annotation-dialog/add-annotation-dialog.component';
import { EditAnnotationDialogComponent } from './dialogs/docu-mine/edit-annotation-dialog/edit-annotation-dialog.component';
import { RemoveAnnotationDialogComponent } from './dialogs/docu-mine/remove-annotation-dialog/remove-annotation-dialog.component';
import { ResizeAnnotationDialogComponent } from './dialogs/docu-mine/resize-annotation-dialog/resize-annotation-dialog.component';
import { EditAnnotationDialogComponent } from './dialogs/docu-mine/edit-annotation-dialog/edit-annotation-dialog.component';
import { DocumentInfoDialogComponent } from './dialogs/document-info-dialog/document-info-dialog.component';
import { EditRedactionDialogComponent } from './dialogs/edit-redaction-dialog/edit-redaction-dialog.component';
import { TablesService } from './services/tables.service';
import { ForceAnnotationDialogComponent } from './dialogs/force-redaction-dialog/force-annotation-dialog.component';
import { HighlightActionDialogComponent } from './dialogs/highlight-action-dialog/highlight-action-dialog.component';
import { ImportRedactionsDialogComponent } from './dialogs/import-redactions-dialog/import-redactions-dialog';
import { ManualAnnotationDialogComponent } from './dialogs/manual-redaction-dialog/manual-annotation-dialog.component';
import { RedactRecommendationDialogComponent } from './dialogs/redact-recommendation-dialog/redact-recommendation-dialog.component';
import { RedactTextDialogComponent } from './dialogs/redact-text-dialog/redact-text-dialog.component';
import { RemoveRedactionDialogComponent } from './dialogs/remove-redaction-dialog/remove-redaction-dialog.component';
import { ResizeRedactionDialogComponent } from './dialogs/resize-redaction-dialog/resize-redaction-dialog.component';
import { FilePreviewScreenComponent } from './file-preview-screen.component';
import { DocumentUnloadedGuard } from './services/document-unloaded.guard';
import { FilePreviewDialogService } from './services/file-preview-dialog.service';
import { ManualRedactionService } from './services/manual-redaction.service';
import { TablesService } from './services/tables.service';
const routes: IqserRoutes = [
{
@ -87,7 +86,6 @@ const dialogs = [
ForceAnnotationDialogComponent,
ResizeRedactionDialogComponent,
ChangeLegalBasisDialogComponent,
RecategorizeImageDialogComponent,
HighlightActionDialogComponent,
AcceptRecommendationDialogComponent,
DocumentInfoDialogComponent,

View File

@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
import { IqserDialog } from '@common-ui/dialog/iqser-dialog.service';
import { getConfig } from '@iqser/common-ui';
import { List, log } from '@iqser/common-ui/lib/utils';
import { AnnotationPermissions } from '@models/file/annotation.permissions';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { Core } from '@pdftron/webviewer';
import {
@ -44,7 +45,7 @@ import { SkippedService } from './skipped.service';
@Injectable()
export class AnnotationActionsService {
readonly #isDocumine;
readonly #isDocumine = getConfig().IS_DOCUMINE;
constructor(
private readonly _manualRedactionService: ManualRedactionService,
@ -59,9 +60,7 @@ export class AnnotationActionsService {
private readonly _skippedService: SkippedService,
private readonly _dossierTemplatesService: DossierTemplatesService,
private readonly _permissionsService: PermissionsService,
) {
this.#isDocumine = getConfig().IS_DOCUMINE;
}
) {}
removeHighlights(highlights: AnnotationWrapper[]): void {
const data = this.#getHighlightOperationData(EarmarkOperation.REMOVE, highlights);
@ -73,18 +72,6 @@ export class AnnotationActionsService {
this._dialogService.openDialog('highlightAction', data);
}
rejectSuggestion(annotations: AnnotationWrapper[]) {
const { dossierId, fileId } = this._state;
this.#processObsAndEmit(
this._manualRedactionService.declineOrRemove(
annotations.map(a => a.id),
dossierId,
fileId,
annotations[0].isModifyDictionary,
),
).then();
}
forceAnnotation(annotations: AnnotationWrapper[], hint: boolean = false) {
const { dossierId, fileId } = this._state;
const data = { dossier: this._state.dossier(), annotations, hint };
@ -147,7 +134,7 @@ export class AnnotationActionsService {
await this.#processObsAndEmit(zip(requests).pipe(log()));
}
async removeRedaction(redaction: AnnotationWrapper, permissions) {
async removeRedaction(redaction: AnnotationWrapper, permissions: AnnotationPermissions) {
const removePermissions: RemoveRedactionPermissions = {
canRemoveOnlyHere: permissions.canRemoveOnlyHere,
canRemoveFromDictionary: permissions.canRemoveFromDictionary,
@ -181,19 +168,6 @@ export class AnnotationActionsService {
}
}
recategorizeImages(annotations: AnnotationWrapper[]) {
const data = { annotations, dossier: this._state.dossier() };
const { dossierId, fileId } = this._state;
this._dialogService.openDialog('recategorizeImage', data, ({ comment, type }: { type: string; comment: string }) => {
const body: List<IRecategorizationRequest> = annotations.map(({ id }) => ({
annotationId: id,
type,
comment,
}));
this.#processObsAndEmit(this._manualRedactionService.recategorizeRedactions(body, dossierId, fileId)).then();
});
}
undoDirectAction(annotations: AnnotationWrapper[]) {
const { dossierId, fileId } = this._state;
const modifyDictionary = annotations[0].isModifyDictionary;
@ -231,7 +205,6 @@ export class AnnotationActionsService {
if (annotationWrapper.rectangle || annotationWrapper.imported || annotationWrapper.isImage) {
this._annotationManager.delete(annotationWrapper);
const rectangleAnnotation = this.#generateRectangle(annotationWrapper);
console.log(rectangleAnnotation);
return this._annotationManager.add(rectangleAnnotation);
}
@ -293,11 +266,9 @@ export class AnnotationActionsService {
async cancelResize(annotationWrapper: AnnotationWrapper) {
this._annotationManager.resizingAnnotationId = undefined;
this._annotationManager.resizingHasStarted = false;
this._annotationManager.delete(annotationWrapper);
await this._annotationDrawService.draw([annotationWrapper], this._skippedService.hideSkipped(), this._state.dossierTemplateId);
this._annotationManager.deselect();
await this._fileDataService.annotationsChanged();
}
#generateRectangle(annotationWrapper: AnnotationWrapper) {
@ -333,9 +304,7 @@ export class AnnotationActionsService {
}
async #processObsAndEmit(obs: Observable<unknown>) {
await firstValueFrom(obs)
.then(() => this._fileDataService.annotationsChanged())
.catch(() => this._fileDataService.annotationsChanged());
await firstValueFrom(obs).finally(() => this._fileDataService.annotationsChanged());
}
#getFalsePositiveText(annotation: AnnotationWrapper) {
@ -389,7 +358,7 @@ export class AnnotationActionsService {
rect.topLeft.x + rect.width - 2,
rect.topLeft.y + rect.height - percentHeightOffset,
);
const quadWords = await this._extractTextFromRect(page, pdfNetRect);
const quadWords = await this.#extractTextFromRect(page, pdfNetRect);
words.push(...quadWords);
}
@ -411,7 +380,7 @@ export class AnnotationActionsService {
};
}
private async _extractTextFromRect(page: Core.PDFNet.Page, rect: Core.PDFNet.Rect) {
async #extractTextFromRect(page: Core.PDFNet.Page, rect: Core.PDFNet.Rect) {
const txt = await this._pdf.PDFNet.TextExtractor.create();
await txt.begin(page, rect); // Read the page.

View File

@ -1,23 +1,14 @@
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { ForceAnnotationDialogComponent } from '../dialogs/force-redaction-dialog/force-annotation-dialog.component';
import { DocumentInfoDialogComponent } from '../dialogs/document-info-dialog/document-info-dialog.component';
import { ManualAnnotationDialogComponent } from '../dialogs/manual-redaction-dialog/manual-annotation-dialog.component';
import { ChangeLegalBasisDialogComponent } from '../dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component';
import { RecategorizeImageDialogComponent } from '../dialogs/recategorize-image-dialog/recategorize-image-dialog.component';
import { ConfirmationDialogComponent, DialogConfig, DialogService } from '@iqser/common-ui';
import { ChangeLegalBasisDialogComponent } from '../dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component';
import { DocumentInfoDialogComponent } from '../dialogs/document-info-dialog/document-info-dialog.component';
import { ForceAnnotationDialogComponent } from '../dialogs/force-redaction-dialog/force-annotation-dialog.component';
import { HighlightActionDialogComponent } from '../dialogs/highlight-action-dialog/highlight-action-dialog.component';
import { ManualAnnotationDialogComponent } from '../dialogs/manual-redaction-dialog/manual-annotation-dialog.component';
import { StructuredComponentManagementDialogComponent } from '../dialogs/structured-component-management-dialog/structured-component-management-dialog.component';
type DialogType =
| 'confirm'
| 'documentInfo'
| 'rss'
| 'recategorizeImage'
| 'changeLegalBasis'
| 'forceAnnotation'
| 'manualAnnotation'
| 'highlightAction';
type DialogType = 'confirm' | 'documentInfo' | 'rss' | 'changeLegalBasis' | 'forceAnnotation' | 'manualAnnotation' | 'highlightAction';
@Injectable()
export class FilePreviewDialogService extends DialogService<DialogType> {
@ -30,9 +21,6 @@ export class FilePreviewDialogService extends DialogService<DialogType> {
component: DocumentInfoDialogComponent,
dialogConfig: { autoFocus: true },
},
recategorizeImage: {
component: RecategorizeImageDialogComponent,
},
changeLegalBasis: {
component: ChangeLegalBasisDialogComponent,
},

View File

@ -1,15 +1,15 @@
import { inject, Injectable, NgZone } from '@angular/core';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { AnnotationPermissions } from '@models/file/annotation.permissions';
import { PermissionsService } from '@services/permissions.service';
import { FilePreviewStateService } from './file-preview-state.service';
import { TranslateService } from '@ngx-translate/core';
import { AnnotationActionsService } from './annotation-actions.service';
import { IqserPermissionsService } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { IHeaderElement } from '@red/domain';
import { REDAnnotationManager } from '../../pdf-viewer/services/annotation-manager.service';
import { IqserPermissionsService } from '@iqser/common-ui';
import { BASE_HREF_FN } from '@iqser/common-ui/lib/utils';
import { AnnotationPermissions } from '@models/file/annotation.permissions';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { TranslateService } from '@ngx-translate/core';
import { IHeaderElement } from '@red/domain';
import { PermissionsService } from '@services/permissions.service';
import { REDAnnotationManager } from '../../pdf-viewer/services/annotation-manager.service';
import { AnnotationActionsService } from './annotation-actions.service';
import { FilePreviewStateService } from './file-preview-state.service';
@Injectable()
export class PdfAnnotationActionsService {
@ -27,21 +27,18 @@ export class PdfAnnotationActionsService {
const permissions = this.#getAnnotationsPermissions(annotations);
// you can only resize one annotation at a time
if (permissions.canResize) {
if (permissions.canResizeAnnotation) {
const firstAnnotation = annotations[0];
// if we already entered resize-mode previously
if (firstAnnotation.id === this.#annotationManager.resizingAnnotationId) {
if (this.#annotationManager.resizingHasStarted) {
const acceptResizeButton = this.#getButton('check', _('annotation-actions.resize-accept.label'), () =>
this.#annotationActionsService.acceptResize(firstAnnotation),
);
const cancelResizeButton = this.#getButton('close', _('annotation-actions.resize-cancel.label'), () =>
this.#annotationActionsService.cancelResize(firstAnnotation),
);
const acceptResizeButton = this.#getButton('check', _('annotation-actions.resize-accept.label'), () =>
this.#annotationActionsService.acceptResize(firstAnnotation),
);
const cancelResizeButton = this.#getButton('close', _('annotation-actions.resize-cancel.label'), () =>
this.#annotationActionsService.cancelResize(firstAnnotation),
);
return [acceptResizeButton, cancelResizeButton];
}
return [];
return [acceptResizeButton, cancelResizeButton];
}
const resizeButton = this.#getButton('resize', _('annotation-actions.resize.label'), () =>
@ -103,22 +100,11 @@ export class PdfAnnotationActionsService {
};
}
#getAnnotationsPermissions(annotations: AnnotationWrapper[]) {
#getAnnotationsPermissions(annotations: AnnotationWrapper[]): AnnotationPermissions {
const dossier = this.#state.dossier();
const isApprover = this.#permissionsService.isApprover(dossier);
const dictionaries = this.#state.dictionaries;
const permissions = annotations.map(a => AnnotationPermissions.forUser(isApprover, a, dictionaries, this.#iqserPermissionsService));
return {
canResize: permissions.length === 1 && permissions[0].canResizeAnnotation,
canChangeLegalBasis: permissions.reduce((acc, next) => acc && next.canChangeLegalBasis, true),
canRecategorizeAnnotation: permissions.reduce((acc, next) => acc && next.canRecategorizeAnnotation, true),
canRemoveFromDictionary: permissions.reduce((acc, next) => acc && next.canRemoveFromDictionary, true),
canAcceptRecommendation: permissions.reduce((acc, next) => acc && next.canAcceptRecommendation, true),
canMarkAsFalsePositive: permissions.reduce((acc, next) => acc && next.canMarkAsFalsePositive, true),
canForceRedaction: permissions.reduce((acc, next) => acc && next.canForceRedaction, true),
canForceHint: permissions.reduce((acc, next) => acc && next.canForceHint, true),
canRemoveOnlyHere: permissions.reduce((acc, next) => acc && next.canRemoveOnlyHere, true),
canRemoveRedaction: permissions.reduce((acc, next) => acc && next.canRemoveRedaction, true),
};
return AnnotationPermissions.reduce(permissions);
}
}

View File

@ -286,7 +286,7 @@ export class PdfProxyService {
this.handleAnnotationSelected([]);
}
#processSelectedAnnotations(annotations: Annotation[], action) {
#processSelectedAnnotations(annotations: Annotation[], action: string) {
let nextAnnotations: Annotation[];
if (action === 'deselected') {
@ -313,7 +313,10 @@ export class PdfProxyService {
nextAnnotations = this._annotationManager.selected;
}
this.#configureAnnotationSpecificActions(nextAnnotations);
const annotationWasResized = this._annotationManager.resizingAnnotationId && action === 'modify';
if (!this._annotationManager.resizingAnnotationId || annotationWasResized) {
this.#configureAnnotationSpecificActions(nextAnnotations);
}
if (!(annotations.length === 1 && annotations[0].ReadOnly)) {
this._pdf.enable(TextPopups.ADD_RECTANGLE);
@ -344,7 +347,6 @@ export class PdfProxyService {
// which is automatically converted to redaction when resized,
// so the original annotation(wrapper) is removed
this._annotationManager.resizingAnnotationId = undefined;
this._annotationManager.resizingHasStarted = false;
return;
}
@ -359,20 +361,20 @@ export class PdfProxyService {
}
#configureAnnotationSpecificActions(viewerAnnotations: Annotation[]) {
this._pdf.resetAnnotationActions();
if (!this.canPerformActions()) {
return this._pdf.resetAnnotationActions();
return;
}
const annotationWrappers = viewerAnnotations.map(va => this._fileDataService.find(va.Id)).filter(va => !!va);
this._pdf.resetAnnotationActions();
if (annotationWrappers.length === 0) {
return this.#configureRectangleAnnotationPopup(viewerAnnotations[0]);
}
// Add hide action as last item
const allAnnotationsHaveImageAction = annotationWrappers.reduce((acc, next) => acc && next.isImage, true);
if (allAnnotationsHaveImageAction) {
const allAreImage = annotationWrappers.reduce((acc, next) => acc && next.isImage, true);
if (allAreImage && !this._annotationManager.resizingAnnotationId) {
const allAreVisible = viewerAnnotations.reduce((acc, next) => next.isVisible() && acc, true);
const visibilityButton = {

View File

@ -4,8 +4,6 @@ import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { Core } from '@pdftron/webviewer';
import { IRectangle, ISectionRectangle, SuperTypes } from '@red/domain';
import { DefaultColorsService } from '@services/entity-services/default-colors.service';
import { RedactionLogService } from '@services/files/redaction-log.service';
import { UserPreferenceService } from '@users/user-preference.service';
import { hexToRgb } from '@utils/functions';
import { BoundingBox, Table } from '../../file-preview/services/tables.service';
import { REDAnnotationManager } from './annotation-manager.service';
@ -31,8 +29,6 @@ export function getSectionRectangle(box: BoundingBox): ISectionRectangle {
@Injectable()
export class AnnotationDrawService {
constructor(
private readonly _redactionLogService: RedactionLogService,
private readonly _userPreferenceService: UserPreferenceService,
private readonly _annotationManager: REDAnnotationManager,
private readonly _pdf: PdfViewer,
private readonly _documentViewer: REDDocumentViewer,
@ -41,7 +37,7 @@ export class AnnotationDrawService {
async draw(annotations: List<AnnotationWrapper>, hideSkipped: boolean, dossierTemplateId: string) {
try {
return this._pdf.runWithCleanup(() => this._draw(annotations, hideSkipped, dossierTemplateId));
return this._pdf.runWithCleanup(() => this.#draw(annotations, hideSkipped, dossierTemplateId));
} catch (e) {
console.error(e);
}
@ -70,7 +66,7 @@ export class AnnotationDrawService {
async drawTables(tables: Table[], page: number, dossierTemplateId: string) {
const sections: Core.Annotations.RectangleAnnotation[] = [];
tables.forEach(table => {
const section = this._computeSection(page, getSectionRectangle(table.boundingBox), dossierTemplateId);
const section = this.#computeSection(page, getSectionRectangle(table.boundingBox), dossierTemplateId);
if (table.experimental) {
section.StrokeColor = this.convertColor('#800080');
} else {
@ -80,7 +76,7 @@ export class AnnotationDrawService {
table.cellsPerRow
.flatMap(row => row)
.forEach(row => {
const cellSection = this._computeSection(page, getSectionRectangle(row.boundingBox), dossierTemplateId);
const cellSection = this.#computeSection(page, getSectionRectangle(row.boundingBox), dossierTemplateId);
if (table.experimental) {
cellSection.StrokeColor = this.convertColor('#800080');
} else {
@ -93,10 +89,10 @@ export class AnnotationDrawService {
await this._annotationManager.add(sections);
}
private async _draw(annotationWrappers: List<AnnotationWrapper>, hideSkipped: boolean, dossierTemplateId: string) {
async #draw(annotationWrappers: List<AnnotationWrapper>, hideSkipped: boolean, dossierTemplateId: string) {
const totalPages = this._pdf.totalPages();
const annotations = annotationWrappers
?.map(annotation => this._computeAnnotation(annotation, hideSkipped, totalPages, dossierTemplateId))
?.map(annotation => this.#computeAnnotation(annotation, hideSkipped, totalPages, dossierTemplateId))
.filterTruthy();
if (!this._documentViewer.loaded()) {
return;
@ -104,7 +100,7 @@ export class AnnotationDrawService {
await this._annotationManager.add(annotations);
}
private _computeSection(pageNumber: number, sectionRectangle: ISectionRectangle, dossierTemplateId: string) {
#computeSection(pageNumber: number, sectionRectangle: ISectionRectangle, dossierTemplateId: string) {
const rectangleAnnot = this._pdf.rectangle();
const pageHeight = this._documentViewer.getHeight(pageNumber);
const rectangle: IRectangle = {
@ -125,7 +121,7 @@ export class AnnotationDrawService {
return rectangleAnnot;
}
private _computeAnnotation(annotationWrapper: AnnotationWrapper, hideSkipped: boolean, totalPages: number, dossierTemplateId: string) {
#computeAnnotation(annotationWrapper: AnnotationWrapper, hideSkipped: boolean, totalPages: number, dossierTemplateId: string) {
const pageNumber = this._pdf.isCompareMode() ? annotationWrapper.pageNumber * 2 - 1 : annotationWrapper.pageNumber;
if (pageNumber > totalPages) {
// skip imported annotations from files that have more pages than the current one
@ -150,7 +146,7 @@ export class AnnotationDrawService {
}
const annotation = this._pdf.textHighlight();
annotation.Quads = this._rectanglesToQuads(annotationWrapper.positions, pageNumber);
annotation.Quads = this.#rectanglesToQuads(annotationWrapper.positions, pageNumber);
annotation.Opacity = annotationWrapper.isChangeLogRemoved ? DEFAULT_REMOVED_ANNOTATION_OPACITY : DEFAULT_TEXT_ANNOTATION_OPACITY;
annotation.setContents(annotationWrapper.content);
annotation.PageNumber = pageNumber;
@ -190,12 +186,12 @@ export class AnnotationDrawService {
return annotation;
}
private _rectanglesToQuads(positions: IRectangle[], pageNumber: number): Quad[] {
#rectanglesToQuads(positions: IRectangle[], pageNumber: number): Quad[] {
const pageHeight = this._documentViewer.getHeight(pageNumber);
return positions.map(p => this._rectangleToQuad(p, pageHeight));
return positions.map(p => this.#rectangleToQuad(p, pageHeight));
}
private _rectangleToQuad(rectangle: IRectangle, pageHeight: number): Quad {
#rectangleToQuad(rectangle: IRectangle, pageHeight: number): Quad {
const x1 = rectangle.topLeft.x;
const y1 = pageHeight - (rectangle.topLeft.y + rectangle.height);

View File

@ -1,13 +1,13 @@
import { inject, Injectable, signal } from '@angular/core';
import { Core } from '@pdftron/webviewer';
import type { List } from '@iqser/common-ui/lib/utils';
import { AnnotationPredicate, DeleteAnnotationsOptions } from '../utils/types';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { Subject } from 'rxjs';
import { asList, getId, isStringOrWrapper } from '../utils/functions';
import { Core } from '@pdftron/webviewer';
import { getLast } from '@utils/functions';
import { AnnotationToolNames } from '../utils/constants';
import { NGXLogger } from 'ngx-logger';
import { Subject } from 'rxjs';
import { AnnotationToolNames } from '../utils/constants';
import { asList, getId, isStringOrWrapper } from '../utils/functions';
import { AnnotationPredicate, DeleteAnnotationsOptions } from '../utils/types';
import AnnotationManager = Core.AnnotationManager;
import Annotation = Core.Annotations.Annotation;
@ -19,7 +19,6 @@ export class REDAnnotationManager {
readonly #annotationSelected$ = new Subject<[Annotation[], string]>();
readonly annotationSelected$ = this.#annotationSelected$.asObservable();
resizingAnnotationId?: string = undefined;
resizingHasStarted = false;
readonly hidden = this.#hidden.asReadonly();
get selected() {
@ -141,14 +140,12 @@ export class REDAnnotationManager {
// when a rectangle is drawn,
// it returns one annotation with tool name 'AnnotationCreateRectangle;
// this will auto select rectangle after drawing
if (annotations.length === 1 && annotations[0].ToolName === AnnotationToolNames.AnnotationCreateRectangle) {
this.#manager.selectAnnotations(annotations);
annotations[0].disableRotationControl();
}
if (action === 'modify' && options.source === 'resize' && !this.resizingHasStarted) {
this.resizingHasStarted = true;
this.#annotationSelected$.next([annotations, action]);
const annotation = annotations.length === 1 ? annotations[0] : undefined;
if (annotation && annotation.ToolName === AnnotationToolNames.AnnotationCreateRectangle) {
this.#manager.selectAnnotation(annotation);
annotation.disableRotationControl();
}
this.#annotationSelected$.next([annotations, action]);
});
}

View File

@ -1,18 +1,18 @@
import { effect, inject, Injectable, Signal, signal } from '@angular/core';
import { toObservable } from '@angular/core/rxjs-interop';
import { ActivatedRoute } from '@angular/router';
import { log } from '@iqser/common-ui/lib/utils';
import { Core } from '@pdftron/webviewer';
import { RotationType, RotationTypes } from '@red/domain';
import { UserPreferenceService } from '@users/user-preference.service';
import { NGXLogger } from 'ngx-logger';
import { BehaviorSubject, fromEvent, Observable } from 'rxjs';
import { filter, tap } from 'rxjs/operators';
import { ActivatedRoute } from '@angular/router';
import { PdfViewer } from './pdf-viewer.service';
import { UserPreferenceService } from '@users/user-preference.service';
import { log } from '@iqser/common-ui/lib/utils';
import { stopAndPrevent, stopAndPreventIfNotAllowed } from '../utils/functions';
import { RotationType, RotationTypes } from '@red/domain';
import { AnnotationToolNames } from '../utils/constants';
import { toObservable } from '@angular/core/rxjs-interop';
import DocumentViewer = Core.DocumentViewer;
import { stopAndPrevent, stopAndPreventIfNotAllowed } from '../utils/functions';
import { PdfViewer } from './pdf-viewer.service';
import Color = Core.Annotations.Color;
import DocumentViewer = Core.DocumentViewer;
import Quad = Core.Math.Quad;
@Injectable()

View File

@ -1,26 +1,26 @@
import { DestroyRef, inject, Injectable, signal, Signal } from '@angular/core';
import WebViewer, { Core, WebViewerInstance, WebViewerOptions } from '@pdftron/webviewer';
import { ErrorService, getConfig } from '@iqser/common-ui';
import { AppConfig, File, IHeaderElement } from '@red/domain';
import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
import { ActivatedRoute } from '@angular/router';
import { map, startWith } from 'rxjs/operators';
import { combineLatest, fromEvent, Observable } from 'rxjs';
import { NGXLogger } from 'ngx-logger';
import { DISABLED_HOTKEYS, DOCUMENT_LOADING_ERROR, SEARCH_OPTIONS, USELESS_ELEMENTS } from '../utils/constants';
import { Rgb } from '../utils/types';
import { asList } from '../utils/functions';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { environment } from '@environments/environment';
import { ErrorService, getConfig } from '@iqser/common-ui';
import { BASE_HREF_FN, shareDistinctLast } from '@iqser/common-ui/lib/utils';
import { TranslateService } from '@ngx-translate/core';
import WebViewer, { Core, WebViewerInstance, WebViewerOptions } from '@pdftron/webviewer';
import { AppConfig, File, IHeaderElement } from '@red/domain';
import { LicenseService } from '@services/license.service';
import { UserPreferenceService } from '@users/user-preference.service';
import { environment } from '@environments/environment';
import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { BASE_HREF_FN, shareDistinctLast } from '@iqser/common-ui/lib/utils';
import TextTool = Core.Tools.TextTool;
import { NGXLogger } from 'ngx-logger';
import { combineLatest, fromEvent, Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
import { DISABLED_HOTKEYS, DOCUMENT_LOADING_ERROR, SEARCH_OPTIONS, USELESS_ELEMENTS } from '../utils/constants';
import { asList } from '../utils/functions';
import { Rgb } from '../utils/types';
import Annotation = Core.Annotations.Annotation;
import TextHighlightAnnotation = Core.Annotations.TextHighlightAnnotation;
import DocumentViewer = Core.DocumentViewer;
import Quad = Core.Math.Quad;
import TextTool = Core.Tools.TextTool;
@Injectable()
export class PdfViewer {
@ -40,6 +40,7 @@ export class PdfViewer {
},
};
readonly #destroyRef = inject(DestroyRef);
readonly #logger = inject(NGXLogger);
readonly #totalPages = signal<number>(0);
readonly currentPage$ = inject(ActivatedRoute).queryParamMap.pipe(
map(params => Number(params.get('page') ?? '1')),
@ -118,6 +119,7 @@ export class PdfViewer {
resetAnnotationActions() {
if (this.#instance.UI.annotationPopup.getItems().length) {
this.#logger.info('[PDF] Reset annotation actions');
this.#instance.UI.annotationPopup.update([]);
}
}

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<svg fill="currentColor" id="ReadactManager_symbol_positive"
<svg fill="#dd4d50" id="ReadactManager_symbol_positive"
version="1.1" viewBox="0 0 119.476 121.277" x="0px" xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
y="0px">
@ -8,5 +8,5 @@
s-1.904-4.254-4.254-4.254H28.843H15.793H4.254C1.904,73.401,0,71.496,0,69.146c0-2.349,1.904-4.254,4.254-4.254h24.59h41.062
c2.349,0,4.255-1.904,4.255-4.254s-1.906-4.254-4.255-4.254H28.843H4.254C1.904,56.385,0,54.48,0,52.13
c0-2.349,1.904-4.254,4.254-4.254h11.539h13.051H52.6c2.349,0,4.254-1.905,4.254-4.254c0-2.35-1.904-4.254-4.254-4.254H28.843
h-7.283c-2.349,0-4.254-1.905-4.254-4.254c0-2.349,1.904-4.254,4.254-4.254h7.283V14.712C59.063,14.712,74.16,0,74.16,0z" />
h-7.283c-2.349,0-4.254-1.905-4.254-4.254c0-2.349,1.904-4.254,4.254-4.254h7.283V14.712C59.063,14.712,74.16,0,74.16,0z"/>
</svg>

Before

Width:  |  Height:  |  Size: 948 B

After

Width:  |  Height:  |  Size: 942 B

@ -1 +1 @@
Subproject commit c9abddb3017082bb169ef6da2c1a63b0b3cdf837
Subproject commit 21d387912ca7788f4dc9675edca1c0cdc40e8bac