Merge branch 'VM/RED-6774'
This commit is contained in:
commit
cabc3fb15b
@ -10,6 +10,7 @@ export class AnnotationPermissions {
|
||||
canMarkAsFalsePositive = true;
|
||||
canRemoveOrSuggestToRemoveOnlyHere = true;
|
||||
canRemoveOrSuggestToRemoveFromDictionary = true;
|
||||
canRemoveOrSuggestToRemoveRedaction = true;
|
||||
canAcceptSuggestion = true;
|
||||
canRejectSuggestion = true;
|
||||
canForceRedaction = true;
|
||||
@ -63,6 +64,12 @@ export class AnnotationPermissions {
|
||||
!annotation.pending &&
|
||||
!annotation.hasBeenResized;
|
||||
|
||||
permissions.canRemoveOrSuggestToRemoveRedaction =
|
||||
annotations.length === 1 &&
|
||||
(permissions.canRemoveOrSuggestToRemoveOnlyHere ||
|
||||
permissions.canRemoveOrSuggestToRemoveFromDictionary ||
|
||||
permissions.canMarkAsFalsePositive);
|
||||
|
||||
permissions.canChangeLegalBasis = canAddOrRequestRedaction && annotation.isRedacted && !annotation.pending;
|
||||
|
||||
permissions.canRecategorizeImage =
|
||||
|
||||
@ -2,6 +2,8 @@ import { IManualRedactionEntry } from '@red/domain';
|
||||
|
||||
export const ManualRedactionEntryTypes = {
|
||||
DICTIONARY: 'DICTIONARY',
|
||||
REDACT: 'REDACT',
|
||||
HINT: 'HINT',
|
||||
REDACTION: 'REDACTION',
|
||||
FALSE_POSITIVE: 'FALSE_POSITIVE',
|
||||
} as const;
|
||||
|
||||
@ -150,29 +150,38 @@
|
||||
icon="red:visibility"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="removeOrSuggestRemoveAnnotation(true)"
|
||||
*ngIf="annotationPermissions.canRemoveOrSuggestToRemoveFromDictionary"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.remove-annotation.remove-from-dict' | translate"
|
||||
[type]="buttonType"
|
||||
icon="red:remove-from-dict"
|
||||
></iqser-circle-button>
|
||||
<!-- <iqser-circle-button-->
|
||||
<!-- (action)="removeOrSuggestRemoveAnnotation(true)"-->
|
||||
<!-- *ngIf="annotationPermissions.canRemoveOrSuggestToRemoveFromDictionary"-->
|
||||
<!-- [tooltipPosition]="tooltipPosition"-->
|
||||
<!-- [tooltip]="'annotation-actions.remove-annotation.remove-from-dict' | translate"-->
|
||||
<!-- [type]="buttonType"-->
|
||||
<!-- icon="red:remove-from-dict"-->
|
||||
<!-- ></iqser-circle-button>-->
|
||||
|
||||
<!-- <iqser-circle-button-->
|
||||
<!-- (action)="markAsFalsePositive()"-->
|
||||
<!-- *ngIf="annotationPermissions.canMarkAsFalsePositive"-->
|
||||
<!-- [tooltipPosition]="tooltipPosition"-->
|
||||
<!-- [tooltip]="'annotation-actions.remove-annotation.false-positive' | translate"-->
|
||||
<!-- [type]="buttonType"-->
|
||||
<!-- icon="red:thumb-down"-->
|
||||
<!-- ></iqser-circle-button>-->
|
||||
|
||||
<!-- <iqser-circle-button-->
|
||||
<!-- (action)="removeOrSuggestRemoveAnnotation(false)"-->
|
||||
<!-- *ngIf="annotationPermissions.canRemoveOrSuggestToRemoveOnlyHere"-->
|
||||
<!-- [tooltipPosition]="tooltipPosition"-->
|
||||
<!-- [tooltip]="'annotation-actions.remove-annotation.only-here' | translate"-->
|
||||
<!-- [type]="buttonType"-->
|
||||
<!-- icon="iqser:trash"-->
|
||||
<!-- ></iqser-circle-button>-->
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="markAsFalsePositive()"
|
||||
*ngIf="annotationPermissions.canMarkAsFalsePositive"
|
||||
(action)="removeOrSuggestRemoveRedaction()"
|
||||
*ngIf="annotationPermissions.canRemoveOrSuggestToRemoveRedaction"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.remove-annotation.false-positive' | translate"
|
||||
[type]="buttonType"
|
||||
icon="red:thumb-down"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="removeOrSuggestRemoveAnnotation(false)"
|
||||
*ngIf="annotationPermissions.canRemoveOrSuggestToRemoveOnlyHere"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.remove-annotation.only-here' | translate"
|
||||
[tooltip]="'annotation-actions.remove-annotation.remove-redaction' | translate"
|
||||
[type]="buttonType"
|
||||
icon="iqser:trash"
|
||||
></iqser-circle-button>
|
||||
|
||||
@ -10,6 +10,7 @@ import { HelpModeService, IqserPermissionsService } from '@iqser/common-ui';
|
||||
import { ViewModeService } from '../../services/view-mode.service';
|
||||
import { REDAnnotationManager } from '../../../pdf-viewer/services/annotation-manager.service';
|
||||
import { Roles } from '@users/roles';
|
||||
import { RemoveRedactionPermissions } from '../../dialogs/remove-redaction-dialog/remove-redaction-dialog.component';
|
||||
|
||||
export const AnnotationButtonTypes = {
|
||||
dark: 'dark',
|
||||
@ -78,6 +79,10 @@ export class AnnotationActionsComponent implements OnChanges {
|
||||
this.annotationActionsService.removeOrSuggestRemoveAnnotation(this.annotations, removeFromDict);
|
||||
}
|
||||
|
||||
removeOrSuggestRemoveRedaction() {
|
||||
this.annotationActionsService.removeOrSuggestRemoveRedaction(this.annotations[0], this.annotationPermissions);
|
||||
}
|
||||
|
||||
markAsFalsePositive() {
|
||||
this.annotationActionsService.markAsFalsePositive(this.annotations);
|
||||
}
|
||||
|
||||
@ -0,0 +1,91 @@
|
||||
<section class="dialog">
|
||||
<form (submit)="save()" [formGroup]="form">
|
||||
<div [translate]="'redact-text.dialog.title'" class="dialog-header heading-l"></div>
|
||||
|
||||
<div class="dialog-content">
|
||||
<div class="iqser-input-group w-450">
|
||||
<label [translate]="'redact-text.dialog.content.selected-text'"></label>
|
||||
{{ form.get('selectedText').value }}
|
||||
</div>
|
||||
|
||||
<iqser-details-radio
|
||||
[options]="options"
|
||||
(extraOptionChanged)="extraOptionChanged($event)"
|
||||
formControlName="option"
|
||||
></iqser-details-radio>
|
||||
|
||||
<ng-container *deny="roles.getRss; if: !dictionaryRequest && type !== 'HINT'">
|
||||
<div class="iqser-input-group required w-450">
|
||||
<label [translate]="'redact-text.dialog.content.reason'"></label>
|
||||
<mat-form-field>
|
||||
<mat-select
|
||||
[placeholder]="'redact-text.dialog.content.reason-placeholder' | translate"
|
||||
class="full-width"
|
||||
formControlName="reason"
|
||||
>
|
||||
<mat-option
|
||||
*ngFor="let option of legalOptions"
|
||||
[matTooltip]="option.description"
|
||||
[value]="option"
|
||||
matTooltipPosition="after"
|
||||
>
|
||||
{{ option.label }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="iqser-input-group w-450">
|
||||
<label [translate]="'redact-text.dialog.content.legal-basis'"></label>
|
||||
<input [value]="form.get('reason').value?.legalBasis" disabled type="text" />
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *deny="roles.getRss; if: dictionaryRequest || type === 'HINT'">
|
||||
<div class="iqser-input-group required w-450">
|
||||
<label [translate]="'redact-text.dialog.content.type'"></label>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-select formControlName="dictionary" [placeholder]="'redact-text.dialog.content.type-placeholder' | translate">
|
||||
<mat-select-trigger>{{ displayedDictionaryLabel }}</mat-select-trigger>
|
||||
<mat-option
|
||||
*ngFor="let dictionary of dictionaries"
|
||||
[matTooltip]="dictionary.description"
|
||||
[value]="dictionary.type"
|
||||
matTooltipPosition="after"
|
||||
>
|
||||
<span> {{ dictionary.label }} </span>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="iqser-input-group w-450">
|
||||
<label [translate]="'redact-text.dialog.content.comment'"></label>
|
||||
<textarea
|
||||
formControlName="comment"
|
||||
iqserHasScrollbar
|
||||
name="comment"
|
||||
rows="4"
|
||||
type="text"
|
||||
[placeholder]="'redact-text.dialog.content.comment-placeholder' | translate"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<iqser-icon-button
|
||||
[label]="'redact-text.dialog.actions.save' | translate"
|
||||
[submit]="true"
|
||||
[type]="iconButtonTypes.primary"
|
||||
[disabled]="disabled"
|
||||
>
|
||||
</iqser-icon-button>
|
||||
|
||||
<div class="all-caps-label cancel" mat-dialog-close [translate]="'redact-text.dialog.actions.cancel'"></div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||
</section>
|
||||
@ -0,0 +1,8 @@
|
||||
label {
|
||||
font-weight: bold;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
iqser-details-radio {
|
||||
padding-top: 20px;
|
||||
}
|
||||
@ -0,0 +1,225 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { DetailsRadioOption, IconButtonTypes, IqserPermissionsService } from '@iqser/common-ui';
|
||||
import { Dictionary, Dossier, DossierTemplate, File, IAddRedactionRequest, IManualRedactionEntry, SuperTypes } from '@red/domain';
|
||||
import { FormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||
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 {
|
||||
ManualRedactionEntryType,
|
||||
ManualRedactionEntryTypes,
|
||||
ManualRedactionEntryWrapper,
|
||||
} from '@models/file/manual-redaction-entry.wrapper';
|
||||
import { redactTextTranslations } from '@translations/redact-text-translations';
|
||||
import { RedactTextOption, RedactTextOptions } from './redact-text-options';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { IqserDialogComponent } from '../../../../../../../../libs/common-ui/src/lib/dialog/iqser-dialog-component.directive';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
|
||||
const PIN_ICON = 'red:push-pin';
|
||||
const FOLDER_ICON = 'red:folder';
|
||||
|
||||
interface RedactTextData {
|
||||
manualRedactionEntryWrapper: ManualRedactionEntryWrapper;
|
||||
dossierId: string;
|
||||
file: File;
|
||||
}
|
||||
|
||||
interface DialogResult {
|
||||
redaction: IManualRedactionEntry;
|
||||
dictionary: Dictionary;
|
||||
applyToAllDossiers: boolean | null;
|
||||
}
|
||||
|
||||
@Component({
|
||||
templateUrl: './redact-text-dialog.component.html',
|
||||
styleUrls: ['./redact-text-dialog.component.scss'],
|
||||
})
|
||||
export class RedactTextDialogComponent
|
||||
extends IqserDialogComponent<RedactTextDialogComponent, RedactTextData, DialogResult>
|
||||
implements OnInit
|
||||
{
|
||||
readonly roles = Roles;
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
readonly options: DetailsRadioOption<RedactTextOption>[];
|
||||
readonly type: ManualRedactionEntryType;
|
||||
dictionaryRequest = false;
|
||||
legalOptions: LegalBasisOption[] = [];
|
||||
dictionaries: Dictionary[] = [];
|
||||
form!: UntypedFormGroup;
|
||||
|
||||
#manualRedactionTypeExists = true;
|
||||
#applyToAllDossiers = true;
|
||||
|
||||
readonly #translations = redactTextTranslations;
|
||||
readonly #dossier: Dossier;
|
||||
readonly #hint: boolean;
|
||||
|
||||
constructor(
|
||||
private readonly _justificationsService: JustificationsService,
|
||||
private readonly _activeDossiersService: ActiveDossiersService,
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
private readonly _iqserPermissionsService: IqserPermissionsService,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
) {
|
||||
super();
|
||||
this.#dossier = _activeDossiersService.find(this.data.dossierId);
|
||||
this.type = this.data.manualRedactionEntryWrapper.type;
|
||||
this.#hint = this.type === ManualRedactionEntryTypes.HINT;
|
||||
this.#manualRedactionTypeExists = this._dictionaryService.hasManualType(this.#dossier.dossierTemplateId);
|
||||
this.options = this.#options();
|
||||
|
||||
this.form = this.#getForm();
|
||||
|
||||
this.form
|
||||
.get('option')
|
||||
.valueChanges.pipe(
|
||||
tap((option: DetailsRadioOption<RedactTextOption>) => {
|
||||
this.dictionaryRequest = option.value === RedactTextOptions.IN_DOSSIER;
|
||||
this.#resetValues();
|
||||
}),
|
||||
takeUntilDestroyed(),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
get displayedDictionaryLabel() {
|
||||
const dictType = this.form.get('dictionary').value;
|
||||
if (dictType) {
|
||||
return this.dictionaries.find(d => d.type === dictType)?.label ?? null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
get disabled() {
|
||||
if (this.dictionaryRequest || this.#hint) {
|
||||
return !this.form.get('dictionary').value;
|
||||
}
|
||||
return !this.form.get('reason').value;
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.dictionaries = this._dictionaryService.getPossibleDictionaries(this.#dossier.dossierTemplateId, this.#hint);
|
||||
|
||||
const data = await firstValueFrom(this._justificationsService.getForDossierTemplate(this.#dossier.dossierTemplateId));
|
||||
this.legalOptions = data.map(lbm => ({
|
||||
legalBasis: lbm.reason,
|
||||
description: lbm.description,
|
||||
label: lbm.name,
|
||||
}));
|
||||
this.legalOptions.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
this.#selectReason();
|
||||
this.#formatSelectedTextValue();
|
||||
}
|
||||
|
||||
extraOptionChanged(option: DetailsRadioOption<RedactTextOption>): void {
|
||||
this.#applyToAllDossiers = option.extraOption.checked;
|
||||
}
|
||||
|
||||
save(): void {
|
||||
this.#enhanceManualRedaction(this.data.manualRedactionEntryWrapper.manualRedactionEntry);
|
||||
const redaction = this.data.manualRedactionEntryWrapper.manualRedactionEntry;
|
||||
this.dialogRef.close({
|
||||
redaction,
|
||||
dictionary: this.dictionaries.find(d => d.type === this.form.get('dictionary').value),
|
||||
applyToAllDossiers: this.dictionaryRequest ? this.#applyToAllDossiers : null,
|
||||
});
|
||||
}
|
||||
|
||||
#getForm(): UntypedFormGroup {
|
||||
return this._formBuilder.group({
|
||||
selectedText: this.data?.manualRedactionEntryWrapper?.manualRedactionEntry?.value,
|
||||
reason: [null],
|
||||
comment: [null],
|
||||
dictionary: [this.#manualRedactionTypeExists ? SuperTypes.ManualRedaction : null],
|
||||
classification: ['non-readable content'],
|
||||
section: [null],
|
||||
option: [this.options[0]],
|
||||
});
|
||||
}
|
||||
|
||||
#formatSelectedTextValue(): void {
|
||||
this.data.manualRedactionEntryWrapper.manualRedactionEntry.value =
|
||||
this.data.manualRedactionEntryWrapper.manualRedactionEntry.value.replace(
|
||||
// eslint-disable-next-line no-control-regex,max-len
|
||||
/([^\s\d-]{2,})[-\u00AD]\u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029]/gi,
|
||||
'$1',
|
||||
);
|
||||
}
|
||||
|
||||
#selectReason() {
|
||||
if (this.legalOptions.length === 1) {
|
||||
this.form.get('reason').setValue(this.legalOptions[0]);
|
||||
}
|
||||
}
|
||||
|
||||
#enhanceManualRedaction(addRedactionRequest: IAddRedactionRequest) {
|
||||
const legalOption: LegalBasisOption = this.form.get('reason').value;
|
||||
addRedactionRequest.type = this.form.get('dictionary').value;
|
||||
if (legalOption) {
|
||||
addRedactionRequest.reason = legalOption.description;
|
||||
addRedactionRequest.legalBasis = legalOption.legalBasis;
|
||||
}
|
||||
|
||||
if (this._iqserPermissionsService.has(Roles.getRss)) {
|
||||
const selectedType = this.dictionaries.find(d => d.type === addRedactionRequest.type);
|
||||
addRedactionRequest.addToDictionary = selectedType.hasDictionary;
|
||||
} else {
|
||||
addRedactionRequest.addToDictionary = this.dictionaryRequest && addRedactionRequest.type !== 'dossier_redaction';
|
||||
addRedactionRequest.addToDossierDictionary = this.dictionaryRequest && addRedactionRequest.type === 'dossier_redaction';
|
||||
}
|
||||
|
||||
if (!addRedactionRequest.reason) {
|
||||
addRedactionRequest.reason = 'Dictionary Request';
|
||||
}
|
||||
const commentValue = this.form.get('comment').value;
|
||||
addRedactionRequest.comment = commentValue ? { text: commentValue } : null;
|
||||
addRedactionRequest.section = this.form.get('section').value;
|
||||
addRedactionRequest.value = addRedactionRequest.rectangle
|
||||
? this.form.get('classification').value
|
||||
: this.form.get('selectedText').value;
|
||||
}
|
||||
|
||||
#options() {
|
||||
const options: DetailsRadioOption<RedactTextOption>[] = [
|
||||
{
|
||||
label: this.#translations[this.type].onlyHere.label,
|
||||
description: this.#translations[this.type].onlyHere.description,
|
||||
icon: PIN_ICON,
|
||||
value: RedactTextOptions.ONLY_HERE,
|
||||
},
|
||||
];
|
||||
if (!this._iqserPermissionsService.has(Roles.getRss)) {
|
||||
options.push({
|
||||
label: this.#translations[this.type].inDossier.label,
|
||||
description: this.#translations[this.type].inDossier.description,
|
||||
descriptionParams: { dossierName: this.#dossier.dossierName },
|
||||
icon: FOLDER_ICON,
|
||||
value: RedactTextOptions.IN_DOSSIER,
|
||||
extraOption: {
|
||||
label: this.#translations[this.type].inDossier.extraOptionLabel,
|
||||
checked: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
#resetValues() {
|
||||
this.#applyToAllDossiers = true;
|
||||
this.options[1].extraOption.checked = true;
|
||||
if (this.dictionaryRequest) {
|
||||
this.form.get('reason').setValue(null);
|
||||
this.form.get('dictionary').setValue(null);
|
||||
return;
|
||||
}
|
||||
this.form.get('dictionary').setValue(this.#manualRedactionTypeExists ? SuperTypes.ManualRedaction : null);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
export const RedactTextOptions = {
|
||||
ONLY_HERE: 'ONLY_HERE',
|
||||
IN_DOSSIER: 'IN_DOSSIER',
|
||||
} as const;
|
||||
|
||||
export type RedactTextOption = keyof typeof RedactTextOptions;
|
||||
@ -0,0 +1,123 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { DetailsRadioOption, IconButtonTypes } from '@iqser/common-ui';
|
||||
import { RemoveRedactionOption, RemoveRedactionOptions } from './remove-redaction-options';
|
||||
import { FormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||
import { removeRedactionTranslations } from '@translations/remove-redaction-translations';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { Dossier } from '@red/domain';
|
||||
import { IqserDialogComponent } from '../../../../../../../../libs/common-ui/src/lib/dialog/iqser-dialog-component.directive';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
const PIN_ICON = 'red:push-pin';
|
||||
const FOLDER_ICON = 'red:folder';
|
||||
const REMOVE_FROM_DICT_ICON = 'red:remove-from-dict';
|
||||
|
||||
export interface RemoveRedactionPermissions {
|
||||
canRemoveOrSuggestToRemoveOnlyHere: boolean;
|
||||
canRemoveOrSuggestToRemoveFromDictionary: boolean;
|
||||
canMarkAsFalsePositive: boolean;
|
||||
}
|
||||
|
||||
export interface RemoveRedactionData {
|
||||
redaction: AnnotationWrapper;
|
||||
dossier: Dossier;
|
||||
falsePositiveContext: string;
|
||||
permissions: RemoveRedactionPermissions;
|
||||
}
|
||||
|
||||
export interface RemoveRedactionResult {
|
||||
comment: string;
|
||||
option: DetailsRadioOption<RemoveRedactionOption>;
|
||||
applyToAllDossiers: boolean | null;
|
||||
}
|
||||
|
||||
@Component({
|
||||
templateUrl: './remove-redaction-dialog.html',
|
||||
styleUrls: ['./remove-redaction-dialog.component.scss'],
|
||||
})
|
||||
export class RemoveRedactionDialogComponent extends IqserDialogComponent<
|
||||
RemoveRedactionDialogComponent,
|
||||
RemoveRedactionData,
|
||||
RemoveRedactionResult
|
||||
> {
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
readonly options: DetailsRadioOption<RemoveRedactionOption>[];
|
||||
|
||||
form!: UntypedFormGroup;
|
||||
|
||||
readonly #redaction: AnnotationWrapper;
|
||||
readonly #permissions: RemoveRedactionPermissions;
|
||||
readonly #translations = removeRedactionTranslations;
|
||||
|
||||
constructor(private readonly _formBuilder: FormBuilder, private readonly _permissionsService: PermissionsService) {
|
||||
super();
|
||||
this.#redaction = this.data.redaction;
|
||||
this.#permissions = this.data.permissions;
|
||||
this.options = this.#options();
|
||||
this.form = this.#getForm();
|
||||
|
||||
this.form
|
||||
.get('option')
|
||||
.valueChanges.pipe(
|
||||
tap(() => {
|
||||
this.options[1].extraOption.checked = true;
|
||||
this.options[2].extraOption.checked = true;
|
||||
}),
|
||||
takeUntilDestroyed(),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
save(): void {
|
||||
this.dialogRef.close(this.form.getRawValue());
|
||||
}
|
||||
|
||||
#getForm(): UntypedFormGroup {
|
||||
return this._formBuilder.group({
|
||||
comment: [null],
|
||||
option: [this.options[0]],
|
||||
});
|
||||
}
|
||||
|
||||
#options() {
|
||||
const options: DetailsRadioOption<RemoveRedactionOption>[] = [];
|
||||
if (this.#permissions.canRemoveOrSuggestToRemoveOnlyHere) {
|
||||
options.push({
|
||||
label: this.#translations.ONLY_HERE.label,
|
||||
description: this.#translations.ONLY_HERE.description,
|
||||
descriptionParams: { value: this.#redaction.value },
|
||||
icon: PIN_ICON,
|
||||
value: RemoveRedactionOptions.ONLY_HERE,
|
||||
});
|
||||
}
|
||||
if (this.#permissions.canRemoveOrSuggestToRemoveFromDictionary) {
|
||||
options.push({
|
||||
label: this.#translations.IN_DOSSIER.label,
|
||||
description: this.#translations.IN_DOSSIER.description,
|
||||
descriptionParams: { value: this.#redaction.value },
|
||||
icon: FOLDER_ICON,
|
||||
value: RemoveRedactionOptions.IN_DOSSIER,
|
||||
extraOption: {
|
||||
label: this.#translations.IN_DOSSIER.extraOptionLabel,
|
||||
checked: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (this.#permissions.canMarkAsFalsePositive) {
|
||||
options.push({
|
||||
label: this.#translations.FALSE_POSITIVE.label,
|
||||
description: this.#translations.FALSE_POSITIVE.description,
|
||||
descriptionParams: { value: this.#redaction.value, type: this.#redaction.type, context: this.data.falsePositiveContext },
|
||||
icon: REMOVE_FROM_DICT_ICON,
|
||||
value: RemoveRedactionOptions.FALSE_POSITIVE,
|
||||
extraOption: {
|
||||
label: this.#translations.FALSE_POSITIVE.extraOptionLabel,
|
||||
checked: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
return options;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
<section class="dialog">
|
||||
<form (submit)="save()" [formGroup]="form">
|
||||
<div [translate]="'remove-redaction.dialog.title'" class="dialog-header heading-l"></div>
|
||||
|
||||
<div class="dialog-content">
|
||||
<iqser-details-radio [options]="options" formControlName="option"></iqser-details-radio>
|
||||
|
||||
<div class="iqser-input-group w-450">
|
||||
<label [translate]="'remove-redaction.dialog.content.comment'"></label>
|
||||
<textarea
|
||||
formControlName="comment"
|
||||
iqserHasScrollbar
|
||||
name="comment"
|
||||
rows="4"
|
||||
type="text"
|
||||
[placeholder]="'remove-redaction.dialog.content.comment-placeholder' | translate"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<iqser-icon-button
|
||||
[label]="'remove-redaction.dialog.actions.save' | translate"
|
||||
[submit]="true"
|
||||
[type]="iconButtonTypes.primary"
|
||||
>
|
||||
</iqser-icon-button>
|
||||
|
||||
<div class="all-caps-label cancel" mat-dialog-close [translate]="'remove-redaction.dialog.actions.cancel'"></div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||
</section>
|
||||
@ -0,0 +1,7 @@
|
||||
export const RemoveRedactionOptions = {
|
||||
ONLY_HERE: 'ONLY_HERE',
|
||||
IN_DOSSIER: 'IN_DOSSIER',
|
||||
FALSE_POSITIVE: 'FALSE_POSITIVE',
|
||||
} as const;
|
||||
|
||||
export type RemoveRedactionOption = keyof typeof RemoveRedactionOptions;
|
||||
@ -39,7 +39,7 @@ import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { AnnotationDrawService } from '../pdf-viewer/services/annotation-draw.service';
|
||||
import { AnnotationProcessingService } from './services/annotation-processing.service';
|
||||
import { Dictionary, File, ViewModes } from '@red/domain';
|
||||
import { Dictionary, File, IManualRedactionEntry, ViewModes } from '@red/domain';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { combineLatest, firstValueFrom, of, pairwise } from 'rxjs';
|
||||
import { PreferencesKeys, UserPreferenceService } from '@users/user-preference.service';
|
||||
@ -74,6 +74,9 @@ import { ConfigService } from '@services/config.service';
|
||||
import { ReadableRedactionsService } from '../pdf-viewer/services/readable-redactions.service';
|
||||
import { Roles } from '@users/roles';
|
||||
import { SuggestionsService } from './services/suggestions.service';
|
||||
import { IqserDialog } from '../../../../../../libs/common-ui/src/lib/dialog/iqser-dialog.service';
|
||||
import { RedactTextDialogComponent } from './dialogs/redact-text-dialog/redact-text-dialog.component';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
|
||||
const textActions = [TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE];
|
||||
|
||||
@ -124,6 +127,7 @@ export class FilePreviewScreenComponent
|
||||
private readonly _changeRef: ChangeDetectorRef,
|
||||
private readonly _tenantsService: TenantsService,
|
||||
private readonly _dialogService: FilePreviewDialogService,
|
||||
private readonly _iqserDialog: IqserDialog,
|
||||
private readonly _pageRotationService: PageRotationService,
|
||||
private readonly _viewerHeaderService: ViewerHeaderService,
|
||||
private readonly _annotationDrawService: AnnotationDrawService,
|
||||
@ -137,6 +141,7 @@ export class FilePreviewScreenComponent
|
||||
private readonly _readableRedactionsService: ReadableRedactionsService,
|
||||
private readonly _helpModeService: HelpModeService,
|
||||
private readonly _suggestionsService: SuggestionsService,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _dialog: MatDialog,
|
||||
) {
|
||||
super();
|
||||
@ -366,6 +371,34 @@ export class FilePreviewScreenComponent
|
||||
);
|
||||
}
|
||||
|
||||
async openRedactTextDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) {
|
||||
const file = this.state.file();
|
||||
const result = await this._iqserDialog
|
||||
.openDefault(RedactTextDialogComponent, {
|
||||
data: { manualRedactionEntryWrapper, dossierId: this.dossierId, file },
|
||||
})
|
||||
.result();
|
||||
|
||||
if (result) {
|
||||
const add$ = this._manualRedactionService.addAnnotation(
|
||||
[result.redaction],
|
||||
this.dossierId,
|
||||
this.fileId,
|
||||
result.dictionary?.label,
|
||||
);
|
||||
|
||||
if (result.applyToAllDossiers !== null) {
|
||||
const dossierTemplate = this._dossierTemplatesService.find(this.state.dossierTemplateId);
|
||||
const { ...body } = dossierTemplate;
|
||||
body.applyDictionaryUpdatesToAllDossiersByDefault = result.applyToAllDossiers;
|
||||
await this._dossierTemplatesService.createOrUpdate(body);
|
||||
}
|
||||
|
||||
const addAndReload$ = add$.pipe(switchMap(() => this._filesService.reload(this.dossierId, file)));
|
||||
return firstValueFrom(addAndReload$.pipe(catchError(() => of(undefined))));
|
||||
}
|
||||
}
|
||||
|
||||
toggleFullScreen() {
|
||||
this.fullScreen = !this.fullScreen;
|
||||
if (this.fullScreen) {
|
||||
@ -623,6 +656,10 @@ export class FilePreviewScreenComponent
|
||||
this.openManualAnnotationDialog($event);
|
||||
});
|
||||
|
||||
this.addActiveScreenSubscription = this.pdfProxyService.redactTextRequested$.subscribe($event => {
|
||||
this.openRedactTextDialog($event);
|
||||
});
|
||||
|
||||
this.addActiveScreenSubscription = this.pdfProxyService.pageChanged$.subscribe(page =>
|
||||
this._ngZone.run(() => this.#updateQueryParamsPage(page)),
|
||||
);
|
||||
|
||||
@ -65,6 +65,8 @@ import { PagesComponent } from './components/pages/pages.component';
|
||||
import { SharedModule } from '@shared/shared.module';
|
||||
import { SharedDossiersModule } from '../shared-dossiers/shared-dossiers.module';
|
||||
import { FalsePositiveDialogComponent } from './dialogs/false-positive-dialog/false-positive-dialog.component';
|
||||
import { RedactTextDialogComponent } from './dialogs/redact-text-dialog/redact-text-dialog.component';
|
||||
import { RemoveRedactionDialogComponent } from './dialogs/remove-redaction-dialog/remove-redaction-dialog.component';
|
||||
|
||||
const routes: IqserRoutes = [
|
||||
{
|
||||
@ -89,6 +91,8 @@ const dialogs = [
|
||||
ImportRedactionsDialogComponent,
|
||||
RssDialogComponent,
|
||||
FalsePositiveDialogComponent,
|
||||
RedactTextDialogComponent,
|
||||
RemoveRedactionDialogComponent,
|
||||
];
|
||||
|
||||
const components = [
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ManualRedactionService } from './manual-redaction.service';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { firstValueFrom, Observable } from 'rxjs';
|
||||
import { firstValueFrom, Observable, of } from 'rxjs';
|
||||
import { getFirstRelevantTextPart } from '../../../utils';
|
||||
import { Core } from '@pdftron/webviewer';
|
||||
import {
|
||||
@ -30,6 +30,14 @@ import { PdfViewer } from '../../pdf-viewer/services/pdf-viewer.service';
|
||||
import { REDAnnotationManager } from '../../pdf-viewer/services/annotation-manager.service';
|
||||
import { SkippedService } from './skipped.service';
|
||||
import { REDDocumentViewer } from '../../pdf-viewer/services/document-viewer.service';
|
||||
import {
|
||||
RemoveRedactionDialogComponent,
|
||||
RemoveRedactionPermissions,
|
||||
RemoveRedactionResult,
|
||||
} from '../dialogs/remove-redaction-dialog/remove-redaction-dialog.component';
|
||||
import { RemoveRedactionOptions } from '../dialogs/remove-redaction-dialog/remove-redaction-options';
|
||||
import { IqserDialog } from '../../../../../../../libs/common-ui/src/lib/dialog/iqser-dialog.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
|
||||
@Injectable()
|
||||
export class AnnotationActionsService {
|
||||
@ -37,6 +45,7 @@ export class AnnotationActionsService {
|
||||
private readonly _manualRedactionService: ManualRedactionService,
|
||||
private readonly _dialogService: FilePreviewDialogService,
|
||||
private readonly _dialog: MatDialog,
|
||||
private readonly _iqserDialog: IqserDialog,
|
||||
private readonly _pdf: PdfViewer,
|
||||
private readonly _documentViewer: REDDocumentViewer,
|
||||
private readonly _annotationManager: REDAnnotationManager,
|
||||
@ -44,6 +53,7 @@ export class AnnotationActionsService {
|
||||
private readonly _state: FilePreviewStateService,
|
||||
private readonly _fileDataService: FileDataService,
|
||||
private readonly _skippedService: SkippedService,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
) {}
|
||||
|
||||
acceptSuggestion(annotations: AnnotationWrapper[]) {
|
||||
@ -129,6 +139,41 @@ export class AnnotationActionsService {
|
||||
});
|
||||
}
|
||||
|
||||
async removeOrSuggestRemoveRedaction(redaction: AnnotationWrapper, permissions) {
|
||||
const removePermissions: RemoveRedactionPermissions = {
|
||||
canRemoveOrSuggestToRemoveOnlyHere: permissions.canRemoveOrSuggestToRemoveOnlyHere,
|
||||
canRemoveOrSuggestToRemoveFromDictionary: permissions.canRemoveOrSuggestToRemoveFromDictionary,
|
||||
canMarkAsFalsePositive: permissions.canMarkAsFalsePositive,
|
||||
};
|
||||
|
||||
const result: RemoveRedactionResult = await this._iqserDialog
|
||||
.openDefault(RemoveRedactionDialogComponent, {
|
||||
data: {
|
||||
redaction,
|
||||
dossier: this._state.dossier(),
|
||||
falsePositiveContext: this._getFalsePositiveText(redaction),
|
||||
permissions: removePermissions,
|
||||
},
|
||||
})
|
||||
.result();
|
||||
|
||||
if (result) {
|
||||
if (result.option.value === RemoveRedactionOptions.FALSE_POSITIVE) {
|
||||
this.#setAsFalsePositive(redaction, result.comment);
|
||||
} else {
|
||||
const removeFromDictionary = result.option.value === RemoveRedactionOptions.IN_DOSSIER;
|
||||
this.#removeRedaction(redaction, result.comment, removeFromDictionary);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.option.extraOption) {
|
||||
const dossierTemplate = this._dossierTemplatesService.find(this._state.dossierTemplateId);
|
||||
const { ...body } = dossierTemplate;
|
||||
body.applyDictionaryUpdatesToAllDossiersByDefault = result.applyToAllDossiers;
|
||||
await this._dossierTemplatesService.createOrUpdate(body);
|
||||
}
|
||||
}
|
||||
|
||||
recategorizeImages(annotations: AnnotationWrapper[]) {
|
||||
const data = { annotations, dossier: this._state.dossier() };
|
||||
const { dossierId, fileId } = this._state;
|
||||
@ -375,4 +420,34 @@ export class AnnotationActionsService {
|
||||
}
|
||||
return words;
|
||||
}
|
||||
|
||||
#setAsFalsePositive(redaction: AnnotationWrapper, comment: string) {
|
||||
const request = {
|
||||
sourceId: redaction.id,
|
||||
value: this._getFalsePositiveText(redaction),
|
||||
type: redaction.type,
|
||||
positions: redaction.positions,
|
||||
addToDictionary: true,
|
||||
reason: 'False Positive',
|
||||
dictionaryEntryType: redaction.isRecommendation
|
||||
? DictionaryEntryTypes.FALSE_RECOMMENDATION
|
||||
: DictionaryEntryTypes.FALSE_POSITIVE,
|
||||
comment: comment ? { text: comment } : null,
|
||||
};
|
||||
const { dossierId, fileId } = this._state;
|
||||
|
||||
this.#processObsAndEmit(this._manualRedactionService.addAnnotation([request], dossierId, fileId)).then();
|
||||
}
|
||||
|
||||
#removeRedaction(redaction: AnnotationWrapper, comment: string, removeFromDictionary: boolean) {
|
||||
const body = {
|
||||
annotationId: redaction.id,
|
||||
comment: comment,
|
||||
removeFromDictionary,
|
||||
};
|
||||
const { dossierId, fileId } = this._state;
|
||||
this.#processObsAndEmit(
|
||||
this._manualRedactionService.removeOrSuggestRemove([body], dossierId, fileId, removeFromDictionary, redaction.isHint),
|
||||
).then();
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,14 +62,14 @@ export class PdfAnnotationActionsService {
|
||||
availableActions.push(recategorizeButton);
|
||||
}
|
||||
|
||||
if (permissions.canRemoveOrSuggestToRemoveFromDictionary) {
|
||||
const removeFromDictButton = this.#getButton(
|
||||
'remove-from-dict',
|
||||
_('annotation-actions.remove-annotation.remove-from-dict'),
|
||||
() => this.#annotationActionsService.removeOrSuggestRemoveAnnotation(annotations, true),
|
||||
);
|
||||
availableActions.push(removeFromDictButton);
|
||||
}
|
||||
// if (permissions.canRemoveOrSuggestToRemoveFromDictionary) {
|
||||
// const removeFromDictButton = this.#getButton(
|
||||
// 'remove-from-dict',
|
||||
// _('annotation-actions.remove-annotation.remove-from-dict'),
|
||||
// () => this.#annotationActionsService.removeOrSuggestRemoveAnnotation(annotations, true),
|
||||
// );
|
||||
// availableActions.push(removeFromDictButton);
|
||||
// }
|
||||
|
||||
if (permissions.canAcceptRecommendation) {
|
||||
const acceptRecommendationButton = this.#getButton('check', _('annotation-actions.accept-recommendation.label'), () =>
|
||||
@ -92,12 +92,12 @@ export class PdfAnnotationActionsService {
|
||||
availableActions.push(undoButton);
|
||||
}
|
||||
|
||||
if (permissions.canMarkAsFalsePositive) {
|
||||
const markAsFalsePositiveButton = this.#getButton('thumb-down', _('annotation-actions.remove-annotation.false-positive'), () =>
|
||||
this.#annotationActionsService.markAsFalsePositive(annotations),
|
||||
);
|
||||
availableActions.push(markAsFalsePositiveButton);
|
||||
}
|
||||
// if (permissions.canMarkAsFalsePositive) {
|
||||
// const markAsFalsePositiveButton = this.#getButton('thumb-down', _('annotation-actions.remove-annotation.false-positive'), () =>
|
||||
// this.#annotationActionsService.markAsFalsePositive(annotations),
|
||||
// );
|
||||
// availableActions.push(markAsFalsePositiveButton);
|
||||
// }
|
||||
|
||||
if (permissions.canForceRedaction) {
|
||||
const forceRedactionButton = this.#getButton('thumb-up', _('annotation-actions.force-redaction.label'), () =>
|
||||
@ -120,13 +120,20 @@ export class PdfAnnotationActionsService {
|
||||
availableActions.push(rejectSuggestionButton);
|
||||
}
|
||||
|
||||
if (permissions.canRemoveOrSuggestToRemoveOnlyHere) {
|
||||
const removeOrSuggestToRemoveOnlyHereButton = this.#getButton(
|
||||
'trash',
|
||||
_('annotation-actions.remove-annotation.only-here'),
|
||||
() => this.#annotationActionsService.removeOrSuggestRemoveAnnotation(annotations, false),
|
||||
// if (permissions.canRemoveOrSuggestToRemoveOnlyHere) {
|
||||
// const removeOrSuggestToRemoveOnlyHereButton = this.#getButton(
|
||||
// 'trash',
|
||||
// _('annotation-actions.remove-annotation.only-here'),
|
||||
// () => this.#annotationActionsService.removeOrSuggestRemoveAnnotation(annotations, false),
|
||||
// );
|
||||
// availableActions.push(removeOrSuggestToRemoveOnlyHereButton);
|
||||
// }
|
||||
|
||||
if (permissions.canRemoveOrSuggestToRemoveRedaction) {
|
||||
const removeOrSuggestToRemoveButton = this.#getButton('trash', _('annotation-actions.remove-annotation.remove-redaction'), () =>
|
||||
this.#annotationActionsService.removeOrSuggestRemoveRedaction(annotations[0], permissions),
|
||||
);
|
||||
availableActions.push(removeOrSuggestToRemoveOnlyHereButton);
|
||||
availableActions.push(removeOrSuggestToRemoveButton);
|
||||
}
|
||||
|
||||
return availableActions;
|
||||
@ -164,6 +171,7 @@ export class PdfAnnotationActionsService {
|
||||
canForceHint: permissions.reduce((acc, next) => acc && next.canForceHint, true),
|
||||
canRejectSuggestion: permissions.reduce((acc, next) => acc && next.canRejectSuggestion, true),
|
||||
canRemoveOrSuggestToRemoveOnlyHere: permissions.reduce((acc, next) => acc && next.canRemoveOrSuggestToRemoveOnlyHere, true),
|
||||
canRemoveOrSuggestToRemoveRedaction: permissions.reduce((acc, next) => acc && next.canRemoveOrSuggestToRemoveRedaction, true),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,6 +42,7 @@ import Quad = Core.Math.Quad;
|
||||
export class PdfProxyService {
|
||||
readonly annotationSelected$ = this.#annotationSelected$;
|
||||
readonly manualAnnotationRequested$ = new Subject<ManualRedactionEntryWrapper>();
|
||||
readonly redactTextRequested$ = new Subject<ManualRedactionEntryWrapper>();
|
||||
readonly pageChanged$ = this._pdf.pageChanged$.pipe(
|
||||
tap(() => this._handleCustomActions()),
|
||||
tap(() => this._pdf.resetAnnotationActions()),
|
||||
@ -60,6 +61,7 @@ export class PdfProxyService {
|
||||
? this._convertPath('/assets/icons/general/pdftron-action-add-component.svg')
|
||||
: this._convertPath('/assets/icons/general/pdftron-action-add-redaction.svg');
|
||||
readonly #addDictIcon = this._convertPath('/assets/icons/general/pdftron-action-add-dict.svg');
|
||||
readonly #addHintIcon = this._convertPath('/assets/icons/general/pdftron-action-add-hint.svg');
|
||||
|
||||
constructor(
|
||||
private readonly _translateService: TranslateService,
|
||||
@ -159,21 +161,34 @@ export class PdfProxyService {
|
||||
if (this._iqserPermissionsService.has(Roles.redactions.write) || this._iqserPermissionsService.has(Roles.redactions.request)) {
|
||||
popups.push({
|
||||
type: 'actionButton',
|
||||
dataElement: TextPopups.ADD_REDACTION,
|
||||
dataElement: TextPopups.REDACT_TEXT,
|
||||
img: this.#addRedactionIcon,
|
||||
title: this.#getTitle(ManualRedactionEntryTypes.REDACTION),
|
||||
onClick: () => this._ngZone.run(() => this._addManualRedactionOfType(ManualRedactionEntryTypes.REDACTION)),
|
||||
onClick: () => this._ngZone.run(() => this._redactText(ManualRedactionEntryTypes.REDACT)),
|
||||
});
|
||||
popups.push({
|
||||
type: 'actionButton',
|
||||
dataElement: TextPopups.ADD_HINT,
|
||||
img: this.#addHintIcon,
|
||||
onClick: () => this._ngZone.run(() => this._redactText(ManualRedactionEntryTypes.HINT)),
|
||||
});
|
||||
|
||||
if (!this._iqserPermissionsService.has(Roles.getRss)) {
|
||||
popups.push({
|
||||
type: 'actionButton',
|
||||
dataElement: TextPopups.ADD_DICTIONARY,
|
||||
img: this.#addDictIcon,
|
||||
title: this.#getTitle(ManualRedactionEntryTypes.DICTIONARY),
|
||||
onClick: () => this._ngZone.run(() => this._addManualRedactionOfType(ManualRedactionEntryTypes.DICTIONARY)),
|
||||
});
|
||||
}
|
||||
// popups.push({
|
||||
// type: 'actionButton',
|
||||
// dataElement: TextPopups.ADD_REDACTION,
|
||||
// img: this.#addRedactionIcon,
|
||||
// title: this.#getTitle(ManualRedactionEntryTypes.REDACTION),
|
||||
// onClick: () => this._ngZone.run(() => this._addManualRedactionOfType(ManualRedactionEntryTypes.REDACTION)),
|
||||
// });
|
||||
//
|
||||
// if (!this._iqserPermissionsService.has(Roles.getRss)) {
|
||||
// popups.push({
|
||||
// type: 'actionButton',
|
||||
// dataElement: TextPopups.ADD_DICTIONARY,
|
||||
// img: this.#addDictIcon,
|
||||
// title: this.#getTitle(ManualRedactionEntryTypes.DICTIONARY),
|
||||
// onClick: () => this._ngZone.run(() => this._addManualRedactionOfType(ManualRedactionEntryTypes.DICTIONARY)),
|
||||
// });
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,6 +204,13 @@ export class PdfProxyService {
|
||||
this.manualAnnotationRequested$.next({ manualRedactionEntry, type });
|
||||
}
|
||||
|
||||
private _redactText(type: ManualRedactionEntryType) {
|
||||
const selectedQuads: Record<string, Quad[]> = this._pdf.documentViewer.getSelectedTextQuads();
|
||||
const text = this._documentViewer.selectedText();
|
||||
const manualRedactionEntry = this._getManualRedaction(selectedQuads, text, true);
|
||||
this.redactTextRequested$.next({ manualRedactionEntry, type });
|
||||
}
|
||||
|
||||
private _handleCustomActions() {
|
||||
const isCurrentPageExcluded = this._state.file().isPageExcluded(this._pdf.currentPage());
|
||||
|
||||
|
||||
@ -29,6 +29,8 @@ export const HeaderElements = {
|
||||
export type HeaderElementType = ValuesOf<typeof HeaderElements>;
|
||||
|
||||
export const TextPopups = {
|
||||
REDACT_TEXT: 'redact-text',
|
||||
ADD_HINT: 'add-hint',
|
||||
ADD_REDACTION: 'add-redaction',
|
||||
ADD_DICTIONARY: 'add-dictionary',
|
||||
ADD_RECTANGLE: 'add-rectangle',
|
||||
|
||||
@ -63,6 +63,7 @@ export class IconsModule {
|
||||
'padding-top-bottom',
|
||||
'page',
|
||||
'preview',
|
||||
'push-pin',
|
||||
'put-back',
|
||||
'read-only',
|
||||
'ready-for-approval',
|
||||
|
||||
@ -145,6 +145,24 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
||||
return !!this._dictionariesMapService.get(dossierTemplateId).find(e => e.type === SuperTypes.ManualRedaction && !e.virtual);
|
||||
}
|
||||
|
||||
getPossibleDictionaries(dossierTemplateId: string, hintTypes: boolean): Dictionary[] {
|
||||
const possibleDictionaries: Dictionary[] = [];
|
||||
|
||||
this._dictionariesMapService.get(dossierTemplateId).forEach((d: Dictionary) => {
|
||||
if (!hintTypes) {
|
||||
if (!d.hint) {
|
||||
possibleDictionaries.push(d);
|
||||
}
|
||||
} else if (d.hint) {
|
||||
possibleDictionaries.push(d);
|
||||
}
|
||||
});
|
||||
|
||||
possibleDictionaries.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
return possibleDictionaries;
|
||||
}
|
||||
|
||||
getRedactionTypes(dossierTemplateId: string): Dictionary[] {
|
||||
const possibleDictionaries: Dictionary[] = [];
|
||||
|
||||
|
||||
32
apps/red-ui/src/app/translations/redact-text-translations.ts
Normal file
32
apps/red-ui/src/app/translations/redact-text-translations.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
|
||||
interface Option {
|
||||
label: string;
|
||||
description: string;
|
||||
extraOptionLabel?: string;
|
||||
}
|
||||
|
||||
export const redactTextTranslations: Record<'REDACT' | 'HINT', Record<'onlyHere' | 'inDossier', Option>> = {
|
||||
REDACT: {
|
||||
onlyHere: {
|
||||
label: _('redact-text.dialog.content.options.redact.only-here.label'),
|
||||
description: _('redact-text.dialog.content.options.redact.only-here.description'),
|
||||
},
|
||||
inDossier: {
|
||||
label: _('redact-text.dialog.content.options.redact.in-dossier.label'),
|
||||
description: _('redact-text.dialog.content.options.redact.in-dossier.description'),
|
||||
extraOptionLabel: _('redact-text.dialog.content.options.redact.in-dossier.extraOptionLabel'),
|
||||
},
|
||||
},
|
||||
HINT: {
|
||||
onlyHere: {
|
||||
label: _('redact-text.dialog.content.options.hint.only-here.label'),
|
||||
description: _('redact-text.dialog.content.options.hint.only-here.description'),
|
||||
},
|
||||
inDossier: {
|
||||
label: _('redact-text.dialog.content.options.hint.in-dossier.label'),
|
||||
description: _('redact-text.dialog.content.options.hint.in-dossier.description'),
|
||||
extraOptionLabel: _('redact-text.dialog.content.options.hint.in-dossier.extraOptionLabel'),
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
@ -0,0 +1,25 @@
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { RemoveRedactionOption } from '../modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-options';
|
||||
|
||||
interface Option {
|
||||
label: string;
|
||||
description: string;
|
||||
extraOptionLabel?: string;
|
||||
}
|
||||
|
||||
export const removeRedactionTranslations: { [key in RemoveRedactionOption]: Option } = {
|
||||
ONLY_HERE: {
|
||||
label: _('remove-redaction.dialog.content.options.only-here.label'),
|
||||
description: _('remove-redaction.dialog.content.options.only-here.description'),
|
||||
},
|
||||
IN_DOSSIER: {
|
||||
label: _('remove-redaction.dialog.content.options.in-dossier.label'),
|
||||
description: _('remove-redaction.dialog.content.options.in-dossier.description'),
|
||||
extraOptionLabel: _('remove-redaction.dialog.content.options.redact.in-dossier.extraOptionLabel'),
|
||||
},
|
||||
FALSE_POSITIVE: {
|
||||
label: _('remove-redaction.dialog.content.options.false-positive.label'),
|
||||
description: _('remove-redaction.dialog.content.options.false-positive.description'),
|
||||
extraOptionLabel: _('remove-redaction.dialog.content.options.redact.false-positive.extraOptionLabel'),
|
||||
},
|
||||
};
|
||||
@ -43,6 +43,10 @@
|
||||
"generic": "Fehler beim Erstellen der Dossiervorlage."
|
||||
},
|
||||
"form": {
|
||||
"apply-updates-default": {
|
||||
"description": "",
|
||||
"heading": ""
|
||||
},
|
||||
"description": "Beschreibung",
|
||||
"description-placeholder": "Beschreibung eingeben",
|
||||
"hidden-text": {
|
||||
@ -61,11 +65,7 @@
|
||||
"title": ""
|
||||
},
|
||||
"valid-from": "Gültig ab",
|
||||
"valid-to": "Gültig bis",
|
||||
"apply-updates-default": {
|
||||
"heading": "",
|
||||
"description": ""
|
||||
}
|
||||
"valid-to": "Gültig bis"
|
||||
},
|
||||
"save": "Dossier-Vorlage speichern",
|
||||
"title": "{type, select, edit{Dossier-Vorlage {name} bearbeiten} create{Dossier-Vorlage erstellen} clone{} other{}}"
|
||||
@ -330,9 +330,7 @@
|
||||
"recategorize-image": "neu kategorisieren",
|
||||
"reject-suggestion": "Vorschlag ablehnen",
|
||||
"remove-annotation": {
|
||||
"false-positive": "Falsch positiv",
|
||||
"only-here": "nur hier entfernen",
|
||||
"remove-from-dict": "Aus dem Wörterbuch entfernen"
|
||||
"remove-redaction": ""
|
||||
},
|
||||
"remove-highlights": {
|
||||
"label": ""
|
||||
@ -1904,6 +1902,49 @@
|
||||
},
|
||||
"header": "Bildtypen bearbeiten"
|
||||
},
|
||||
"redact-text": {
|
||||
"dialog": {
|
||||
"actions": {
|
||||
"cancel": "",
|
||||
"save": ""
|
||||
},
|
||||
"content": {
|
||||
"comment": "",
|
||||
"comment-placeholder": "",
|
||||
"legal-basis": "",
|
||||
"options": {
|
||||
"hint": {
|
||||
"in-dossier": {
|
||||
"description": "",
|
||||
"extraOptionLabel": "",
|
||||
"label": ""
|
||||
},
|
||||
"only-here": {
|
||||
"description": "",
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"redact": {
|
||||
"in-dossier": {
|
||||
"description": "",
|
||||
"extraOptionLabel": "",
|
||||
"label": ""
|
||||
},
|
||||
"only-here": {
|
||||
"description": "",
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"reason": "",
|
||||
"reason-placeholder": "",
|
||||
"selected-text": "",
|
||||
"type": "",
|
||||
"type-placeholder": ""
|
||||
},
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"redaction-abbreviation": "R",
|
||||
"references": "",
|
||||
"remove-annotations-dialog": {
|
||||
@ -1921,6 +1962,41 @@
|
||||
},
|
||||
"value": "Wert"
|
||||
},
|
||||
"remove-redaction": {
|
||||
"dialog": {
|
||||
"actions": {
|
||||
"cancel": "",
|
||||
"save": ""
|
||||
},
|
||||
"content": {
|
||||
"comment": "",
|
||||
"comment-placeholder": "",
|
||||
"options": {
|
||||
"false-positive": {
|
||||
"description": "",
|
||||
"label": ""
|
||||
},
|
||||
"in-dossier": {
|
||||
"description": "",
|
||||
"label": ""
|
||||
},
|
||||
"only-here": {
|
||||
"description": "",
|
||||
"label": ""
|
||||
},
|
||||
"redact": {
|
||||
"false-positive": {
|
||||
"extraOptionLabel": ""
|
||||
},
|
||||
"in-dossier": {
|
||||
"extraOptionLabel": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"report-type": {
|
||||
"label": "{length} {length, plural, one{Berichtstyp} other{Berichtstypen}}"
|
||||
},
|
||||
|
||||
@ -43,6 +43,10 @@
|
||||
"generic": "Failed to create dossier template."
|
||||
},
|
||||
"form": {
|
||||
"apply-updates-default": {
|
||||
"description": "Apply dictionary updates to all dossiers by default",
|
||||
"heading": "Entity configuration"
|
||||
},
|
||||
"description": "Description",
|
||||
"description-placeholder": "Enter Description",
|
||||
"hidden-text": {
|
||||
@ -61,11 +65,7 @@
|
||||
"title": "Keep overlapping elements"
|
||||
},
|
||||
"valid-from": "Valid from",
|
||||
"valid-to": "Valid to",
|
||||
"apply-updates-default": {
|
||||
"heading": "Entity configuration",
|
||||
"description": "Apply dictionary updates to all dossiers by default"
|
||||
}
|
||||
"valid-to": "Valid to"
|
||||
},
|
||||
"save": "Save Dossier Template",
|
||||
"title": "{type, select, edit{Edit {name}} create{Create} clone{Clone} other{}} Dossier Template"
|
||||
@ -330,9 +330,7 @@
|
||||
"recategorize-image": "Recategorize",
|
||||
"reject-suggestion": "Reject Suggestion",
|
||||
"remove-annotation": {
|
||||
"false-positive": "False Positive",
|
||||
"only-here": "Remove only here",
|
||||
"remove-from-dict": "Remove from dictionary"
|
||||
"remove-redaction": "Remove"
|
||||
},
|
||||
"remove-highlights": {
|
||||
"label": "Remove Selected Earmarks"
|
||||
@ -1904,6 +1902,49 @@
|
||||
},
|
||||
"header": "Edit Image Type"
|
||||
},
|
||||
"redact-text": {
|
||||
"dialog": {
|
||||
"actions": {
|
||||
"cancel": "Cancel",
|
||||
"save": "Save"
|
||||
},
|
||||
"content": {
|
||||
"comment": "Comment",
|
||||
"comment-placeholder": "Add remarks or mentions ...",
|
||||
"legal-basis": "Legal Basis",
|
||||
"options": {
|
||||
"hint": {
|
||||
"in-dossier": {
|
||||
"description": "Add hint in every document in {dossierName}.",
|
||||
"extraOptionLabel": "Apply to all dossiers",
|
||||
"label": "Add hint in dossier"
|
||||
},
|
||||
"only-here": {
|
||||
"description": "Add hint only at this position in this document.",
|
||||
"label": "Add hint only here"
|
||||
}
|
||||
},
|
||||
"redact": {
|
||||
"in-dossier": {
|
||||
"description": "Add redaction in every document in {dossierName}.",
|
||||
"extraOptionLabel": "Apply to all dossiers",
|
||||
"label": "Redact in dossier"
|
||||
},
|
||||
"only-here": {
|
||||
"description": "Add redaction only at this position in this document.",
|
||||
"label": "Redact only here"
|
||||
}
|
||||
}
|
||||
},
|
||||
"reason": "Reason",
|
||||
"reason-placeholder": "Select a reason ...",
|
||||
"selected-text": "Selected text:",
|
||||
"type": "Type",
|
||||
"type-placeholder": "Select type ..."
|
||||
},
|
||||
"title": "Redact text"
|
||||
}
|
||||
},
|
||||
"redaction-abbreviation": "R",
|
||||
"references": "{count} {count, plural, one{reference} other{references}}",
|
||||
"remove-annotations-dialog": {
|
||||
@ -1921,6 +1962,41 @@
|
||||
},
|
||||
"value": "Value"
|
||||
},
|
||||
"remove-redaction": {
|
||||
"dialog": {
|
||||
"actions": {
|
||||
"cancel": "Cancel",
|
||||
"save": "Save"
|
||||
},
|
||||
"content": {
|
||||
"comment": "Comment",
|
||||
"comment-placeholder": "Add remarks or mentions ...",
|
||||
"options": {
|
||||
"false-positive": {
|
||||
"description": "\"{value}\" is not a {type} in this context: \"{context}\".",
|
||||
"label": "False positive"
|
||||
},
|
||||
"in-dossier": {
|
||||
"description": "Do not redact \"{value}\" in any document of the current dossier.",
|
||||
"label": "Remove in dossier"
|
||||
},
|
||||
"only-here": {
|
||||
"description": "Do not redact \"{value}\" at this position in the current document.",
|
||||
"label": "Remove here"
|
||||
},
|
||||
"redact": {
|
||||
"false-positive": {
|
||||
"extraOptionLabel": "Apply to all dossiers"
|
||||
},
|
||||
"in-dossier": {
|
||||
"extraOptionLabel": "Apply to all dossiers"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Remove redaction"
|
||||
}
|
||||
},
|
||||
"report-type": {
|
||||
"label": "{length} report {length, plural, one{type} other{types}}"
|
||||
},
|
||||
|
||||
@ -43,6 +43,10 @@
|
||||
"generic": "Fehler beim Erstellen der Dossiervorlage."
|
||||
},
|
||||
"form": {
|
||||
"apply-updates-default": {
|
||||
"description": "",
|
||||
"heading": ""
|
||||
},
|
||||
"description": "Beschreibung",
|
||||
"description-placeholder": "Beschreibung eingeben",
|
||||
"hidden-text": {
|
||||
@ -326,9 +330,7 @@
|
||||
"recategorize-image": "neu kategorisieren",
|
||||
"reject-suggestion": "Vorschlag ablehnen",
|
||||
"remove-annotation": {
|
||||
"false-positive": "Falsch positiv",
|
||||
"only-here": "nur hier entfernen",
|
||||
"remove-from-dict": "Aus dem Wörterbuch entfernen"
|
||||
"remove-redaction": ""
|
||||
},
|
||||
"remove-highlights": {
|
||||
"label": ""
|
||||
@ -1900,6 +1902,49 @@
|
||||
},
|
||||
"header": "Bildtypen bearbeiten"
|
||||
},
|
||||
"redact-text": {
|
||||
"dialog": {
|
||||
"actions": {
|
||||
"cancel": "",
|
||||
"save": ""
|
||||
},
|
||||
"content": {
|
||||
"comment": "",
|
||||
"comment-placeholder": "",
|
||||
"legal-basis": "",
|
||||
"options": {
|
||||
"hint": {
|
||||
"in-dossier": {
|
||||
"description": "",
|
||||
"extraOptionLabel": "",
|
||||
"label": ""
|
||||
},
|
||||
"only-here": {
|
||||
"description": "",
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"redact": {
|
||||
"in-dossier": {
|
||||
"description": "",
|
||||
"extraOptionLabel": "",
|
||||
"label": ""
|
||||
},
|
||||
"only-here": {
|
||||
"description": "",
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"reason": "",
|
||||
"reason-placeholder": "",
|
||||
"selected-text": "",
|
||||
"type": "",
|
||||
"type-placeholder": ""
|
||||
},
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"redaction-abbreviation": "C",
|
||||
"references": "",
|
||||
"remove-annotations-dialog": {
|
||||
@ -1917,6 +1962,41 @@
|
||||
},
|
||||
"value": "Wert"
|
||||
},
|
||||
"remove-redaction": {
|
||||
"dialog": {
|
||||
"actions": {
|
||||
"cancel": "",
|
||||
"save": ""
|
||||
},
|
||||
"content": {
|
||||
"comment": "",
|
||||
"comment-placeholder": "",
|
||||
"options": {
|
||||
"false-positive": {
|
||||
"description": "",
|
||||
"label": ""
|
||||
},
|
||||
"in-dossier": {
|
||||
"description": "",
|
||||
"label": ""
|
||||
},
|
||||
"only-here": {
|
||||
"description": "",
|
||||
"label": ""
|
||||
},
|
||||
"redact": {
|
||||
"false-positive": {
|
||||
"extraOptionLabel": ""
|
||||
},
|
||||
"in-dossier": {
|
||||
"extraOptionLabel": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"report-type": {
|
||||
"label": "{length} {length, plural, one{Berichtstyp} other{Berichtstypen}}"
|
||||
},
|
||||
|
||||
@ -43,6 +43,10 @@
|
||||
"generic": "Failed to create dossier template."
|
||||
},
|
||||
"form": {
|
||||
"apply-updates-default": {
|
||||
"description": "",
|
||||
"heading": ""
|
||||
},
|
||||
"description": "Description",
|
||||
"description-placeholder": "Enter Description",
|
||||
"hidden-text": {
|
||||
@ -326,9 +330,7 @@
|
||||
"recategorize-image": "Recategorize",
|
||||
"reject-suggestion": "Reject Suggestion",
|
||||
"remove-annotation": {
|
||||
"false-positive": "False Positive",
|
||||
"only-here": "Remove only here",
|
||||
"remove-from-dict": "Remove from dictionary"
|
||||
"remove-redaction": "Remove"
|
||||
},
|
||||
"remove-highlights": {
|
||||
"label": "Remove Selected Earmarks"
|
||||
@ -1900,6 +1902,49 @@
|
||||
},
|
||||
"header": "Edit Image Type"
|
||||
},
|
||||
"redact-text": {
|
||||
"dialog": {
|
||||
"actions": {
|
||||
"cancel": "Cancel",
|
||||
"save": "Save"
|
||||
},
|
||||
"content": {
|
||||
"comment": "Comment",
|
||||
"comment-placeholder": "Add remarks or mentions ...",
|
||||
"legal-basis": "Legal Basis",
|
||||
"options": {
|
||||
"hint": {
|
||||
"in-dossier": {
|
||||
"description": "Add hint in every document in {dossierName}.",
|
||||
"extraOptionLabel": "Apply to all dossiers",
|
||||
"label": "Add hint in dossier"
|
||||
},
|
||||
"only-here": {
|
||||
"description": "Add hint only at this position in this document.",
|
||||
"label": "Add hint only here"
|
||||
}
|
||||
},
|
||||
"redact": {
|
||||
"in-dossier": {
|
||||
"description": "Add redaction in every document in {dossierName}.",
|
||||
"extraOptionLabel": "Apply to all dossiers",
|
||||
"label": "Redact in dossier"
|
||||
},
|
||||
"only-here": {
|
||||
"description": "Add redaction only at this position in this document.",
|
||||
"label": "Redact only here"
|
||||
}
|
||||
}
|
||||
},
|
||||
"reason": "Reason",
|
||||
"reason-placeholder": "Select a reason ...",
|
||||
"selected-text": "Selected text:",
|
||||
"type": "Type",
|
||||
"type-placeholder": "Select type ..."
|
||||
},
|
||||
"title": "Redact text"
|
||||
}
|
||||
},
|
||||
"redaction-abbreviation": "C",
|
||||
"references": "{count} {count, plural, one{reference} other{references}}",
|
||||
"remove-annotations-dialog": {
|
||||
@ -1917,6 +1962,41 @@
|
||||
},
|
||||
"value": "Value"
|
||||
},
|
||||
"remove-redaction": {
|
||||
"dialog": {
|
||||
"actions": {
|
||||
"cancel": "Cancel",
|
||||
"save": "Save"
|
||||
},
|
||||
"content": {
|
||||
"comment": "Comment",
|
||||
"comment-placeholder": "Add remarks or mentions ...",
|
||||
"options": {
|
||||
"false-positive": {
|
||||
"description": "\"{value}\" is not a {type} in this context: \"{context}\".",
|
||||
"label": "False positive"
|
||||
},
|
||||
"in-dossier": {
|
||||
"description": "Do not redact \"{value}\" in any document of the current dossier.",
|
||||
"label": "Remove in dossier"
|
||||
},
|
||||
"only-here": {
|
||||
"description": "Do not redact \"{value}\" at this position in the current document.",
|
||||
"label": "Remove here"
|
||||
},
|
||||
"redact": {
|
||||
"false-positive": {
|
||||
"extraOptionLabel": "Apply to all dossiers"
|
||||
},
|
||||
"in-dossier": {
|
||||
"extraOptionLabel": "Apply to all dossiers"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Remove redaction"
|
||||
}
|
||||
},
|
||||
"report-type": {
|
||||
"label": "{length} report {length, plural, one{type} other{types}}"
|
||||
},
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" version="1.1" viewBox="0 0 16 16" width="16px"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd" id="Redacted" stroke="none" stroke-width="1">
|
||||
<circle fill="#94989f" cx="8" cy="8" r="8" />
|
||||
<text fill="#FFFFFF" font-family="Arial, Helvetica, sans-serif" font-size="11" font-weight="500"
|
||||
id="H">
|
||||
<tspan x="4" y="12">H</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 486 B |
@ -3,7 +3,7 @@
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd" id="Redacted" stroke="none" stroke-width="1">
|
||||
<rect fill="#283241" height="16" id="Rectangle" width="16" x="0" y="0"></rect>
|
||||
<text fill="#FFFFFF" font-family="Inter-SemiBold, Inter" font-size="11" font-weight="500"
|
||||
<text fill="#FFFFFF" font-family="Arial, Helvetica, sans-serif" font-size="11" font-weight="500"
|
||||
id="H">
|
||||
<tspan x="4.421875" y="12">R</tspan>
|
||||
</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 519 B After Width: | Height: | Size: 526 B |
3
apps/red-ui/src/assets/icons/general/push-pin.svg
Normal file
3
apps/red-ui/src/assets/icons/general/push-pin.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M75.5002 10L79.4998 55H85V65H55V100H45V65H15V55H20.5002L24.4998 10H20V0H80V10H75.5002ZM34.4995 10L30.4998 55H69.4991L65.4995 10H34.4995Z" fill="#283241"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 311 B |
@ -1 +1 @@
|
||||
Subproject commit 2fe65233bc4186ba67c924efae4d82cfb8b89fe7
|
||||
Subproject commit 6b45d4aa70554b5f41e59d689094d6dad8291c8e
|
||||
Loading…
x
Reference in New Issue
Block a user