Pull request #290: RED-2211

Merge in RED/ui from RED-2211 to master

* commit '01233562870907faf875eee573219961e940bc5e':
  add engines property and show relevant messages in popover
  wip redaction sources
  add name pipe
  move annotations list to a separate component
This commit is contained in:
Dan Percic 2021-09-24 19:19:31 +02:00
commit 7429708518
29 changed files with 441 additions and 215 deletions

View File

@ -57,11 +57,10 @@ export class AnnotationWrapper {
isChangeLogEntry?: boolean;
changeLogType?: 'ADDED' | 'REMOVED';
engines?: string[];
private _origin: RedactionLogEntryWrapper;
constructor() {}
get isChangeLogRemoved() {
return this.changeLogType === 'REMOVED';
}
@ -220,6 +219,7 @@ export class AnnotationWrapper {
annotationWrapper.legalBasisValue = redactionLogEntry.legalBasis;
annotationWrapper.comments = redactionLogEntry.comments || [];
annotationWrapper.manual = redactionLogEntry.manual;
annotationWrapper.engines = redactionLogEntry.engines;
this._createContent(annotationWrapper, redactionLogEntry);
this._setSuperType(annotationWrapper, redactionLogEntry);

View File

@ -35,4 +35,5 @@ export interface RedactionLogEntryWrapper {
recategorizationType?: string;
legalBasisChangeValue?: string;
engines?: string[];
}

View File

@ -1,4 +1,4 @@
<div [translateParams]="{ userName: userName }" [translate]="'reset-password-dialog.header'" class="dialog-header heading-l"></div>
<div [translateParams]="{ userName: user | name }" [translate]="'reset-password-dialog.header'" class="dialog-header heading-l"></div>
<form (submit)="save()" [formGroup]="passwordForm">
<div class="dialog-content">

View File

@ -23,10 +23,6 @@ export class ResetPasswordComponent {
private readonly _loadingService: LoadingService
) {}
get userName() {
return this._userService.getNameForId(this.user.id);
}
async save() {
this._loadingService.start();
await this._userControllerService

View File

@ -1,9 +1,10 @@
<div *ngFor="let comment of annotation.comments" class="comment">
<div class="comment-details-wrapper">
<div [matTooltipPosition]="'above'" [matTooltip]="comment.date | date: 'exactDate'" class="small-label">
<b> {{ getOwnerName(comment) }} </b>
<strong> {{ comment.user | name }} </strong>
{{ comment.date | date: 'sophisticatedDate' }}
</div>
<div class="comment-actions">
<iqser-circle-button
(action)="deleteComment(comment)"
@ -15,6 +16,7 @@
></iqser-circle-button>
</div>
</div>
<div>{{ comment.text }}</div>
</div>

View File

@ -52,8 +52,4 @@ export class CommentsComponent {
}
});
}
getOwnerName(comment: Comment): string {
return this._userService.getNameForId(comment.user);
}
}

View File

@ -0,0 +1,23 @@
<ng-container *ngIf="hasEnginesToShow">
<div cdkOverlayOrigin #trigger="cdkOverlayOrigin" class="chip" (mouseover)="isPopoverOpen = true" (mouseout)="isPopoverOpen = false">
<ng-container *ngFor="let engine of engines">
<mat-icon *ngIf="engine.show" [svgIcon]="engine.icon"></mat-icon>
</ng-container>
</div>
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayOffsetY]="-8"
[cdkConnectedOverlayOrigin]="trigger"
[cdkConnectedOverlayOpen]="isPopoverOpen"
>
<div class="popover">
<ng-container *ngFor="let engine of engines">
<div *ngIf="engine.show" class="flex-align-items-center">
<mat-icon [svgIcon]="engine.icon"></mat-icon>
<span>{{ engine.description }}</span>
</div>
</ng-container>
</div>
</ng-template>
</ng-container>

View File

@ -0,0 +1,51 @@
@use 'variables';
.popover {
width: 260px;
padding: 10px;
border-radius: 3px;
background-color: variables.$grey-1;
color: variables.$white;
mat-icon {
color: variables.$white;
flex-shrink: 0;
}
span {
padding-left: 8px;
font-size: 11px;
line-height: 14px;
}
}
.chip {
height: 24px;
&:hover {
background-color: variables.$grey-6;
border-radius: 12px;
mat-icon {
opacity: 1;
}
}
mat-icon {
opacity: 50%;
margin-left: 3px;
margin-right: 3px;
&:first-of-type {
margin-left: 8px;
}
&:last-of-type {
margin-right: 8px;
}
}
}
mat-icon {
width: 10px;
}

View File

@ -0,0 +1,65 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { OnChange } from '@iqser/common-ui';
import { TranslateService } from '@ngx-translate/core';
interface Engine {
readonly icon: string;
readonly description: string;
readonly show: boolean;
}
type Engines = readonly Engine[];
const Engines = {
DICTIONARY: 'DICTIONARY',
NER: 'NER',
RULE: 'RULE'
} as const;
type EngineName = keyof typeof Engines;
@Component({
selector: 'redaction-annotation-source',
templateUrl: './annotation-source.component.html',
styleUrls: ['./annotation-source.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AnnotationSourceComponent {
@Input()
@OnChange<AnnotationWrapper, AnnotationSourceComponent>('updateEngines')
annotation: AnnotationWrapper;
isPopoverOpen = false;
engines: Engines;
constructor(private readonly _translateService: TranslateService) {}
get hasEnginesToShow(): boolean {
return this.engines.length && this.engines.some(source => source.show);
}
updateEngines(): void {
this.engines = [
{
icon: 'red:dictionary',
description: this._translateService.instant('annotation-engines.dictionary'),
show: this._isBasedOn(Engines.DICTIONARY)
},
{
icon: 'red:ai',
description: this._translateService.instant('annotation-engines.ner'),
show: this._isBasedOn(Engines.NER)
},
{
icon: 'red:rule',
description: this._translateService.instant('annotation-engines.rule', { rule: this.annotation.legalBasisValue }),
show: this._isBasedOn(Engines.RULE)
}
];
}
private _isBasedOn(engineName: EngineName) {
return !!this.annotation.engines?.includes(engineName);
}
}

View File

@ -0,0 +1,62 @@
<div
(click)="annotationClicked(annotation, $event)"
*ngFor="let annotation of annotations"
[attr.annotation-id]="annotation.id"
[attr.annotation-page]="activeViewerPage"
[class.active]="isSelected(annotation.annotationId)"
[class.multi-select-active]="multiSelectActive"
class="annotation-wrapper"
>
<div class="active-bar-marker"></div>
<div [class.removed]="annotation.isChangeLogRemoved" class="annotation">
<div [matTooltip]="annotation.content" class="details" matTooltipPosition="above">
<redaction-type-annotation-icon [annotation]="annotation | log"></redaction-type-annotation-icon>
<div class="flex-1">
<div>
<strong>{{ annotation.typeLabel | translate }}</strong>
</div>
<div *ngIf="annotation?.type !== 'manual'">
<strong>
<span>{{ annotation.descriptor | translate }}</span
>: </strong
>{{ annotation.type | humanize: false }}
</div>
<div *ngIf="annotation.shortContent && !annotation.isHint">
<strong><span translate="content"></span>: </strong>{{ annotation.shortContent }}
</div>
</div>
<div class="active-icon-marker-container">
<iqser-round-checkbox
*ngIf="multiSelectActive && isSelected(annotation.annotationId)"
[active]="true"
></iqser-round-checkbox>
</div>
</div>
<div class="actions-wrapper">
<div
(click)="comments.toggleExpandComments($event)"
[matTooltip]="'comments.comments' | translate: { count: annotation.comments?.length }"
class="comments-counter"
matTooltipPosition="above"
>
<mat-icon svgIcon="red:comment"></mat-icon>
{{ annotation.comments.length }}
</div>
<div *ngIf="!multiSelectActive" class="actions">
<ng-container
[ngTemplateOutletContext]="{ annotation: annotation }"
[ngTemplateOutlet]="annotationActionsTemplate"
></ng-container>
</div>
</div>
<redaction-comments #comments [annotation]="annotation"></redaction-comments>
</div>
<redaction-annotation-source [annotation]="annotation"></redaction-annotation-source>
</div>

View File

@ -0,0 +1,96 @@
@use 'variables';
:host {
width: 100%;
position: relative;
}
.annotation-wrapper {
display: flex;
width: 100%;
border-bottom: 1px solid variables.$separator;
.active-bar-marker {
min-width: 4px;
min-height: 100%;
}
.active-icon-marker-container {
min-width: 20px;
}
&.active {
&:not(.lower-height) .active-bar-marker {
background-color: variables.$primary;
}
}
.annotation {
padding: 10px 16px 8px 10px;
font-size: 11px;
line-height: 14px;
cursor: pointer;
display: flex;
flex-direction: column;
width: 100%;
&.removed {
text-decoration: line-through;
color: variables.$grey-7;
}
.details {
display: flex;
position: relative;
}
.actions-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 8px;
min-height: 34px;
padding-left: 18px;
.comments-counter {
cursor: pointer;
display: flex;
align-items: center;
padding: 0 8px;
transition: background-color 0.2s;
line-height: 13px;
height: 24px;
border-radius: 12px;
mat-icon {
width: 10px;
height: 10px;
margin-right: 4px;
}
&:hover {
background-color: variables.$grey-4;
}
}
}
redaction-type-annotation-icon {
margin-top: 6px;
margin-right: 10px;
}
}
&:hover {
background-color: variables.$grey-8;
::ng-deep .annotation-actions {
display: flex;
}
}
}
redaction-annotation-source {
position: absolute;
top: 6px;
right: 8px;
}

View File

@ -0,0 +1,48 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, TemplateRef } from '@angular/core';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { IqserEventTarget } from '@iqser/common-ui';
@Component({
selector: 'redaction-annotations-list',
templateUrl: './annotations-list.component.html',
styleUrls: ['./annotations-list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AnnotationsListComponent {
@Input() annotations: AnnotationWrapper[];
@Input() selectedAnnotations: AnnotationWrapper[];
@Input() annotationActionsTemplate: TemplateRef<unknown>;
@Input() multiSelectActive = false;
@Input() activeViewerPage: number;
@Input() canMultiSelect = true;
@Output() readonly multiSelectActiveChange = new EventEmitter<boolean>();
@Output() readonly pagesPanelActive = new EventEmitter<boolean>();
@Output() readonly selectAnnotations = new EventEmitter<
AnnotationWrapper[] | { annotations: AnnotationWrapper[]; multiSelect: boolean }
>();
@Output() readonly deselectAnnotations = new EventEmitter<AnnotationWrapper[]>();
annotationClicked(annotation: AnnotationWrapper, $event: MouseEvent): void {
if (($event.target as IqserEventTarget).localName === 'input') {
return;
}
this.pagesPanelActive.emit(false);
if (this.isSelected(annotation.annotationId)) {
this.deselectAnnotations.emit([annotation]);
} else {
if (this.canMultiSelect && ($event.ctrlKey || $event.metaKey) && this.selectedAnnotations.length > 0) {
this.multiSelectActive = true;
this.multiSelectActiveChange.emit(true);
}
this.selectAnnotations.emit({
annotations: [annotation],
multiSelect: this.multiSelectActive
});
}
}
isSelected(annotationId: string): boolean {
return !!this.selectedAnnotations?.find(a => a?.annotationId === annotationId);
}
}

View File

@ -1,6 +1,3 @@
<!-- This is a hack to subscribe to an observable using async pipe instead of component class -->
<ng-container *ngIf="displayedAnnotations$ | async"></ng-container>
<div *ngIf="!excludePages" class="right-title heading" translate="file-preview.tabs.annotations.label">
<div>
<div
@ -150,8 +147,7 @@
(click)="actionPerformed.emit('view-exclude-pages')"
class="with-underline"
translate="file-preview.excluded-from-redaction"
>
</a
></a
>.
</ng-container>
</iqser-empty-state>
@ -174,62 +170,17 @@
</div>
</ng-container>
<div
(click)="annotationClicked(annotation, $event)"
*ngFor="let annotation of displayedAnnotations.get(activeViewerPage)"
[attr.annotation-id]="annotation.id"
[attr.annotation-page]="activeViewerPage"
[class.active]="isSelected(annotation)"
[class.multi-select-active]="multiSelectActive"
class="annotation-wrapper"
>
<div class="active-bar-marker"></div>
<div [class.removed]="annotation.isChangeLogRemoved" class="annotation">
<div [matTooltip]="annotation.content" class="details" matTooltipPosition="above">
<redaction-type-annotation-icon [annotation]="annotation"></redaction-type-annotation-icon>
<div class="flex-1">
<div>
<strong>{{ annotation.typeLabel | translate }}</strong>
</div>
<div *ngIf="annotation?.type !== 'manual'">
<strong>
<span>{{ annotation.descriptor | translate }}</span
>: </strong
>{{ annotation.type | humanize: false }}
</div>
<div *ngIf="annotation.shortContent && !annotation.isHint">
<strong><span translate="content"></span>: </strong>{{ annotation.shortContent }}
</div>
</div>
<div class="active-icon-marker-container">
<iqser-round-checkbox
*ngIf="multiSelectActive && isSelected(annotation)"
[active]="true"
></iqser-round-checkbox>
</div>
</div>
<div class="actions-wrapper">
<div
(click)="toggleExpandComments(annotation, $event)"
[matTooltip]="'comments.comments' | translate: { count: annotation.comments?.length }"
class="comments-counter"
matTooltipPosition="above"
>
<mat-icon svgIcon="red:comment"></mat-icon>
{{ annotation.comments.length }}
</div>
<div *ngIf="!multiSelectActive" class="actions">
<ng-container
[ngTemplateOutletContext]="{ annotation: annotation }"
[ngTemplateOutlet]="annotationActionsTemplate"
></ng-container>
</div>
</div>
<redaction-comments [annotation]="annotation"></redaction-comments>
</div>
</div>
<redaction-annotations-list
[canMultiSelect]="!isReadOnly"
[annotations]="(displayedAnnotations$ | async)?.get(activeViewerPage)"
[selectedAnnotations]="selectedAnnotations"
[annotationActionsTemplate]="annotationActionsTemplate"
[(multiSelectActive)]="multiSelectActive"
[activeViewerPage]="activeViewerPage"
(pagesPanelActive)="pagesPanelActive = $event"
(selectAnnotations)="selectAnnotations.emit($event)"
(deselectAnnotations)="deselectAnnotations.emit($event)"
></redaction-annotations-list>
</div>
</ng-container>

View File

@ -157,96 +157,12 @@
align-items: center;
flex-direction: column;
.annotation-wrapper {
display: flex;
width: 100%;
border-bottom: 1px solid variables.$separator;
.active-bar-marker {
min-width: 4px;
min-height: 100%;
}
.active-icon-marker-container {
min-width: 20px;
}
&.active {
&:not(.lower-height) .active-bar-marker {
background-color: variables.$primary;
}
}
.annotation {
padding: 10px 16px 8px 10px;
font-size: 11px;
line-height: 14px;
cursor: pointer;
display: flex;
flex-direction: column;
width: 100%;
&.removed {
text-decoration: line-through;
color: variables.$grey-7;
}
.details {
display: flex;
position: relative;
}
.actions-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 8px;
min-height: 34px;
padding-left: 18px;
.comments-counter {
cursor: pointer;
display: flex;
align-items: center;
padding: 0 8px;
transition: background-color 0.2s;
line-height: 13px;
height: 24px;
border-radius: 12px;
mat-icon {
width: 10px;
height: 10px;
margin-right: 4px;
}
&:hover {
background-color: variables.$grey-4;
}
}
}
redaction-type-annotation-icon {
margin-top: 6px;
margin-right: 10px;
}
}
&:hover {
background-color: variables.$grey-8;
::ng-deep .annotation-actions {
display: flex;
}
}
}
&:hover {
overflow-y: auto;
@include common-mixins.scroll-bar;
}
&.has-scrollbar:hover .annotation-wrapper .annotation {
&.has-scrollbar:hover::ng-deep .annotation-wrapper .annotation {
padding-right: 5px;
}
}

View File

@ -1,23 +1,10 @@
import {
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
HostListener,
Input,
Output,
QueryList,
TemplateRef,
ViewChild,
ViewChildren
} from '@angular/core';
import { ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Input, Output, TemplateRef, ViewChild } from '@angular/core';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { AnnotationProcessingService } from '../../services/annotation-processing.service';
import { MatDialogRef, MatDialogState } from '@angular/material/dialog';
import scrollIntoView from 'scroll-into-view-if-needed';
import { CircleButtonTypes, Debounce, FilterService, IconButtonTypes, IqserEventTarget, NestedFilter } from '@iqser/common-ui';
import { FileDataModel } from '@models/file/file-data.model';
import { CommentsComponent } from '../comments/comments.component';
import { PermissionsService } from '@services/permissions.service';
import { WebViewerInstance } from '@pdftron/webviewer';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
@ -56,7 +43,6 @@ export class FileWorkloadComponent {
@Output() readonly actionPerformed = new EventEmitter<string>();
displayedPages: number[] = [];
pagesPanelActive = true;
@ViewChildren(CommentsComponent) readonly annotationCommentsComponents: QueryList<CommentsComponent>;
@ViewChild('annotationsElement') private readonly _annotationsElement: ElementRef;
@ViewChild('quickNavigation') private readonly _quickNavigationElement: ElementRef;
@ -118,15 +104,6 @@ export class FileWorkloadComponent {
}
}
isSelected(annotation: AnnotationWrapper) {
return this.selectedAnnotations?.find(a => a?.id === annotation.id);
}
toggleExpandComments(annotation: AnnotationWrapper, $event: MouseEvent) {
$event.stopPropagation();
this.annotationCommentsComponents.find(c => c.annotation === annotation).toggleExpandComments();
}
logAnnotation(annotation: AnnotationWrapper) {
console.log(annotation);
}
@ -156,25 +133,6 @@ export class FileWorkloadComponent {
return this.displayedAnnotations;
}
annotationClicked(annotation: AnnotationWrapper, $event: MouseEvent): void {
if (($event.target as IqserEventTarget).localName === 'input') {
return;
}
this.pagesPanelActive = false;
this.logAnnotation(annotation);
if (this.isSelected(annotation)) {
this.deselectAnnotations.emit([annotation]);
} else {
if (($event.ctrlKey || $event.metaKey) && this.selectedAnnotations.length > 0) {
this.multiSelectActive = true;
}
this.selectAnnotations.emit({
annotations: [annotation],
multiSelect: this.multiSelectActive
});
}
}
@HostListener('window:keyup', ['$event'])
handleKeyEvent($event: KeyboardEvent): void {
if (

View File

@ -4,7 +4,7 @@
<mat-label>{{ 'assign-dossier-owner.dialog.single-user' | translate }}</mat-label>
<mat-select formControlName="owner">
<mat-option *ngFor="let userId of ownersSelectOptions" [value]="userId">
{{ userService.getNameForId(userId) }}
{{ userId | name }}
</mat-option>
</mat-select>
</mat-form-field>

View File

@ -14,7 +14,7 @@
<mat-label>{{ 'assign-owner.dialog.label' | translate: { type: data.mode } }}</mat-label>
<mat-select formControlName="singleUser">
<mat-option *ngFor="let userId of singleUsersSelectOptions" [value]="userId">
{{ userService.getNameForId(userId) }}
{{ userId | name }}
</mat-option>
</mat-select>
</mat-form-field>

View File

@ -49,6 +49,9 @@ import { DossiersService } from './services/dossiers.service';
import { DossierDetailsStatsComponent } from './components/dossier-details-stats/dossier-details-stats.component';
import { SearchScreenComponent } from './screens/search-screen/search-screen.component';
import { EditDossierDeletedDocumentsComponent } from './dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component';
import { AnnotationsListComponent } from './components/file-workload/components/annotations-list/annotations-list.component';
import { AnnotationSourceComponent } from './components/file-workload/components/annotation-source/annotation-source.component';
import { OverlayModule } from '@angular/cdk/overlay';
const screens = [DossierListingScreenComponent, DossierOverviewScreenComponent, FilePreviewScreenComponent, SearchScreenComponent];
@ -89,6 +92,8 @@ const components = [
PageExclusionComponent,
DossierDetailsStatsComponent,
EditDossierDeletedDocumentsComponent,
AnnotationsListComponent,
AnnotationSourceComponent,
...screens,
...dialogs
@ -109,6 +114,6 @@ const services = [
@NgModule({
declarations: [...components],
providers: [...services],
imports: [CommonModule, SharedModule, FileUploadDownloadModule, DossiersRoutingModule]
imports: [CommonModule, SharedModule, FileUploadDownloadModule, DossiersRoutingModule, OverlayModule]
})
export class DossiersModule {}

View File

@ -16,11 +16,12 @@ export class AnnotationDrawService {
) {}
drawAnnotations(activeViewer: WebViewerInstance, annotationWrappers: AnnotationWrapper[], hideSkipped = false, compareMode = false) {
const annotations = [];
annotationWrappers.forEach(annotation => {
annotations.push(this.computeAnnotation(activeViewer, annotation, hideSkipped, compareMode));
});
if (!activeViewer) {
return;
}
const annotations = annotationWrappers.map(annotation =>
this.computeAnnotation(activeViewer, annotation, hideSkipped, compareMode)
);
const annotationManager = activeViewer.Core.annotationManager;
annotationManager.addAnnotations(annotations, { imported: true });
annotationManager.drawAnnotationsFromList(annotations);

View File

@ -12,6 +12,7 @@ export class IconsModule {
constructor(private readonly _iconRegistry: MatIconRegistry, private readonly _sanitizer: DomSanitizer) {
const icons = [
'add',
'ai',
'analyse',
'approved',
'arrow-right',
@ -62,6 +63,7 @@ export class IconsModule {
'reason',
'remove-from-dict',
'report',
'rule',
'secret',
'status',
'status-collapse',

View File

@ -0,0 +1,17 @@
import { Pipe, PipeTransform } from '@angular/core';
import { UserService, UserWrapper } from '@services/user.service';
import { TranslateService } from '@ngx-translate/core';
@Pipe({
name: 'name'
})
export class NamePipe implements PipeTransform {
constructor(private readonly _userService: UserService, private readonly _translateService: TranslateService) {}
transform(value: UserWrapper | string): string {
if (typeof value === 'string') {
return this._userService.getNameForId(value) || this._translateService.instant('unknown');
}
return value.name;
}
}

View File

@ -25,6 +25,7 @@ import { AssignUserDropdownComponent } from './components/assign-user-dropdown/a
import { PageHeaderComponent } from './components/page-header/page-header.component';
import { DatePipe } from '@shared/pipes/date.pipe';
import { LongPressDirective } from '@shared/directives/long-press.directive';
import { NamePipe } from '@shared/pipes/name.pipe';
const buttons = [FileDownloadBtnComponent, UserButtonComponent];
@ -45,7 +46,7 @@ const components = [
...buttons
];
const utils = [DatePipe, NavigateLastDossiersScreenDirective, LongPressDirective];
const utils = [DatePipe, NamePipe, NavigateLastDossiersScreenDirective, LongPressDirective];
const modules = [MatConfigModule, ScrollingModule, IconsModule, FormsModule, ReactiveFormsModule, CommonUiModule];
@ -68,4 +69,5 @@ const modules = [MatConfigModule, ScrollingModule, IconsModule, FormsModule, Rea
}
]
})
export class SharedModule {}
export class SharedModule {
}

View File

@ -106,8 +106,7 @@ export class UserService {
}
getNameForId(userId: string): string | undefined {
const user = this.getUserById(userId);
return user ? user.name : undefined;
return this.getUserById(userId)?.name;
}
isManager(user: UserWrapper = this._currentUser): boolean {

View File

@ -1379,5 +1379,10 @@
"text-placeholder": "Text eingeben"
},
"title": "Wasserzeichen"
},
"annotation-engines": {
"dictionary": "",
"ner": "",
"rule": ""
}
}

View File

@ -1521,5 +1521,10 @@
},
"title": "Watermark"
},
"yesterday": "Yesterday"
"yesterday": "Yesterday",
"annotation-engines": {
"dictionary": "Redaction based on dictionary",
"ner": "Redaction based on AI",
"rule": "Redaction based on rule {rule}"
}
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg"
>
<title>ai</title>
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="ai_3" fill="currentColor" fill-rule="nonzero">
<path
d="M20.4612365,85.4425908 L20.4612365,79.2600589 L15.063788,78.3277723 L19.9214917,65.1776251 L48.5279686,65.1776251 L53.2384691,78.3277723 L47.8410206,79.2600589 L47.8410206,85.4425908 L68.3513248,85.4425908 L68.3513248,79.2600589 L63.1010795,78.5240432 L38.5672228,14 L30.2747792,14 L5.29931305,78.5240432 L1.42108547e-14,79.2600589 L1.42108547e-14,85.4425908 L20.4612365,85.4425908 Z M45.5839058,57.0814524 L22.9146222,57.0814524 L34.2001963,26.4141315 L34.4946026,26.4141315 L45.5839058,57.0814524 Z M98,85.4425908 L98,79.2600589 L90.0019627,77.8861629 L90.0019627,21.6054956 L98,20.2315996 L98,14 L72.3375859,14 L72.3375859,20.2315996 L80.3356232,21.6054956 L80.3356232,77.8861629 L72.3375859,79.2600589 L72.3375859,85.4425908 L98,85.4425908 Z"
id="AI"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg"
>
<title>rule</title>
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="rule" fill="currentColor" fill-rule="nonzero">
<path
d="M53.07405,4 L62.93805,5.641 L46.92795,95.8114 L37.06395,94.1704 L53.07405,4 Z M72,21.9047 L100,49.9047 L72,77.9047 L65,70.9047 L86,49.9047 L65,28.9047 L72,21.9047 Z M28,21.9047 L35,28.9047 L14,49.9047 L35,70.9047 L28,77.9047 L0,49.9047 L28,21.9047 Z"
id="Combined-Shape"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 677 B

@ -1 +1 @@
Subproject commit a16d1db3ab938dccc2c9d82c5e3d283bb857f46c
Subproject commit b55e3bf0ddbda8e8459cb0ae23c8abd5fff46a18

View File

@ -40,6 +40,7 @@ export interface RedactionLogEntry {
type?: string;
value?: string;
legalBasisChangeValue?: string;
engines?: string[];
}
export namespace RedactionLogEntry {