Compare commits
111 Commits
master
...
release/3.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
101e8b8860 | ||
|
|
bbeab30e5f | ||
|
|
923cfa856d | ||
|
|
8fa23c33d9 | ||
|
|
7183995832 | ||
|
|
e4a7597ed5 | ||
|
|
75b335c1c1 | ||
|
|
63595742b5 | ||
|
|
1d4184be1b | ||
|
|
b80486b702 | ||
|
|
edafd45e5c | ||
|
|
eabad1b9bc | ||
|
|
fd637deb12 | ||
|
|
e2bf87d147 | ||
|
|
0c4a705e52 | ||
|
|
45d6ca6166 | ||
|
|
5bbcdac4df | ||
|
|
c2753e6e10 | ||
|
|
e2619e9805 | ||
|
|
d8c7c5be43 | ||
|
|
e260b5782f | ||
|
|
758fecb94c | ||
|
|
24ff24cfcb | ||
|
|
b7a9b1250a | ||
|
|
67caaf9a2c | ||
|
|
4413c7570b | ||
|
|
20a7a4efed | ||
|
|
ded580b160 | ||
|
|
ca43cc406a | ||
|
|
9903c808fa | ||
|
|
c64d787e7f | ||
|
|
1f1af16e0c | ||
|
|
27ea01cff9 | ||
|
|
4f12b437f6 | ||
|
|
f4a7eb35b3 | ||
|
|
608b7b06dc | ||
|
|
ac47c349c5 | ||
|
|
8b2c765fa4 | ||
|
|
d29b522c0b | ||
|
|
852fc1eab3 | ||
|
|
128c597f71 | ||
|
|
8d37d280cc | ||
|
|
615811dfd4 | ||
|
|
de6f5099e0 | ||
|
|
491cb70e50 | ||
|
|
6bbb7aac4a | ||
|
|
68b8d3bd17 | ||
|
|
9fc485c7bf | ||
|
|
943080db46 | ||
|
|
d0db9a116f | ||
|
|
9d8124486e | ||
|
|
1d8171e0f6 | ||
|
|
4ffa30eb36 | ||
|
|
1cfa6acae5 | ||
|
|
1a55fb7c91 | ||
|
|
d25c357c18 | ||
|
|
f82361633f | ||
|
|
783f6fc0e4 | ||
|
|
634a1aa8de | ||
|
|
1fb671e8c2 | ||
|
|
c266a39385 | ||
|
|
c05634c7ae | ||
|
|
1686581bbb | ||
|
|
971d5896fc | ||
|
|
4533e9b54f | ||
|
|
f00f772d39 | ||
|
|
f6eba7be11 | ||
|
|
c4300949d6 | ||
|
|
cc606ffbe3 | ||
|
|
939e726e87 | ||
|
|
607c1646fc | ||
|
|
a7d5a6ec13 | ||
|
|
7e024d52ec | ||
|
|
6add09e574 | ||
|
|
a1d1342a04 | ||
|
|
92d744c959 | ||
|
|
4e84fe3f03 | ||
|
|
43a2191723 | ||
|
|
e3f76fd7e0 | ||
|
|
a71d53ec92 | ||
|
|
b87f5dca9d | ||
|
|
2e26e7fbbb | ||
|
|
8e6d94f771 | ||
|
|
ad85677258 | ||
|
|
00a4123dcf | ||
|
|
e32071cf6c | ||
|
|
19ce27b046 | ||
|
|
d0409df1fa | ||
|
|
e6850fbc09 | ||
|
|
ab07929942 | ||
|
|
9f71c120a0 | ||
|
|
692fce549e | ||
|
|
c0b192ddfd | ||
|
|
6f64d4775f | ||
|
|
4774062182 | ||
|
|
ce30a177de | ||
|
|
fa550f1693 | ||
|
|
344e7ac096 | ||
|
|
9510f23280 | ||
|
|
200b4f2d46 | ||
|
|
cdc8b7ed9e | ||
|
|
10e011a311 | ||
|
|
d7d8f6784a | ||
|
|
32eaa42270 | ||
|
|
dc28e59b40 | ||
|
|
5eab66c79e | ||
|
|
7ba8b51f8a | ||
|
|
86a0f5f57a | ||
|
|
fb3ef83cd2 | ||
|
|
0723607a45 | ||
|
|
95575d86fc |
2
.gitignore
vendored
2
.gitignore
vendored
@ -41,8 +41,6 @@ testem.log
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
version.properties
|
|
||||||
|
|
||||||
paligo-styles/style.css*
|
paligo-styles/style.css*
|
||||||
|
|
||||||
migrations.json
|
migrations.json
|
||||||
|
|||||||
14
.gitlab-ci.yml
Normal file
14
.gitlab-ci.yml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
variables:
|
||||||
|
GIT_SUBMODULE_STRATEGY: recursive
|
||||||
|
GIT_SUBMODULE_FORCE_HTTPS: 'true'
|
||||||
|
PROJECT: red-ui
|
||||||
|
DOCKERFILELOCATION: 'docker/$PROJECT/Dockerfile'
|
||||||
|
|
||||||
|
workflow:
|
||||||
|
rules:
|
||||||
|
- when: always
|
||||||
|
|
||||||
|
include:
|
||||||
|
- project: 'gitlab/gitlab'
|
||||||
|
ref: 'main'
|
||||||
|
file: 'ci-templates/docker_build_nexus.yml'
|
||||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -1,3 +1,3 @@
|
|||||||
[submodule "libs/common-ui"]
|
[submodule "libs/common-ui"]
|
||||||
path = libs/common-ui
|
path = libs/common-ui
|
||||||
url = ssh://git@git.iqser.com:2222/sl/common-ui.git
|
url = ../../fforesight/shared-ui-libraries/common-ui.git
|
||||||
|
|||||||
@ -29,7 +29,10 @@
|
|||||||
|
|
||||||
<iqser-help-button *deny="roles.getRss" [iqserHelpMode]="'help_mode'" id="help-mode-button"></iqser-help-button>
|
<iqser-help-button *deny="roles.getRss" [iqserHelpMode]="'help_mode'" id="help-mode-button"></iqser-help-button>
|
||||||
|
|
||||||
<redaction-notifications [iqserHelpMode]="'open_notifications'"></redaction-notifications>
|
<redaction-notifications
|
||||||
|
*ngIf="currentUser.isUser || currentUser.isManager"
|
||||||
|
[iqserHelpMode]="'open_notifications'"
|
||||||
|
></redaction-notifications>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<iqser-user-button [iqserHelpMode]="'open_usermenu'" [matMenuTriggerFor]="userMenu" id="userMenu"></iqser-user-button>
|
<iqser-user-button [iqserHelpMode]="'open_usermenu'" [matMenuTriggerFor]="userMenu" id="userMenu"></iqser-user-button>
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
<iqser-circle-button [matMenuTriggerFor]="menu" [showDot]="hasUnreadNotifications$ | async" icon="red:notification"></iqser-circle-button>
|
<iqser-circle-button
|
||||||
|
[matMenuTriggerFor]="menu"
|
||||||
|
[showDot]="hasUnreadNotifications$ | async"
|
||||||
|
buttonId="notification-button"
|
||||||
|
icon="red:notification"
|
||||||
|
></iqser-circle-button>
|
||||||
|
|
||||||
<mat-menu #menu="matMenu" backdropClass="notifications-backdrop" class="notifications-menu" xPosition="before">
|
<mat-menu #menu="matMenu" backdropClass="notifications-backdrop" class="notifications-menu" xPosition="before">
|
||||||
<ng-template matMenuContent>
|
<ng-template matMenuContent>
|
||||||
@ -13,15 +18,21 @@
|
|||||||
<div *ngFor="let group of groups; let first = first">
|
<div *ngFor="let group of groups; let first = first">
|
||||||
<div class="all-caps-label flex-align-items-center">
|
<div class="all-caps-label flex-align-items-center">
|
||||||
<div>{{ group.date }}</div>
|
<div>{{ group.date }}</div>
|
||||||
<div (click)="markRead($event)" *ngIf="(hasUnreadNotifications$ | async) && first" class="view-all">
|
<div
|
||||||
|
(click)="markRead($event)"
|
||||||
|
*ngIf="(hasUnreadNotifications$ | async) && first"
|
||||||
|
class="view-all"
|
||||||
|
id="notifications-mark-all-as-read-btn"
|
||||||
|
>
|
||||||
{{ 'notifications.mark-all-as-read' | translate }}
|
{{ 'notifications.mark-all-as-read' | translate }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
(click)="markRead($event, [notification], true)"
|
(click)="markRead($event, [notification], true)"
|
||||||
*ngFor="let notification of group.notifications"
|
*ngFor="let notification of group.notifications; trackBy: trackBy"
|
||||||
[class.unread]="!notification.readDate"
|
[class.unread]="!notification.readDate"
|
||||||
|
[id]="'notifications-mark-as-read-' + notification.id + '-btn'"
|
||||||
class="notification"
|
class="notification"
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
>
|
>
|
||||||
@ -33,6 +44,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
(click)="markRead($event, [notification], !notification.readDate)"
|
(click)="markRead($event, [notification], !notification.readDate)"
|
||||||
|
[id]="'notifications-mark-' + notification.id"
|
||||||
class="dot"
|
class="dot"
|
||||||
matTooltip="{{ 'notifications.mark-as' | translate : { type: notification.readDate ? 'unread' : 'read' } }}"
|
matTooltip="{{ 'notifications.mark-as' | translate : { type: notification.readDate ? 'unread' : 'read' } }}"
|
||||||
matTooltipPosition="before"
|
matTooltipPosition="before"
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { NotificationsService } from '@services/notifications.service';
|
|||||||
import { Notification } from '@red/domain';
|
import { Notification } from '@red/domain';
|
||||||
import { distinctUntilChanged, map } from 'rxjs/operators';
|
import { distinctUntilChanged, map } from 'rxjs/operators';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { isToday, shareLast } from '@iqser/common-ui';
|
import { isToday, shareLast, trackByFactory } from '@iqser/common-ui';
|
||||||
import dayjs, { Dayjs } from 'dayjs';
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
@ -29,6 +29,7 @@ function chronologically(first: string, second: string) {
|
|||||||
export class NotificationsComponent {
|
export class NotificationsComponent {
|
||||||
readonly hasUnreadNotifications$: Observable<boolean>;
|
readonly hasUnreadNotifications$: Observable<boolean>;
|
||||||
readonly groupedNotifications$: Observable<NotificationsGroup[]>;
|
readonly groupedNotifications$: Observable<NotificationsGroup[]>;
|
||||||
|
readonly trackBy = trackByFactory();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _notificationsService: NotificationsService,
|
private readonly _notificationsService: NotificationsService,
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import {
|
|||||||
annotationDefaultColorConfig,
|
annotationDefaultColorConfig,
|
||||||
annotationEntityColorConfig,
|
annotationEntityColorConfig,
|
||||||
AnnotationIconType,
|
AnnotationIconType,
|
||||||
|
ChangeTypes,
|
||||||
DefaultColors,
|
DefaultColors,
|
||||||
Dictionary,
|
Dictionary,
|
||||||
Earmark,
|
Earmark,
|
||||||
@ -23,6 +24,7 @@ import {
|
|||||||
} from '@red/domain';
|
} from '@red/domain';
|
||||||
import { RedactionLogEntry } from '@models/file/redaction-log.entry';
|
import { RedactionLogEntry } from '@models/file/redaction-log.entry';
|
||||||
import { IListable, List } from '@iqser/common-ui';
|
import { IListable, List } from '@iqser/common-ui';
|
||||||
|
import { chronologicallyBy, timestampOf } from '../../modules/file-preview/services/file-data.service';
|
||||||
|
|
||||||
export class AnnotationWrapper implements IListable, Record<string, unknown> {
|
export class AnnotationWrapper implements IListable, Record<string, unknown> {
|
||||||
[x: string]: unknown;
|
[x: string]: unknown;
|
||||||
@ -110,6 +112,10 @@ export class AnnotationWrapper implements IListable, Record<string, unknown> {
|
|||||||
return this.type?.toLowerCase() === 'image' || this.image;
|
return this.type?.toLowerCase() === 'image' || this.image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isNotSignatureImage() {
|
||||||
|
return this.isImage && this.recategorizationType === 'signature';
|
||||||
|
}
|
||||||
|
|
||||||
get isOCR() {
|
get isOCR() {
|
||||||
return this.type?.toLowerCase() === 'ocr';
|
return this.type?.toLowerCase() === 'ocr';
|
||||||
}
|
}
|
||||||
@ -134,6 +140,10 @@ export class AnnotationWrapper implements IListable, Record<string, unknown> {
|
|||||||
return this.type?.toLowerCase() === 'false_positive' && !!FalsePositiveSuperTypes[this.superType];
|
return this.type?.toLowerCase() === 'false_positive' && !!FalsePositiveSuperTypes[this.superType];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isSuggestionAddToFalsePositive() {
|
||||||
|
return this.typeLabel === annotationTypesTranslations[SuggestionAddFalsePositive];
|
||||||
|
}
|
||||||
|
|
||||||
get isDeclinedSuggestion() {
|
get isDeclinedSuggestion() {
|
||||||
return this.superType === SuperTypes.DeclinedSuggestion;
|
return this.superType === SuperTypes.DeclinedSuggestion;
|
||||||
}
|
}
|
||||||
@ -186,10 +196,18 @@ export class AnnotationWrapper implements IListable, Record<string, unknown> {
|
|||||||
return this.superType === SuperTypes.SuggestionRecategorizeImage;
|
return this.superType === SuperTypes.SuggestionRecategorizeImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isSuggestionForceHint() {
|
||||||
|
return this.superType === SuperTypes.SuggestionForceHint;
|
||||||
|
}
|
||||||
|
|
||||||
get isSuggestionAdd() {
|
get isSuggestionAdd() {
|
||||||
return !!SuggestionAddSuperTypes[this.superType];
|
return !!SuggestionAddSuperTypes[this.superType];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isSuggestionAddDictionary() {
|
||||||
|
return this.superType === SuperTypes.SuggestionAddDictionary;
|
||||||
|
}
|
||||||
|
|
||||||
get isSuggestionRemove() {
|
get isSuggestionRemove() {
|
||||||
return !!SuggestionRemoveSuperTypes[this.superType];
|
return !!SuggestionRemoveSuperTypes[this.superType];
|
||||||
}
|
}
|
||||||
@ -328,9 +346,14 @@ export class AnnotationWrapper implements IListable, Record<string, unknown> {
|
|||||||
const entity = dictionaries.find(d => d.type === annotationWrapper.typeValue);
|
const entity = dictionaries.find(d => d.type === annotationWrapper.typeValue);
|
||||||
annotationWrapper.entity = entity.virtual ? null : entity;
|
annotationWrapper.entity = entity.virtual ? null : entity;
|
||||||
|
|
||||||
const colorKey = annotationWrapper.isSuperTypeBasedColor
|
let colorKey = annotationWrapper.isSuperTypeBasedColor
|
||||||
? annotationDefaultColorConfig[annotationWrapper.superType]
|
? annotationDefaultColorConfig[annotationWrapper.superType]
|
||||||
: annotationEntityColorConfig[annotationWrapper.superType];
|
: annotationEntityColorConfig[annotationWrapper.superType];
|
||||||
|
|
||||||
|
if (annotationWrapper.isSuperTypeBasedColor && annotationWrapper.isSuggestionResize && !annotationWrapper.isModifyDictionary) {
|
||||||
|
colorKey = annotationDefaultColorConfig[SuperTypes.SuggestionAdd];
|
||||||
|
}
|
||||||
|
|
||||||
annotationWrapper.color = annotationWrapper.isSuperTypeBasedColor ? defaultColors[colorKey] : (entity[colorKey] as string);
|
annotationWrapper.color = annotationWrapper.isSuperTypeBasedColor ? defaultColors[colorKey] : (entity[colorKey] as string);
|
||||||
|
|
||||||
return annotationWrapper;
|
return annotationWrapper;
|
||||||
@ -356,6 +379,15 @@ export class AnnotationWrapper implements IListable, Record<string, unknown> {
|
|||||||
private static _setSuperType(annotationWrapper: AnnotationWrapper, redactionLogEntryWrapper: RedactionLogEntry) {
|
private static _setSuperType(annotationWrapper: AnnotationWrapper, redactionLogEntryWrapper: RedactionLogEntry) {
|
||||||
if (redactionLogEntryWrapper.manualChanges?.length) {
|
if (redactionLogEntryWrapper.manualChanges?.length) {
|
||||||
const lastRelevantManualChange = this._getLastRelevantManualChange(redactionLogEntryWrapper.manualChanges);
|
const lastRelevantManualChange = this._getLastRelevantManualChange(redactionLogEntryWrapper.manualChanges);
|
||||||
|
const viableChanges = redactionLogEntryWrapper.changes.filter(c => c.analysisNumber > 1);
|
||||||
|
const lastChange = viableChanges.sort(chronologicallyBy(x => x.dateTime)).at(-1);
|
||||||
|
const lastChangeOccurredAfterLastManualChange =
|
||||||
|
lastChange && timestampOf(lastChange.dateTime) > timestampOf(lastRelevantManualChange.processedDate);
|
||||||
|
|
||||||
|
if (lastChangeOccurredAfterLastManualChange && lastChange.type === ChangeTypes.ADDED && redactionLogEntryWrapper.redacted) {
|
||||||
|
annotationWrapper.superType = SuperTypes.Redaction;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
annotationWrapper.pending = !lastRelevantManualChange.processed;
|
annotationWrapper.pending = !lastRelevantManualChange.processed;
|
||||||
|
|
||||||
@ -494,7 +526,7 @@ export class AnnotationWrapper implements IListable, Record<string, unknown> {
|
|||||||
if (lastManualChange.processed) {
|
if (lastManualChange.processed) {
|
||||||
switch (lastManualChange.annotationStatus) {
|
switch (lastManualChange.annotationStatus) {
|
||||||
case LogEntryStatuses.APPROVED:
|
case LogEntryStatuses.APPROVED:
|
||||||
return SuperTypes.Redaction;
|
return redactionLogEntry.recommendation ? SuperTypes.Recommendation : SuperTypes.Skipped;
|
||||||
case LogEntryStatuses.DECLINED:
|
case LogEntryStatuses.DECLINED:
|
||||||
return isHintDictionary ? SuperTypes.Hint : SuperTypes.Skipped;
|
return isHintDictionary ? SuperTypes.Hint : SuperTypes.Skipped;
|
||||||
case LogEntryStatuses.REQUESTED:
|
case LogEntryStatuses.REQUESTED:
|
||||||
|
|||||||
@ -0,0 +1,5 @@
|
|||||||
|
@use 'common-mixins';
|
||||||
|
|
||||||
|
.dialog-header {
|
||||||
|
@include common-mixins.line-clamp(1);
|
||||||
|
}
|
||||||
@ -15,6 +15,7 @@ export interface AddEditDossierAttributeDialogData {
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './add-edit-dossier-attribute-dialog.component.html',
|
templateUrl: './add-edit-dossier-attribute-dialog.component.html',
|
||||||
|
styleUrls: ['./add-edit-dossier-attribute-dialog.component.scss'],
|
||||||
})
|
})
|
||||||
export class AddEditDossierAttributeDialogComponent extends BaseDialogComponent implements OnDestroy {
|
export class AddEditDossierAttributeDialogComponent extends BaseDialogComponent implements OnDestroy {
|
||||||
readonly dossierAttribute = this.data.dossierAttribute;
|
readonly dossierAttribute = this.data.dossierAttribute;
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
@use 'common-mixins';
|
||||||
|
|
||||||
.options-wrapper {
|
.options-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -7,3 +9,7 @@
|
|||||||
margin-right: 32px;
|
margin-right: 32px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog-header {
|
||||||
|
@include common-mixins.line-clamp(1);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
<div [translateParams]="{ userName: user | name }" [translate]="'reset-password-dialog.header'" class="dialog-header heading-l"></div>
|
<div [innerHTML]="'reset-password-dialog.header' | translate : { userName: user | name }" class="dialog-header heading-l"></div>
|
||||||
|
|
||||||
<form (submit)="save()" [formGroup]="form">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
<div class="heading">{{ 'confirm-delete-dossier-state.warning' | translate : translateArgs }}</div>
|
<div [innerHTML]="'confirm-delete-dossier-state.warning' | translate : translateArgs" class="heading"></div>
|
||||||
|
|
||||||
<form *ngIf="data.dossierCount !== 0 && data.otherStates.length > 0" [formGroup]="form" class="mt-16">
|
<form *ngIf="data.dossierCount !== 0 && data.otherStates.length > 0" [formGroup]="form" class="mt-16">
|
||||||
<div class="iqser-input-group">
|
<div class="iqser-input-group">
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<div class="dialog-header heading-l" translate="smtp-auth-config.title"></div>
|
<div class="dialog-header heading-l" [translate]="'smtp-auth-config.title'"></div>
|
||||||
|
|
||||||
<form (submit)="save()" [formGroup]="form">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<div class="dialog-header heading-l" translate="upload-dictionary-dialog.title"></div>
|
<div class="dialog-header heading-l" [translate]="'upload-dictionary-dialog.title'"></div>
|
||||||
|
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
<p translate="upload-dictionary-dialog.question"></p>
|
<p translate="upload-dictionary-dialog.question"></p>
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { ActivatedRoute } from '@angular/router';
|
|||||||
import { DictionaryManagerComponent } from '@shared/components/dictionary-manager/dictionary-manager.component';
|
import { DictionaryManagerComponent } from '@shared/components/dictionary-manager/dictionary-manager.component';
|
||||||
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
||||||
import { getCurrentUser, getParam, IqserPermissionsService, List, LoadingService } from '@iqser/common-ui';
|
import { getCurrentUser, getParam, IqserPermissionsService, List, LoadingService } from '@iqser/common-ui';
|
||||||
import { BehaviorSubject, firstValueFrom, lastValueFrom } from 'rxjs';
|
import { BehaviorSubject, firstValueFrom } from 'rxjs';
|
||||||
import { DICTIONARY_TO_ENTRY_TYPE_MAP, DICTIONARY_TYPE_KEY_MAP, DictionaryType, DOSSIER_TEMPLATE_ID, ENTITY_TYPE, User } from '@red/domain';
|
import { DICTIONARY_TO_ENTRY_TYPE_MAP, DICTIONARY_TYPE_KEY_MAP, DictionaryType, DOSSIER_TEMPLATE_ID, ENTITY_TYPE, User } from '@red/domain';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import { ROLES } from '@users/roles';
|
import { ROLES } from '@users/roles';
|
||||||
@ -47,16 +47,15 @@ export class DictionaryScreenComponent implements OnInit {
|
|||||||
|
|
||||||
this._loadingService.start();
|
this._loadingService.start();
|
||||||
try {
|
try {
|
||||||
await lastValueFrom(
|
await this._dictionaryService.saveEntries(
|
||||||
this._dictionaryService.saveEntries(
|
entries,
|
||||||
entries,
|
this.initialEntries$.value,
|
||||||
this.initialEntries$.value,
|
this.#dossierTemplateId,
|
||||||
this.#dossierTemplateId,
|
this.entityType,
|
||||||
this.entityType,
|
null,
|
||||||
null,
|
true,
|
||||||
true,
|
DICTIONARY_TO_ENTRY_TYPE_MAP[this.type],
|
||||||
DICTIONARY_TO_ENTRY_TYPE_MAP[this.type],
|
false,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
await this._loadEntries();
|
await this._loadEntries();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<div
|
<div
|
||||||
[translateParams]="{
|
[innerHTML]="
|
||||||
type: data.justification ? 'edit' : 'create',
|
'add-edit-justification.title' | translate : { type: data.justification ? 'edit' : 'create', name: data.justification?.name }
|
||||||
name: data.justification?.name
|
"
|
||||||
}"
|
|
||||||
[translate]="'add-edit-justification.title'"
|
|
||||||
class="dialog-header heading-l"
|
class="dialog-header heading-l"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
|
|||||||
@ -93,7 +93,7 @@ export class LicenseChartComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cumulativePages !== this._licenseService.currentInfo.numberOfAnalyzedPages) {
|
if (cumulativePages !== this._licenseService.currentLicenseInfo.numberOfAnalyzedPages) {
|
||||||
this._licenseService.wipeStoredReportsAndReloadSelectedLicenseData();
|
this._licenseService.wipeStoredReportsAndReloadSelectedLicenseData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -61,26 +61,8 @@
|
|||||||
<div>{{ licenseService.totalLicensedNumberOfPages }}</div>
|
<div>{{ licenseService.totalLicensedNumberOfPages }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div translate="license-info-screen.analyzed-pages"></div>
|
|
||||||
<div>{{ licenseService.currentInfo.numberOfAnalyzedPages }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div translate="license-info-screen.ocr-analyzed-pages"></div>
|
|
||||||
<div>{{ licenseService.currentInfo.numberOfOcrPages }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section-title all-caps-label" translate="license-info-screen.usage-details"></div>
|
<div class="section-title all-caps-label" translate="license-info-screen.usage-details"></div>
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div
|
|
||||||
*ngIf="licenseService.annualInfo.startDate | date : 'longDate' as startDate"
|
|
||||||
[innerHTML]="'license-info-screen.total-analyzed' | translate : { date: startDate }"
|
|
||||||
></div>
|
|
||||||
<div>{{ licenseService.annualInfo.numberOfAnalyzedPages }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div translate="license-info-screen.current-analyzed"></div>
|
<div translate="license-info-screen.current-analyzed"></div>
|
||||||
<div>
|
<div>
|
||||||
@ -89,10 +71,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div translate="license-info-screen.ocr-analyzed-pages"></div>
|
||||||
|
<div>{{ licenseService.currentLicenseInfo.numberOfOcrPages }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div *ngIf="!!licenseService.unlicensedPages" class="row">
|
<div *ngIf="!!licenseService.unlicensedPages" class="row">
|
||||||
<div translate="license-info-screen.unlicensed-analyzed"></div>
|
<div translate="license-info-screen.unlicensed-analyzed"></div>
|
||||||
<div>{{ licenseService.unlicensedPages }}</div>
|
<div>{{ licenseService.unlicensedPages }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div [innerHTML]="'license-info-screen.total-analyzed' | translate"></div>
|
||||||
|
|
||||||
|
<div>{{ licenseService.allLicensesInfo.numberOfAnalyzedPages }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div [innerHTML]="'license-info-screen.total-ocr-analyzed' | translate"></div>
|
||||||
|
<div>{{ licenseService.allLicensesInfo.numberOfOcrPages }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<redaction-license-chart></redaction-license-chart>
|
<redaction-license-chart></redaction-license-chart>
|
||||||
|
|||||||
@ -62,7 +62,7 @@ export class LicenseScreenComponent {
|
|||||||
const lineBreak = '%0D%0A';
|
const lineBreak = '%0D%0A';
|
||||||
const body = [
|
const body = [
|
||||||
this._translateService.instant('license-info-screen.email.body.analyzed', {
|
this._translateService.instant('license-info-screen.email.body.analyzed', {
|
||||||
pages: this.licenseService.currentInfo.numberOfAnalyzedPages,
|
pages: this.licenseService.currentLicenseInfo.numberOfAnalyzedPages,
|
||||||
}),
|
}),
|
||||||
this._translateService.instant('license-info-screen.email.body.licensed', {
|
this._translateService.instant('license-info-screen.email.body.licensed', {
|
||||||
pages: this.licenseService.totalLicensedNumberOfPages,
|
pages: this.licenseService.totalLicensedNumberOfPages,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<div class="content-container">
|
<div class="content-container">
|
||||||
<div class="viewer" id="viewer"></div>
|
<div #viewer class="viewer" id="viewer"></div>
|
||||||
|
|
||||||
<div *ngIf="changed && currentUser.isAdmin" class="changes-box">
|
<div *ngIf="!!instance && changed && currentUser.isAdmin" class="changes-box">
|
||||||
<iqser-icon-button
|
<iqser-icon-button
|
||||||
(action)="save()"
|
(action)="save()"
|
||||||
[disabled]="!valid"
|
[disabled]="!valid"
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Component, Inject } from '@angular/core';
|
import { Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core';
|
||||||
import WebViewer, { WebViewerInstance } from '@pdftron/webviewer';
|
import WebViewer, { WebViewerInstance } from '@pdftron/webviewer';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||||
@ -51,7 +51,7 @@ interface WatermarkForm {
|
|||||||
templateUrl: './watermark-screen.component.html',
|
templateUrl: './watermark-screen.component.html',
|
||||||
styleUrls: ['./watermark-screen.component.scss'],
|
styleUrls: ['./watermark-screen.component.scss'],
|
||||||
})
|
})
|
||||||
export class WatermarkScreenComponent {
|
export class WatermarkScreenComponent implements OnInit {
|
||||||
readonly iconButtonTypes = IconButtonTypes;
|
readonly iconButtonTypes = IconButtonTypes;
|
||||||
readonly currentUser = getCurrentUser<User>();
|
readonly currentUser = getCurrentUser<User>();
|
||||||
readonly form = this._getForm();
|
readonly form = this._getForm();
|
||||||
@ -62,9 +62,10 @@ export class WatermarkScreenComponent {
|
|||||||
{ value: 'courier', display: 'Courier' },
|
{ value: 'courier', display: 'Courier' },
|
||||||
];
|
];
|
||||||
readonly orientationOptions = ['DIAGONAL', 'HORIZONTAL', 'VERTICAL'];
|
readonly orientationOptions = ['DIAGONAL', 'HORIZONTAL', 'VERTICAL'];
|
||||||
|
@ViewChild('viewer', { static: true }) viewer: ElementRef<HTMLDivElement>;
|
||||||
|
instance: WebViewerInstance;
|
||||||
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
|
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
|
||||||
readonly #watermarkId = Number(getParam(WATERMARK_ID));
|
readonly #watermarkId = Number(getParam(WATERMARK_ID));
|
||||||
private _instance: WebViewerInstance;
|
|
||||||
private _watermark: Partial<IWatermark> = {};
|
private _watermark: Partial<IWatermark> = {};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -102,6 +103,10 @@ export class WatermarkScreenComponent {
|
|||||||
return this.form.valid;
|
return this.form.valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
await this._loadViewer();
|
||||||
|
}
|
||||||
|
|
||||||
@Debounce()
|
@Debounce()
|
||||||
async configChanged() {
|
async configChanged() {
|
||||||
await this._drawWatermark();
|
await this._drawWatermark();
|
||||||
@ -144,15 +149,14 @@ export class WatermarkScreenComponent {
|
|||||||
private async _initForm(watermark: Partial<IWatermark>) {
|
private async _initForm(watermark: Partial<IWatermark>) {
|
||||||
this._watermark = { ...watermark, dossierTemplateId: this.#dossierTemplateId };
|
this._watermark = { ...watermark, dossierTemplateId: this.#dossierTemplateId };
|
||||||
this.form.patchValue({ ...watermark });
|
this.form.patchValue({ ...watermark });
|
||||||
await this._loadViewer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _loadViewer() {
|
private async _loadViewer() {
|
||||||
if (this._instance) {
|
if (this.instance) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._instance = await WebViewer(
|
this.instance = await WebViewer(
|
||||||
{
|
{
|
||||||
licenseKey: this._licenseService.activeLicenseKey,
|
licenseKey: this._licenseService.activeLicenseKey,
|
||||||
path: this._convertPath('/assets/wv-resources'),
|
path: this._convertPath('/assets/wv-resources'),
|
||||||
@ -161,18 +165,18 @@ export class WatermarkScreenComponent {
|
|||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
backendType: 'ems',
|
backendType: 'ems',
|
||||||
},
|
},
|
||||||
document.getElementById('viewer'),
|
this.viewer.nativeElement,
|
||||||
);
|
);
|
||||||
|
|
||||||
this._instance.UI.setTheme(this._userPreferenceService.getTheme());
|
this.instance.UI.setTheme(this._userPreferenceService.getTheme());
|
||||||
|
|
||||||
this._instance.Core.documentViewer.addEventListener('documentLoaded', async () => {
|
this.instance.Core.documentViewer.addEventListener('documentLoaded', async () => {
|
||||||
this._loadingService.stop();
|
this._loadingService.stop();
|
||||||
await this._drawWatermark();
|
await this._drawWatermark();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (environment.production) {
|
if (environment.production) {
|
||||||
this._instance.Core.setCustomFontURL('https://' + window.location.host + this._convertPath('/assets/pdftron'));
|
this.instance.Core.setCustomFontURL('https://' + window.location.host + this._convertPath('/assets/pdftron'));
|
||||||
}
|
}
|
||||||
|
|
||||||
this._disableElements();
|
this._disableElements();
|
||||||
@ -181,16 +185,16 @@ export class WatermarkScreenComponent {
|
|||||||
responseType: 'blob',
|
responseType: 'blob',
|
||||||
});
|
});
|
||||||
const blobData = await firstValueFrom(request);
|
const blobData = await firstValueFrom(request);
|
||||||
this._instance.UI.loadDocument(blobData, { filename: 'blank.pdf' });
|
this.instance.UI.loadDocument(blobData, { filename: 'blank.pdf' });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _disableElements() {
|
private _disableElements() {
|
||||||
this._instance.UI.disableElements(['header', 'toolsHeader', 'pageNavOverlay', 'textPopup']);
|
this.instance.UI.disableElements(['header', 'toolsHeader', 'pageNavOverlay', 'textPopup']);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _drawWatermark() {
|
private async _drawWatermark() {
|
||||||
const pdfNet = this._instance.Core.PDFNet;
|
const pdfNet = this.instance.Core.PDFNet;
|
||||||
const document = await this._instance.Core.documentViewer.getDocument().getPDFDoc();
|
const document = await this.instance.Core.documentViewer.getDocument().getPDFDoc();
|
||||||
|
|
||||||
await stampPDFPage(
|
await stampPDFPage(
|
||||||
document,
|
document,
|
||||||
@ -204,8 +208,8 @@ export class WatermarkScreenComponent {
|
|||||||
[1],
|
[1],
|
||||||
this._licenseService.activeLicenseKey,
|
this._licenseService.activeLicenseKey,
|
||||||
);
|
);
|
||||||
this._instance.Core.documentViewer.refreshAll();
|
this.instance.Core.documentViewer.refreshAll();
|
||||||
this._instance.Core.documentViewer.updateView([0], 0);
|
this.instance.Core.documentViewer.updateView([0], 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getForm() {
|
private _getForm() {
|
||||||
|
|||||||
@ -4,41 +4,40 @@
|
|||||||
<span class="clamp-3"> {{ fileAttributeValue ? (fileAttributeValue | date : 'd MMM yyyy') : '-' }}</span>
|
<span class="clamp-3"> {{ fileAttributeValue ? (fileAttributeValue | date : 'd MMM yyyy') : '-' }}</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-container *ngIf="(isEditingFileAttribute$ | async) === false || isInEditMode">
|
<ng-container *ngIf="((fileAttributesService.isEditingFileAttribute$ | async) === false || isInEditMode) && !file.isInitialProcessing">
|
||||||
<div class="edit-button" *ngIf="!isInEditMode; else input">
|
<div *ngIf="!isInEditMode; else input" class="action-buttons edit-button">
|
||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
id="edit-attribute-button"
|
|
||||||
*ngIf="permissionsService.canEditFileAttributes(file, dossier)"
|
|
||||||
(action)="editFileAttribute($event)"
|
(action)="editFileAttribute($event)"
|
||||||
[icon]="'iqser:edit'"
|
*ngIf="permissionsService.canEditFileAttributes(file, dossier)"
|
||||||
[disabled]="!fileAttribute.editable"
|
[disabled]="!fileAttribute.editable"
|
||||||
|
[icon]="'iqser:edit'"
|
||||||
[tooltip]="'file-attribute.actions.edit' | translate"
|
[tooltip]="'file-attribute.actions.edit' | translate"
|
||||||
|
[iqserHelpMode]="'edit-file-attributes'"
|
||||||
|
id="edit-attribute-button"
|
||||||
></iqser-circle-button>
|
></iqser-circle-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #input>
|
<ng-template #input>
|
||||||
<div class="edit-input" (click)="$event?.stopPropagation()">
|
<div class="edit-input" stopPropagation>
|
||||||
<form (submit)="save()" [formGroup]="form">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<iqser-dynamic-input
|
<iqser-dynamic-input
|
||||||
|
(closedDatepicker)="closedDatepicker = $event"
|
||||||
|
(keydown.escape)="close()"
|
||||||
[formControlName]="fileAttribute.id"
|
[formControlName]="fileAttribute.id"
|
||||||
[id]="fileAttribute.id"
|
[id]="fileAttribute.id"
|
||||||
[type]="fileAttribute.type"
|
[type]="fileAttribute.type"
|
||||||
(keydown.escape)="close()"
|
></iqser-dynamic-input>
|
||||||
(closedDatepicker)="closedDatepicker = $event"
|
|
||||||
>
|
|
||||||
</iqser-dynamic-input>
|
|
||||||
|
|
||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
class="save"
|
|
||||||
[icon]="'iqser:check'"
|
|
||||||
[disabled]="disabled"
|
|
||||||
(action)="save($event)"
|
(action)="save($event)"
|
||||||
|
[disabled]="disabled"
|
||||||
|
[icon]="'iqser:check'"
|
||||||
|
class="save"
|
||||||
></iqser-circle-button>
|
></iqser-circle-button>
|
||||||
<iqser-circle-button [icon]="'iqser:close'" (action)="close($event)"></iqser-circle-button>
|
|
||||||
|
<iqser-circle-button (action)="close($event)" [icon]="'iqser:close'"></iqser-circle-button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- A hack to avoid subscribing in component -->
|
|
||||||
<ng-container *ngIf="selectedFile$ | async"></ng-container>
|
|
||||||
|
|||||||
@ -6,26 +6,19 @@
|
|||||||
|
|
||||||
.edit-button {
|
.edit-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: none;
|
|
||||||
background: radial-gradient(var(--iqser-side-nav) 10%, rgba(244, 245, 247, 0) 60%);
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 150%;
|
right: 10%;
|
||||||
transform: translate(-25%);
|
width: 90%;
|
||||||
|
background: linear-gradient(to left, var(--iqser-side-nav) 20%, rgba(244, 245, 247, 0) 60%);
|
||||||
|
|
||||||
iqser-circle-button {
|
iqser-circle-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 80%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.edit-button {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-input {
|
.edit-input {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { ChangeDetectionStrategy, Component, HostListener, Input, OnInit } from '@angular/core';
|
import { Component, HostListener, Input, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { Dossier, File, FileAttributeConfigTypes, IFileAttributeConfig } from '@red/domain';
|
import { Dossier, File, FileAttributeConfigTypes, IFileAttributeConfig } from '@red/domain';
|
||||||
import { BaseFormComponent, ListingService, Toaster } from '@iqser/common-ui';
|
import { BaseFormComponent, ListingService, Toaster } from '@iqser/common-ui';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import { FormBuilder, UntypedFormGroup } from '@angular/forms';
|
import { FormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||||
import { BehaviorSubject, firstValueFrom, Observable } from 'rxjs';
|
import { firstValueFrom, Subscription } from 'rxjs';
|
||||||
import { FilesService } from '@services/files/files.service';
|
import { FilesService } from '@services/files/files.service';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
@ -15,48 +15,34 @@ import { filter, map, tap } from 'rxjs/operators';
|
|||||||
selector: 'redaction-file-attribute [fileAttribute] [file] [dossier]',
|
selector: 'redaction-file-attribute [fileAttribute] [file] [dossier]',
|
||||||
templateUrl: './file-attribute.component.html',
|
templateUrl: './file-attribute.component.html',
|
||||||
styleUrls: ['./file-attribute.component.scss'],
|
styleUrls: ['./file-attribute.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
})
|
})
|
||||||
export class FileAttributeComponent extends BaseFormComponent implements OnInit {
|
export class FileAttributeComponent extends BaseFormComponent implements OnDestroy {
|
||||||
@Input() fileAttribute!: IFileAttributeConfig;
|
@Input() fileAttribute!: IFileAttributeConfig;
|
||||||
|
|
||||||
@Input() file!: File;
|
@Input() file!: File;
|
||||||
|
|
||||||
@Input() dossier!: Dossier;
|
@Input() dossier!: Dossier;
|
||||||
|
|
||||||
isInEditMode = false;
|
isInEditMode = false;
|
||||||
closedDatepicker = true;
|
closedDatepicker = true;
|
||||||
readonly isEditingFileAttribute$: BehaviorSubject<boolean>;
|
readonly #subscriptions = new Subscription();
|
||||||
readonly selectedFile$: Observable<boolean>;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _fileAttributesService: FileAttributesService,
|
router: Router,
|
||||||
private readonly _toaster: Toaster,
|
private readonly _toaster: Toaster,
|
||||||
private readonly _formBuilder: FormBuilder,
|
private readonly _formBuilder: FormBuilder,
|
||||||
private readonly _filesService: FilesService,
|
private readonly _filesService: FilesService,
|
||||||
private readonly _router: Router,
|
|
||||||
private readonly _listingService: ListingService<File>,
|
|
||||||
readonly permissionsService: PermissionsService,
|
readonly permissionsService: PermissionsService,
|
||||||
|
private readonly _listingService: ListingService<File>,
|
||||||
|
readonly fileAttributesService: FileAttributesService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.isEditingFileAttribute$ = this._fileAttributesService.isEditingFileAttribute$;
|
const sub = router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => this.close());
|
||||||
|
this.#subscriptions.add(sub);
|
||||||
|
|
||||||
_router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => {
|
const sub2 = this._listingService.selectedLength$.pipe(
|
||||||
this.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.selectedFile$ = this._listingService.selectedLength$.pipe(
|
|
||||||
map(selectedLength => !!selectedLength),
|
map(selectedLength => !!selectedLength),
|
||||||
tap(() => this.close()),
|
tap(() => this.close()),
|
||||||
);
|
);
|
||||||
}
|
this.#subscriptions.add(sub2.subscribe());
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
if (this.#noFileAttributes) {
|
|
||||||
this.#initFileAttributes();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.form = this.#getForm();
|
|
||||||
this.initialFormValue = this.form.getRawValue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get isDate(): boolean {
|
get isDate(): boolean {
|
||||||
@ -67,41 +53,27 @@ export class FileAttributeComponent extends BaseFormComponent implements OnInit
|
|||||||
return this.file.fileAttributes.attributeIdToValue[this.fileAttribute.id];
|
return this.file.fileAttributes.attributeIdToValue[this.fileAttribute.id];
|
||||||
}
|
}
|
||||||
|
|
||||||
get #noFileAttributes(): boolean {
|
ngOnDestroy() {
|
||||||
return JSON.stringify(this.file.fileAttributes.attributeIdToValue) === '{}';
|
this.#subscriptions.unsubscribe();
|
||||||
}
|
|
||||||
|
|
||||||
#initFileAttributes() {
|
|
||||||
const configs = this._fileAttributesService.getFileAttributeConfig(this.file.dossierTemplateId).fileAttributeConfigs;
|
|
||||||
configs.forEach(config => (this.file.fileAttributes.attributeIdToValue[config.id] = null));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async editFileAttribute($event: MouseEvent): Promise<void> {
|
async editFileAttribute($event: MouseEvent): Promise<void> {
|
||||||
$event?.stopPropagation();
|
$event.stopPropagation();
|
||||||
this.#toggleEdit();
|
this.#toggleEdit();
|
||||||
}
|
}
|
||||||
|
|
||||||
#getForm(): UntypedFormGroup {
|
async save($event?: MouseEvent) {
|
||||||
const config = {};
|
|
||||||
const fileAttributes = this.file.fileAttributes.attributeIdToValue;
|
|
||||||
Object.keys(fileAttributes).forEach(key => {
|
|
||||||
const attrValue = fileAttributes[key];
|
|
||||||
config[key] = [dayjs(attrValue, 'YYYY-MM-DD', true).isValid() ? dayjs(attrValue).toDate() : attrValue];
|
|
||||||
});
|
|
||||||
return this._formBuilder.group(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
async save($event?: MouseEvent): Promise<void> {
|
|
||||||
$event?.stopPropagation();
|
$event?.stopPropagation();
|
||||||
|
|
||||||
const rawFormValue = this.form.getRawValue();
|
const rawFormValue = this.form.getRawValue();
|
||||||
const fileAttrValue = rawFormValue[this.fileAttribute.id];
|
const fileAttrValue = rawFormValue[this.fileAttribute.id];
|
||||||
const attributeIdToValue = {
|
const attributeIdToValue = {
|
||||||
...rawFormValue,
|
...this.#getForm().getRawValue(),
|
||||||
[this.fileAttribute.id]: this.#formatAttributeValue(fileAttrValue),
|
[this.fileAttribute.id]: this.#formatAttributeValue(fileAttrValue),
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
await firstValueFrom(
|
await firstValueFrom(
|
||||||
this._fileAttributesService.setFileAttributes({ attributeIdToValue }, this.file.dossierId, this.file.fileId),
|
this.fileAttributesService.setFileAttributes({ attributeIdToValue }, this.file.dossierId, this.file.fileId),
|
||||||
);
|
);
|
||||||
await firstValueFrom(this._filesService.reload(this.file.dossierId, this.file));
|
await firstValueFrom(this._filesService.reload(this.file.dossierId, this.file));
|
||||||
this.initialFormValue = rawFormValue;
|
this.initialFormValue = rawFormValue;
|
||||||
@ -115,19 +87,52 @@ export class FileAttributeComponent extends BaseFormComponent implements OnInit
|
|||||||
|
|
||||||
close($event?: MouseEvent): void {
|
close($event?: MouseEvent): void {
|
||||||
$event?.stopPropagation();
|
$event?.stopPropagation();
|
||||||
|
|
||||||
if (this.isInEditMode) {
|
if (this.isInEditMode) {
|
||||||
this.form = this.#getForm();
|
this.form = this.#getForm();
|
||||||
this.#toggleEdit();
|
this.#toggleEdit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@HostListener('document:click')
|
||||||
|
clickOutside() {
|
||||||
|
if (this.isInEditMode && this.closedDatepicker) {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#initFileAttributes() {
|
||||||
|
const configs = this.fileAttributesService.getFileAttributeConfig(this.file.dossierTemplateId).fileAttributeConfigs;
|
||||||
|
configs.forEach(config => {
|
||||||
|
if (!this.file.fileAttributes.attributeIdToValue[config.id]) {
|
||||||
|
this.file.fileAttributes.attributeIdToValue[config.id] = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#getForm(): UntypedFormGroup {
|
||||||
|
const config = {};
|
||||||
|
const fileAttributes = this.file.fileAttributes.attributeIdToValue;
|
||||||
|
Object.keys(fileAttributes).forEach(key => {
|
||||||
|
const attrValue = fileAttributes[key];
|
||||||
|
config[key] = [dayjs(attrValue, 'YYYY-MM-DD', true).isValid() ? dayjs(attrValue).toDate() : attrValue];
|
||||||
|
});
|
||||||
|
return this._formBuilder.group(config);
|
||||||
|
}
|
||||||
|
|
||||||
#formatAttributeValue(attrValue) {
|
#formatAttributeValue(attrValue) {
|
||||||
return this.isDate ? attrValue && dayjs(attrValue).format('YYYY-MM-DD') : attrValue;
|
return this.isDate ? attrValue && dayjs(attrValue).format('YYYY-MM-DD') : attrValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
#toggleEdit(): void {
|
#toggleEdit(): void {
|
||||||
|
if (!this.isInEditMode) {
|
||||||
|
this.#initFileAttributes();
|
||||||
|
this.form = this.#getForm();
|
||||||
|
this.initialFormValue = this.form.getRawValue();
|
||||||
|
}
|
||||||
|
|
||||||
this.isInEditMode = !this.isInEditMode;
|
this.isInEditMode = !this.isInEditMode;
|
||||||
this.isEditingFileAttribute$.next(this.isInEditMode);
|
this.fileAttributesService.isEditingFileAttribute$.next(this.isInEditMode);
|
||||||
|
|
||||||
if (this.isInEditMode) {
|
if (this.isInEditMode) {
|
||||||
this.#focusOnEditInput();
|
this.#focusOnEditInput();
|
||||||
@ -140,11 +145,4 @@ export class FileAttributeComponent extends BaseFormComponent implements OnInit
|
|||||||
input.focus();
|
input.focus();
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('document:click')
|
|
||||||
clickOutside() {
|
|
||||||
if (this.isInEditMode && this.closedDatepicker) {
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -153,7 +153,7 @@ export class ConfigService {
|
|||||||
checker: (dossier: Dossier, filter: INestedFilter) => this._dossierStatusChecker(dossier, filter),
|
checker: (dossier: Dossier, filter: INestedFilter) => this._dossierStatusChecker(dossier, filter),
|
||||||
});
|
});
|
||||||
|
|
||||||
const peopleFilters = [...allDistinctPeople].map(
|
const peopleFilters = this._sortByName([...allDistinctPeople]).map(
|
||||||
userId =>
|
userId =>
|
||||||
new NestedFilter({
|
new NestedFilter({
|
||||||
id: userId,
|
id: userId,
|
||||||
@ -265,4 +265,8 @@ export class ConfigService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private _sortByName(ids: string[]) {
|
||||||
|
return ids.sort((a, b) => this._userService.getName(a).localeCompare(this._userService.getName(b)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,9 @@
|
|||||||
{{ annotation.recategorizationType ?? annotation.entity.label }}
|
{{ annotation.recategorizationType ?? annotation.entity.label }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *deny="roles.getRss; if: !!annotation.shortContent && !annotation.isHint && !annotation.isSkipped">
|
<div
|
||||||
|
*deny="roles.getRss; if: !!annotation.shortContent && !annotation.isHint && !annotation.isSkipped && !annotation.isIgnoredHint"
|
||||||
|
>
|
||||||
<strong><span translate="content"></span>: </strong>{{ annotation.shortContent }}
|
<strong><span translate="content"></span>: </strong>{{ annotation.shortContent }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -28,6 +28,7 @@
|
|||||||
[overlappingElements]="['USER_MENU']"
|
[overlappingElements]="['USER_MENU']"
|
||||||
[primaryFiltersSlug]="'primaryFilters'"
|
[primaryFiltersSlug]="'primaryFilters'"
|
||||||
[secondaryFiltersSlug]="'secondaryFilters'"
|
[secondaryFiltersSlug]="'secondaryFilters'"
|
||||||
|
[fileId]="file.id"
|
||||||
></iqser-popup-filter>
|
></iqser-popup-filter>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ChangeDetectorRef, Component, ElementRef, HostListener, Input, OnDestroy, ViewChild } from '@angular/core';
|
import { ChangeDetectorRef, Component, ElementRef, HostListener, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||||
import { AnnotationProcessingService } from '../../services/annotation-processing.service';
|
import { AnnotationProcessingService } from '../../services/annotation-processing.service';
|
||||||
import { MatDialogState } from '@angular/material/dialog';
|
import { MatDialogState } from '@angular/material/dialog';
|
||||||
@ -31,6 +31,7 @@ import { REDAnnotationManager } from '../../../pdf-viewer/services/annotation-ma
|
|||||||
import { AnnotationsListingService } from '../../services/annotations-listing.service';
|
import { AnnotationsListingService } from '../../services/annotations-listing.service';
|
||||||
import { REDDocumentViewer } from '../../../pdf-viewer/services/document-viewer.service';
|
import { REDDocumentViewer } from '../../../pdf-viewer/services/document-viewer.service';
|
||||||
import { SuggestionsService } from '../../services/suggestions.service';
|
import { SuggestionsService } from '../../services/suggestions.service';
|
||||||
|
import { getLocalStorageDataByFileId } from '@utils/local-storage';
|
||||||
|
|
||||||
const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape'];
|
const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape'];
|
||||||
const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
|
const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
|
||||||
@ -40,7 +41,7 @@ const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
|
|||||||
templateUrl: './file-workload.component.html',
|
templateUrl: './file-workload.component.html',
|
||||||
styleUrls: ['./file-workload.component.scss'],
|
styleUrls: ['./file-workload.component.scss'],
|
||||||
})
|
})
|
||||||
export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy {
|
export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, OnDestroy {
|
||||||
readonly iconButtonTypes = IconButtonTypes;
|
readonly iconButtonTypes = IconButtonTypes;
|
||||||
readonly circleButtonTypes = CircleButtonTypes;
|
readonly circleButtonTypes = CircleButtonTypes;
|
||||||
|
|
||||||
@ -362,7 +363,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
|
|||||||
|
|
||||||
if (this._viewModeService.isRedacted) {
|
if (this._viewModeService.isRedacted) {
|
||||||
annotations = annotations.filter(a => !bool(a.isChangeLogRemoved));
|
annotations = annotations.filter(a => !bool(a.isChangeLogRemoved));
|
||||||
annotations = this._suggestionsService.convertWorkloadRemoveSuggestionsToRedactions(annotations);
|
annotations = this._suggestionsService.filterWorkloadSuggestionsInPreview(annotations);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.displayedAnnotations = this._annotationProcessingService.filterAndGroupAnnotations(annotations, primary, secondary);
|
this.displayedAnnotations = this._annotationProcessingService.filterAndGroupAnnotations(annotations, primary, secondary);
|
||||||
@ -456,4 +457,18 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
|
|||||||
FileWorkloadComponent._scrollToFirstElement(elements);
|
FileWorkloadComponent._scrollToFirstElement(elements);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
setTimeout(() => {
|
||||||
|
const showExcludePages = getLocalStorageDataByFileId(this.file.fileId, 'show-exclude-pages') ?? false;
|
||||||
|
if (showExcludePages) {
|
||||||
|
this.excludedPagesService.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
const showDocumentInfo = getLocalStorageDataByFileId(this.file.fileId, 'show-document-info') ?? false;
|
||||||
|
if (showDocumentInfo) {
|
||||||
|
this.documentInfoService.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { Dossier, File, StatusBarConfigs, User } from '@red/domain';
|
import { Dossier, File, StatusBarConfigs, User } from '@red/domain';
|
||||||
import { List, LoadingService, Toaster } from '@iqser/common-ui';
|
import { getCurrentUser, List, LoadingService, Toaster } from '@iqser/common-ui';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import { workflowFileStatusTranslations } from '@translations/file-status-translations';
|
import { workflowFileStatusTranslations } from '@translations/file-status-translations';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
@ -32,6 +32,7 @@ export class UserManagementComponent {
|
|||||||
private readonly _dossier$: Observable<Dossier>;
|
private readonly _dossier$: Observable<Dossier>;
|
||||||
private readonly _canAssignUser$: Observable<boolean>;
|
private readonly _canAssignUser$: Observable<boolean>;
|
||||||
private readonly _canUnassignUser$: Observable<boolean>;
|
private readonly _canUnassignUser$: Observable<boolean>;
|
||||||
|
readonly currentUserId = getCurrentUser().id;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly fileAssignService: FileAssignService,
|
readonly fileAssignService: FileAssignService,
|
||||||
@ -89,10 +90,10 @@ export class UserManagementComponent {
|
|||||||
|
|
||||||
this.usersOptions$ = combineLatest([this._canUnassignUser$, this.stateService.file$, this._dossier$]).pipe(
|
this.usersOptions$ = combineLatest([this._canUnassignUser$, this.stateService.file$, this._dossier$]).pipe(
|
||||||
map(([canUnassignUser, file, dossier]) => {
|
map(([canUnassignUser, file, dossier]) => {
|
||||||
const unassignUser = canUnassignUser ? [undefined] : [];
|
const unassignUser = canUnassignUser && file.assignee ? [undefined] : [];
|
||||||
return file.isUnderApproval
|
return file.isUnderApproval
|
||||||
? this._customSort([...dossier.approverIds, ...unassignUser], file)
|
? this.#customSort([...dossier.approverIds, ...unassignUser])
|
||||||
: this._customSort([...dossier.memberIds, ...unassignUser], file);
|
: this.#customSort([...dossier.memberIds, ...unassignUser]);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -117,12 +118,12 @@ export class UserManagementComponent {
|
|||||||
this.editingReviewer = false;
|
this.editingReviewer = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _customSort(ids: string[], file: File) {
|
#customSort(ids: string[]) {
|
||||||
let sorted = [...ids].sort((a, b) => this.userService.getName(a).localeCompare(this.userService.getName(b)));
|
let sorted = [...ids].sort((a, b) => this.userService.getName(a).localeCompare(this.userService.getName(b)));
|
||||||
if (file.assignee) {
|
sorted = moveElementInArray(sorted, this.currentUserId, 0);
|
||||||
sorted = moveElementInArray(sorted, file.assignee, 0);
|
if (sorted.includes(undefined)) {
|
||||||
|
sorted = moveElementInArray(sorted, undefined, 1);
|
||||||
}
|
}
|
||||||
sorted = moveElementInArray(sorted, undefined, file.assignee ? 1 : 0);
|
|
||||||
return sorted;
|
return sorted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<form (submit)="save()" [formGroup]="form">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<div class="dialog-header heading-l" translate="change-legal-basis-dialog.header"></div>
|
<div class="dialog-header heading-l" [translate]="'change-legal-basis-dialog.header'"></div>
|
||||||
|
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
<div class="iqser-input-group required w-400">
|
<div class="iqser-input-group required w-400">
|
||||||
<label translate="change-legal-basis-dialog.content.reason"></label>
|
<label [translate]="'change-legal-basis-dialog.content.reason'"></label>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-select
|
<mat-select
|
||||||
[placeholder]="'change-legal-basis-dialog.content.reason-placeholder' | translate"
|
[placeholder]="'change-legal-basis-dialog.content.reason-placeholder' | translate"
|
||||||
@ -19,22 +19,22 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="iqser-input-group w-400">
|
<div class="iqser-input-group w-400">
|
||||||
<label translate="change-legal-basis-dialog.content.legalBasis"></label>
|
<label [translate]="'change-legal-basis-dialog.content.legalBasis'"></label>
|
||||||
<input [value]="form.get('reason').value?.legalBasis" disabled type="text" />
|
<input [value]="form.get('reason').value?.legalBasis" disabled type="text" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="iqser-input-group w-400">
|
<div class="iqser-input-group w-400">
|
||||||
<label translate="change-legal-basis-dialog.content.section"></label>
|
<label [translate]="'change-legal-basis-dialog.content.section'"></label>
|
||||||
<input formControlName="section" name="section" type="text" />
|
<input formControlName="section" name="section" type="text" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="this.allRectangles" class="iqser-input-group w-400">
|
<div *ngIf="this.allRectangles" class="iqser-input-group w-400">
|
||||||
<label translate="change-legal-basis-dialog.content.classification"></label>
|
<label [translate]="'change-legal-basis-dialog.content.classification'"></label>
|
||||||
<input formControlName="classification" name="classification" type="text" />
|
<input formControlName="classification" name="classification" type="text" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="iqser-input-group w-300">
|
<div class="iqser-input-group w-300">
|
||||||
<label translate="change-legal-basis-dialog.content.comment"></label>
|
<label [translate]="'change-legal-basis-dialog.content.comment'"></label>
|
||||||
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
|
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<section *ngIf="!!form" class="dialog">
|
<section *ngIf="!!form" class="dialog">
|
||||||
<div class="dialog-header heading-l" translate="document-info.title"></div>
|
<div class="dialog-header heading-l" [translate]="'document-info.title'"></div>
|
||||||
|
|
||||||
<form (submit)="save()" [formGroup]="form">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
|
|||||||
@ -0,0 +1,31 @@
|
|||||||
|
<section class="dialog">
|
||||||
|
<form (submit)="save()" [formGroup]="form">
|
||||||
|
<div class="dialog-header heading-l" [translate]="'false-positive-dialog.header'"></div>
|
||||||
|
|
||||||
|
<div class="dialog-content">
|
||||||
|
<ul>
|
||||||
|
<li
|
||||||
|
*ngFor="let value of data"
|
||||||
|
[innerHTML]="'false-positive-dialog.content.body-text' | translate : { value: value.text, context: value.context }"
|
||||||
|
></li>
|
||||||
|
</ul>
|
||||||
|
<div class="iqser-input-group w-300">
|
||||||
|
<label [translate]="'false-positive-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]="!form.valid"
|
||||||
|
[label]="'false-positive-dialog.actions.save' | translate"
|
||||||
|
[submit]="true"
|
||||||
|
[type]="iconButtonTypes.primary"
|
||||||
|
></iqser-icon-button>
|
||||||
|
|
||||||
|
<div class="all-caps-label cancel" mat-dialog-close translate="false-positive-dialog.actions.cancel"></div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||||
|
</section>
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
import { Component, Inject, OnInit } from '@angular/core';
|
||||||
|
import { BaseDialogComponent } from '@iqser/common-ui';
|
||||||
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
|
|
||||||
|
export interface FalsePositiveDialogInput {
|
||||||
|
text: string;
|
||||||
|
context: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: './false-positive-dialog.component.html',
|
||||||
|
})
|
||||||
|
export class FalsePositiveDialogComponent extends BaseDialogComponent implements OnInit {
|
||||||
|
constructor(
|
||||||
|
protected readonly _dialogRef: MatDialogRef<FalsePositiveDialogComponent>,
|
||||||
|
@Inject(MAT_DIALOG_DATA) readonly data: FalsePositiveDialogInput[],
|
||||||
|
) {
|
||||||
|
super(_dialogRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
const controlsConfig = { comment: [null] };
|
||||||
|
this.form = this._formBuilder.group(controlsConfig);
|
||||||
|
this.initialFormValue = this.form.getRawValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
save(): void {
|
||||||
|
this._dialogRef.close(this.form.getRawValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,11 +1,11 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<form (submit)="save()" [formGroup]="form">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<div *ngIf="!isHintDialog" class="dialog-header heading-l" translate="manual-annotation.dialog.header.force-redaction"></div>
|
<div *ngIf="!isHintDialog" class="dialog-header heading-l" [translate]="'manual-annotation.dialog.header.force-redaction'"></div>
|
||||||
<div *ngIf="isHintDialog" class="dialog-header heading-l" translate="manual-annotation.dialog.header.force-hint"></div>
|
<div *ngIf="isHintDialog" class="dialog-header heading-l" [translate]="'manual-annotation.dialog.header.force-hint'"></div>
|
||||||
|
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
<div *ngIf="!isHintDialog" class="iqser-input-group required w-400">
|
<div *ngIf="!isHintDialog" class="iqser-input-group required w-400">
|
||||||
<label translate="manual-annotation.dialog.content.reason"></label>
|
<label [translate]="'manual-annotation.dialog.content.reason'"></label>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-select
|
<mat-select
|
||||||
[placeholder]="'manual-annotation.dialog.content.reason-placeholder' | translate"
|
[placeholder]="'manual-annotation.dialog.content.reason-placeholder' | translate"
|
||||||
@ -20,12 +20,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="!isHintDialog" class="iqser-input-group w-400">
|
<div *ngIf="!isHintDialog" class="iqser-input-group w-400">
|
||||||
<label translate="manual-annotation.dialog.content.legalBasis"></label>
|
<label [translate]="'manual-annotation.dialog.content.legalBasis'"></label>
|
||||||
<input [value]="form.get('reason').value?.legalBasis" disabled type="text" />
|
<input [value]="form.get('reason').value?.legalBasis" disabled type="text" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="iqser-input-group w-300">
|
<div class="iqser-input-group w-300">
|
||||||
<label translate="manual-annotation.dialog.content.comment"></label>
|
<label [translate]="'manual-annotation.dialog.content.comment'"></label>
|
||||||
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4"></textarea>
|
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<div class="dialog-header heading-l" translate="import-redactions-dialog.title"></div>
|
<div class="dialog-header heading-l" [translate]="'import-redactions-dialog.title'"></div>
|
||||||
|
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
<div class="mb-24" translate="import-redactions-dialog.details"></div>
|
<div class="mb-24" [translate]="'import-redactions-dialog.details'"></div>
|
||||||
<iqser-upload-file (fileChanged)="fileChanged($event)"></iqser-upload-file>
|
<iqser-upload-file (fileChanged)="fileChanged($event)"></iqser-upload-file>
|
||||||
|
|
||||||
<div class="only-for-pages">
|
<div class="only-for-pages">
|
||||||
@ -29,7 +29,7 @@
|
|||||||
[type]="iconButtonTypes.primary"
|
[type]="iconButtonTypes.primary"
|
||||||
></iqser-icon-button>
|
></iqser-icon-button>
|
||||||
|
|
||||||
<div class="all-caps-label cancel" mat-dialog-close translate="import-redactions-dialog.actions.cancel"></div>
|
<div class="all-caps-label cancel" mat-dialog-close [translate]="'import-redactions-dialog.actions.cancel'"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
<div *ngIf="!isRectangle" class="iqser-input-group w-450">
|
<div *ngIf="!isRectangle" class="iqser-input-group w-450">
|
||||||
<label translate="manual-annotation.dialog.content.text"></label>
|
<label [translate]="'manual-annotation.dialog.content.text'"></label>
|
||||||
<div *ngIf="!isEditingSelectedText" class="flex-align-items-center">
|
<div *ngIf="!isEditingSelectedText" class="flex-align-items-center">
|
||||||
{{ form.get('selectedText').value }}
|
{{ form.get('selectedText').value }}
|
||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
@ -28,15 +28,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="isRectangle" class="iqser-input-group">
|
<div *ngIf="isRectangle" class="iqser-input-group">
|
||||||
<label translate="manual-annotation.dialog.content.rectangle"></label>
|
<label [translate]="'manual-annotation.dialog.content.rectangle'"></label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
*ngIf="!isFalsePositiveRequest && (isDictionaryRequest || !manualRedactionTypeExists)"
|
*ngIf="!isFalsePositiveRequest && (isDictionaryRequest || !manualRedactionTypeExists)"
|
||||||
class="iqser-input-group required w-450"
|
class="iqser-input-group required w-450"
|
||||||
>
|
>
|
||||||
<label *ngIf="isDictionaryRequest" translate="manual-annotation.dialog.content.dictionary"></label>
|
<label *ngIf="isDictionaryRequest" [translate]="'manual-annotation.dialog.content.dictionary'"></label>
|
||||||
<label *ngIf="!isDictionaryRequest" translate="manual-annotation.dialog.content.type"></label>
|
<label *ngIf="!isDictionaryRequest" [translate]="'manual-annotation.dialog.content.type'"></label>
|
||||||
|
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-select formControlName="dictionary">
|
<mat-select formControlName="dictionary">
|
||||||
@ -54,7 +54,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *deny="roles.getRss; if: !isDictionaryRequest" class="iqser-input-group required w-450">
|
<div *deny="roles.getRss; if: !isDictionaryRequest" class="iqser-input-group required w-450">
|
||||||
<label translate="manual-annotation.dialog.content.reason"></label>
|
<label [translate]="'manual-annotation.dialog.content.reason'"></label>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-select
|
<mat-select
|
||||||
[placeholder]="'manual-annotation.dialog.content.reason-placeholder' | translate"
|
[placeholder]="'manual-annotation.dialog.content.reason-placeholder' | translate"
|
||||||
@ -74,22 +74,22 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *deny="roles.getRss; if: !isDictionaryRequest" class="iqser-input-group w-450">
|
<div *deny="roles.getRss; if: !isDictionaryRequest" class="iqser-input-group w-450">
|
||||||
<label translate="manual-annotation.dialog.content.legalBasis"></label>
|
<label [translate]="'manual-annotation.dialog.content.legalBasis'"></label>
|
||||||
<input [value]="form.get('reason').value?.legalBasis" disabled type="text" />
|
<input [value]="form.get('reason').value?.legalBasis" disabled type="text" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="isRectangle" class="iqser-input-group w-450">
|
<div *ngIf="isRectangle" class="iqser-input-group w-450">
|
||||||
<label translate="manual-annotation.dialog.content.section"></label>
|
<label [translate]="'manual-annotation.dialog.content.section'"></label>
|
||||||
<input formControlName="section" name="section" type="text" />
|
<input formControlName="section" name="section" type="text" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="isRectangle" class="iqser-input-group w-450">
|
<div *ngIf="isRectangle" class="iqser-input-group w-450">
|
||||||
<label translate="manual-annotation.dialog.content.classification"></label>
|
<label [translate]="'manual-annotation.dialog.content.classification'"></label>
|
||||||
<input formControlName="classification" name="classification" type="text" />
|
<input formControlName="classification" name="classification" type="text" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="iqser-input-group w-450">
|
<div class="iqser-input-group w-450">
|
||||||
<label translate="manual-annotation.dialog.content.comment"></label>
|
<label [translate]="'manual-annotation.dialog.content.comment'"></label>
|
||||||
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
|
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<form (submit)="save()" [formGroup]="form">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<div class="dialog-header heading-l" translate="recategorize-image-dialog.header"></div>
|
<div class="dialog-header heading-l" [translate]="'recategorize-image-dialog.header'"></div>
|
||||||
|
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
<div class="iqser-input-group required w-400">
|
<div class="iqser-input-group required w-400">
|
||||||
<label translate="recategorize-image-dialog.content.type"></label>
|
<label [translate]="'recategorize-image-dialog.content.type'"></label>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-select
|
<mat-select
|
||||||
[placeholder]="'recategorize-image-dialog.content.type-placeholder' | translate"
|
[placeholder]="'recategorize-image-dialog.content.type-placeholder' | translate"
|
||||||
@ -19,7 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="iqser-input-group w-300">
|
<div class="iqser-input-group w-300">
|
||||||
<label translate="recategorize-image-dialog.content.comment"></label>
|
<label [translate]="'recategorize-image-dialog.content.comment'"></label>
|
||||||
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
|
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -32,7 +32,7 @@
|
|||||||
[type]="iconButtonTypes.primary"
|
[type]="iconButtonTypes.primary"
|
||||||
></iqser-icon-button>
|
></iqser-icon-button>
|
||||||
|
|
||||||
<div class="all-caps-label cancel" mat-dialog-close translate="recategorize-image-dialog.actions.cancel"></div>
|
<div class="all-caps-label cancel" mat-dialog-close [translate]="'recategorize-image-dialog.actions.cancel'"></div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
@ -1,21 +1,23 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<div class="dialog-header heading-l">
|
<div
|
||||||
{{
|
class="dialog-header heading-l"
|
||||||
|
[innerHTML]="
|
||||||
(data.removeFromDictionary
|
(data.removeFromDictionary
|
||||||
? 'remove-annotations-dialog.remove-from-dictionary.title'
|
? 'remove-annotations-dialog.remove-from-dictionary.title'
|
||||||
: 'remove-annotations-dialog.remove-only-here.title'
|
: 'remove-annotations-dialog.remove-only-here.title'
|
||||||
) | translate : { hint: data.hint }
|
) | translate : { hint: data.hint }
|
||||||
}}
|
"
|
||||||
</div>
|
></div>
|
||||||
<form (submit)="save()" [formGroup]="form">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<div class="dialog-content">
|
<div
|
||||||
{{
|
class="dialog-content"
|
||||||
|
[innerHTML]="
|
||||||
(data.removeFromDictionary
|
(data.removeFromDictionary
|
||||||
? 'remove-annotations-dialog.remove-from-dictionary.question'
|
? 'remove-annotations-dialog.remove-from-dictionary.question'
|
||||||
: 'remove-annotations-dialog.remove-only-here.question'
|
: 'remove-annotations-dialog.remove-only-here.question'
|
||||||
) | translate : { hint: data.hint }
|
) | translate : { hint: data.hint }
|
||||||
}}
|
"
|
||||||
|
>
|
||||||
<div *ngIf="data.removeFromDictionary" class="content-wrapper">
|
<div *ngIf="data.removeFromDictionary" class="content-wrapper">
|
||||||
<table class="default-table">
|
<table class="default-table">
|
||||||
<thead>
|
<thead>
|
||||||
@ -40,7 +42,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="iqser-input-group w-300">
|
<div class="iqser-input-group w-300">
|
||||||
<label translate="manual-annotation.dialog.content.comment"></label>
|
<label [translate]="'manual-annotation.dialog.content.comment'"></label>
|
||||||
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
|
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -53,7 +55,7 @@
|
|||||||
[type]="iconButtonTypes.primary"
|
[type]="iconButtonTypes.primary"
|
||||||
></iqser-icon-button>
|
></iqser-icon-button>
|
||||||
|
|
||||||
<div class="all-caps-label cancel" mat-dialog-close translate="remove-annotations-dialog.cancel"></div>
|
<div class="all-caps-label cancel" mat-dialog-close [translate]="'remove-annotations-dialog.cancel'"></div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<form (submit)="save()" [formGroup]="form">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<div class="dialog-header heading-l" translate="resize-annotation-dialog.header"></div>
|
<div [translate]="'resize-annotation-dialog.header'" class="dialog-header heading-l"></div>
|
||||||
|
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
<div class="iqser-input-group w-300">
|
<div class="iqser-input-group w-300">
|
||||||
<label translate="resize-annotation-dialog.content.comment"></label>
|
<label [translate]="'resize-annotation-dialog.content.comment'"></label>
|
||||||
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
|
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="form.get('updateDictionary')" class="iqser-input-group">
|
<div *ngIf="form.get('updateDictionary')" class="iqser-input-group">
|
||||||
<mat-checkbox color="primary" formControlName="updateDictionary">
|
<mat-checkbox color="primary" formControlName="updateDictionary"
|
||||||
{{ 'resize-annotation-dialog.content.update-dictionary' | translate : { text: this.text } }}
|
><span [innerHTML]="'resize-annotation-dialog.content.update-dictionary' | translate : { text: this.text }"></span>
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -23,7 +23,7 @@
|
|||||||
[type]="iconButtonTypes.primary"
|
[type]="iconButtonTypes.primary"
|
||||||
></iqser-icon-button>
|
></iqser-icon-button>
|
||||||
|
|
||||||
<div class="all-caps-label cancel" mat-dialog-close translate="resize-annotation-dialog.actions.cancel"></div>
|
<div [translate]="'resize-annotation-dialog.actions.cancel'" class="all-caps-label cancel" mat-dialog-close></div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<div class="dialog-header heading-l" translate="rss-dialog.title"></div>
|
<div class="dialog-header heading-l" [translate]="'rss-dialog.title'"></div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
@ -79,7 +79,7 @@
|
|||||||
label="Export All"
|
label="Export All"
|
||||||
></iqser-icon-button>
|
></iqser-icon-button>
|
||||||
|
|
||||||
<div class="all-caps-label cancel" mat-dialog-close translate="rss-dialog.actions.close"></div>
|
<div class="all-caps-label cancel" mat-dialog-close [translate]="'rss-dialog.actions.close'"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
CircleButtonTypes,
|
CircleButtonTypes,
|
||||||
ConfirmationDialogInput,
|
ConfirmationDialogInput,
|
||||||
ConfirmOptions,
|
ConfirmOptions,
|
||||||
|
copyLocalStorageFiltersValues,
|
||||||
CustomError,
|
CustomError,
|
||||||
Debounce,
|
Debounce,
|
||||||
ErrorService,
|
ErrorService,
|
||||||
@ -37,7 +38,7 @@ import { AnnotationDrawService } from '../pdf-viewer/services/annotation-draw.se
|
|||||||
import { AnnotationProcessingService } from './services/annotation-processing.service';
|
import { AnnotationProcessingService } from './services/annotation-processing.service';
|
||||||
import { Dictionary, File, ViewModes } from '@red/domain';
|
import { Dictionary, File, ViewModes } from '@red/domain';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import { combineLatest, firstValueFrom, from, Observable, of, pairwise } from 'rxjs';
|
import { combineLatest, first, firstValueFrom, from, Observable, of, pairwise } from 'rxjs';
|
||||||
import { PreferencesKeys, UserPreferenceService } from '@users/user-preference.service';
|
import { PreferencesKeys, UserPreferenceService } from '@users/user-preference.service';
|
||||||
import { byId, byPage, download, handleFilterDelta, hasChanges } from '../../utils';
|
import { byId, byPage, download, handleFilterDelta, hasChanges } from '../../utils';
|
||||||
import { FilesService } from '@services/files/files.service';
|
import { FilesService } from '@services/files/files.service';
|
||||||
@ -216,7 +217,6 @@ export class FilePreviewScreenComponent
|
|||||||
|
|
||||||
switch (this._viewModeService.viewMode) {
|
switch (this._viewModeService.viewMode) {
|
||||||
case ViewModes.STANDARD: {
|
case ViewModes.STANDARD: {
|
||||||
this._readableRedactionsService.setAnnotationsColor(redactions, 'annotationColor');
|
|
||||||
const wrappers = await this._fileDataService.annotations;
|
const wrappers = await this._fileDataService.annotations;
|
||||||
const ocrAnnotationIds = wrappers.filter(a => a.isOCR).map(a => a.id);
|
const ocrAnnotationIds = wrappers.filter(a => a.isOCR).map(a => a.id);
|
||||||
const standardEntries = annotations
|
const standardEntries = annotations
|
||||||
@ -225,6 +225,7 @@ export class FilePreviewScreenComponent
|
|||||||
const nonStandardEntries = annotations.filter(
|
const nonStandardEntries = annotations.filter(
|
||||||
a => bool(a.getCustomData('changeLogRemoved')) || this._annotationManager.isHidden(a.Id),
|
a => bool(a.getCustomData('changeLogRemoved')) || this._annotationManager.isHidden(a.Id),
|
||||||
);
|
);
|
||||||
|
this._readableRedactionsService.setAnnotationsColor(standardEntries, 'annotationColor');
|
||||||
this._readableRedactionsService.setAnnotationsOpacity(standardEntries, true);
|
this._readableRedactionsService.setAnnotationsOpacity(standardEntries, true);
|
||||||
this._annotationManager.show(standardEntries);
|
this._annotationManager.show(standardEntries);
|
||||||
this._annotationManager.hide(nonStandardEntries);
|
this._annotationManager.hide(nonStandardEntries);
|
||||||
@ -243,8 +244,8 @@ export class FilePreviewScreenComponent
|
|||||||
const nonRedactionEntries = annotations.filter(
|
const nonRedactionEntries = annotations.filter(
|
||||||
a => !bool(a.getCustomData('redaction')) || bool(a.getCustomData('changeLogRemoved')),
|
a => !bool(a.getCustomData('redaction')) || bool(a.getCustomData('changeLogRemoved')),
|
||||||
);
|
);
|
||||||
this._readableRedactionsService.setPreviewAnnotationsOpacity(redactions);
|
this._readableRedactionsService.setAnnotationsColor(redactions, 'redactionColor');
|
||||||
this._readableRedactionsService.setPreviewAnnotationsColor(redactions);
|
this._readableRedactionsService.setAnnotationsOpacity(redactions);
|
||||||
this._annotationManager.show(redactions);
|
this._annotationManager.show(redactions);
|
||||||
this._annotationManager.hide(nonRedactionEntries);
|
this._annotationManager.hide(nonRedactionEntries);
|
||||||
this._suggestionsService.hideSuggestionsInPreview(redactions);
|
this._suggestionsService.hideSuggestionsInPreview(redactions);
|
||||||
@ -262,7 +263,7 @@ export class FilePreviewScreenComponent
|
|||||||
|
|
||||||
ngOnDetach() {
|
ngOnDetach() {
|
||||||
this._viewerHeaderService.resetCompareButtons();
|
this._viewerHeaderService.resetCompareButtons();
|
||||||
this._viewerHeaderService.enableLoadAllAnnotations();
|
this._viewerHeaderService.enableLoadAllAnnotations(); // Reset the button state (since the viewer is reused between files)
|
||||||
super.ngOnDetach();
|
super.ngOnDetach();
|
||||||
this._changeRef.markForCheck();
|
this._changeRef.markForCheck();
|
||||||
}
|
}
|
||||||
@ -299,6 +300,8 @@ export class FilePreviewScreenComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.pdfProxyService.configureElements();
|
this.pdfProxyService.configureElements();
|
||||||
|
|
||||||
|
this.#restoreOldFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
@ -691,12 +694,19 @@ export class FilePreviewScreenComponent
|
|||||||
.subscribe();
|
.subscribe();
|
||||||
|
|
||||||
this.addActiveScreenSubscription = this._readableRedactionsService.active$.pipe(switchMap(() => this.updateViewMode())).subscribe();
|
this.addActiveScreenSubscription = this._readableRedactionsService.active$.pipe(switchMap(() => this.updateViewMode())).subscribe();
|
||||||
this.addActiveScreenSubscription = this._viewModeService.viewMode$
|
|
||||||
|
this.addActiveScreenSubscription = combineLatest([this._viewModeService.viewMode$, this.state.file$, this._documentViewer.loaded$])
|
||||||
.pipe(
|
.pipe(
|
||||||
tap(viewMode =>
|
map(([viewMode, file]) => {
|
||||||
viewMode === 'STANDARD' || viewMode === 'TEXT_HIGHLIGHTS'
|
if (viewMode === 'REDACTED' && !this._readableRedactionsService.active) {
|
||||||
? this._viewerHeaderService.enableRotationButtons()
|
this._readableRedactionsService.setCustomDrawHandler();
|
||||||
: this._viewerHeaderService.disableRotationButtons(),
|
} else {
|
||||||
|
this._readableRedactionsService.restoreDraw();
|
||||||
|
}
|
||||||
|
return ['STANDARD', 'TEXT_HIGHLIGHTS'].includes(viewMode) && this.permissionsService.canRotatePage(file);
|
||||||
|
}),
|
||||||
|
tap(canRotate =>
|
||||||
|
canRotate ? this._viewerHeaderService.enableRotationButtons() : this._viewerHeaderService.disableRotationButtons(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.subscribe();
|
.subscribe();
|
||||||
@ -781,4 +791,18 @@ export class FilePreviewScreenComponent
|
|||||||
private _isJapaneseString(text: string) {
|
private _isJapaneseString(text: string) {
|
||||||
return text.match(/[\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf\u3400-\u4dbf]/);
|
return text.match(/[\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf\u3400-\u4dbf]/);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#restoreOldFilters() {
|
||||||
|
combineLatest([
|
||||||
|
this._filterService.getGroup$('primaryFilters').pipe(first(filterGroup => filterGroup !== undefined)),
|
||||||
|
this._filterService.getGroup$('secondaryFilters').pipe(first(secondaryFilters => secondaryFilters !== undefined)),
|
||||||
|
]).subscribe(([primaryFilters, secondaryFilters]) => {
|
||||||
|
const localStorageFiltersString = localStorage.getItem('workload-filters') ?? '{}';
|
||||||
|
const localStorageFilters = JSON.parse(localStorageFiltersString)[this.fileId];
|
||||||
|
if (localStorageFilters) {
|
||||||
|
copyLocalStorageFiltersValues(primaryFilters.filters, localStorageFilters.primaryFilters);
|
||||||
|
copyLocalStorageFiltersValues(secondaryFilters.filters, localStorageFilters.secondaryFilters);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,13 +55,13 @@ import { RssDialogComponent } from './dialogs/rss-dialog/rss-dialog.component';
|
|||||||
import { ReadonlyBannerComponent } from './components/readonly-banner/readonly-banner.component';
|
import { ReadonlyBannerComponent } from './components/readonly-banner/readonly-banner.component';
|
||||||
import { SuggestionsService } from './services/suggestions.service';
|
import { SuggestionsService } from './services/suggestions.service';
|
||||||
import { PagesComponent } from './components/pages/pages.component';
|
import { PagesComponent } from './components/pages/pages.component';
|
||||||
|
import { FalsePositiveDialogComponent } from './dialogs/false-positive-dialog/false-positive-dialog.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: FilePreviewScreenComponent,
|
component: FilePreviewScreenComponent,
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
data: { reuse: true },
|
|
||||||
canDeactivate: [PendingChangesGuard, DocumentUnloadedGuard],
|
canDeactivate: [PendingChangesGuard, DocumentUnloadedGuard],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -78,6 +78,7 @@ const dialogs = [
|
|||||||
DocumentInfoDialogComponent,
|
DocumentInfoDialogComponent,
|
||||||
ImportRedactionsDialogComponent,
|
ImportRedactionsDialogComponent,
|
||||||
RssDialogComponent,
|
RssDialogComponent,
|
||||||
|
FalsePositiveDialogComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
const components = [
|
const components = [
|
||||||
|
|||||||
@ -251,21 +251,24 @@ export class AnnotationActionsService {
|
|||||||
|
|
||||||
markAsFalsePositive($event: MouseEvent, annotations: AnnotationWrapper[]) {
|
markAsFalsePositive($event: MouseEvent, annotations: AnnotationWrapper[]) {
|
||||||
$event?.stopPropagation();
|
$event?.stopPropagation();
|
||||||
|
const data = annotations.map(annotation => ({ text: annotation.value, context: this._getFalsePositiveText(annotation) }));
|
||||||
|
this._dialogService.openDialog('falsePositive', null, data, (result: { comment: string }) => {
|
||||||
|
const requests: List<IAddRedactionRequest> = annotations.map(annotation => ({
|
||||||
|
sourceId: annotation.id,
|
||||||
|
value: this._getFalsePositiveText(annotation),
|
||||||
|
type: annotation.type,
|
||||||
|
positions: annotation.positions,
|
||||||
|
addToDictionary: true,
|
||||||
|
reason: 'False Positive',
|
||||||
|
dictionaryEntryType: annotation.isRecommendation
|
||||||
|
? DictionaryEntryTypes.FALSE_RECOMMENDATION
|
||||||
|
: DictionaryEntryTypes.FALSE_POSITIVE,
|
||||||
|
comment: result.comment ? { text: result.comment } : null,
|
||||||
|
}));
|
||||||
|
const { dossierId, fileId } = this._state;
|
||||||
|
|
||||||
const requests: List<IAddRedactionRequest> = annotations.map(annotation => ({
|
this.#processObsAndEmit(this._manualRedactionService.addAnnotation(requests, dossierId, fileId));
|
||||||
sourceId: annotation.id,
|
});
|
||||||
value: this._getFalsePositiveText(annotation),
|
|
||||||
type: annotation.type,
|
|
||||||
positions: annotation.positions,
|
|
||||||
addToDictionary: true,
|
|
||||||
reason: 'False Positive',
|
|
||||||
dictionaryEntryType: annotation.isRecommendation
|
|
||||||
? DictionaryEntryTypes.FALSE_RECOMMENDATION
|
|
||||||
: DictionaryEntryTypes.FALSE_POSITIVE,
|
|
||||||
}));
|
|
||||||
const { dossierId, fileId } = this._state;
|
|
||||||
|
|
||||||
this.#processObsAndEmit(this._manualRedactionService.addAnnotation(requests, dossierId, fileId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#generateRectangle(annotationWrapper: AnnotationWrapper) {
|
#generateRectangle(annotationWrapper: AnnotationWrapper) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { BehaviorSubject, merge, Observable } from 'rxjs';
|
import { BehaviorSubject, firstValueFrom, merge, Observable } from 'rxjs';
|
||||||
import { shareLast } from '@iqser/common-ui';
|
import { shareLast } from '@iqser/common-ui';
|
||||||
import { map, startWith, tap, withLatestFrom } from 'rxjs/operators';
|
import { map, startWith, tap, withLatestFrom } from 'rxjs/operators';
|
||||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||||
@ -49,6 +49,10 @@ export class DocumentInfoService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shown() {
|
||||||
|
return firstValueFrom(this.shown$);
|
||||||
|
}
|
||||||
|
|
||||||
show() {
|
show() {
|
||||||
this._show$.next(true);
|
this._show$.next(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
import { BehaviorSubject, firstValueFrom, Observable } from 'rxjs';
|
||||||
import { shareDistinctLast } from '@iqser/common-ui';
|
import { shareDistinctLast } from '@iqser/common-ui';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
@ -14,6 +14,10 @@ export class ExcludedPagesService {
|
|||||||
this.hidden$ = this.shown$.pipe(map(value => !value));
|
this.hidden$ = this.shown$.pipe(map(value => !value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shown() {
|
||||||
|
return firstValueFrom(this.shown$);
|
||||||
|
}
|
||||||
|
|
||||||
show() {
|
show() {
|
||||||
this._show$.next(true);
|
this._show$.next(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,11 +34,11 @@ import { ViewedPagesMapService } from '@services/files/viewed-pages-map.service'
|
|||||||
|
|
||||||
const DELTA_VIEW_TIME = 10 * 60 * 1000; // 10 minutes;
|
const DELTA_VIEW_TIME = 10 * 60 * 1000; // 10 minutes;
|
||||||
|
|
||||||
function timestampOf(value: string) {
|
export function timestampOf(value: string) {
|
||||||
return dayjs(value).valueOf();
|
return dayjs(value).valueOf();
|
||||||
}
|
}
|
||||||
|
|
||||||
function chronologicallyBy<T>(property: (x: T) => string) {
|
export function chronologicallyBy<T>(property: (x: T) => string) {
|
||||||
return (a: T, b: T) => timestampOf(property(a)) - timestampOf(property(b));
|
return (a: T, b: T) => timestampOf(property(a)) - timestampOf(property(b));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,14 +203,20 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
|
|||||||
const redactionLogCopy = JSON.parse(JSON.stringify(redactionLog));
|
const redactionLogCopy = JSON.parse(JSON.stringify(redactionLog));
|
||||||
redactionLogCopy.redactionLogEntry = redactionLogCopy.redactionLogEntry?.reduce((filtered, entry) => {
|
redactionLogCopy.redactionLogEntry = redactionLogCopy.redactionLogEntry?.reduce((filtered, entry) => {
|
||||||
const lastChange = entry.manualChanges.at(-1);
|
const lastChange = entry.manualChanges.at(-1);
|
||||||
if (lastChange?.annotationStatus === LogEntryStatuses.REQUESTED && !entry.hint) {
|
|
||||||
|
if (
|
||||||
|
lastChange?.annotationStatus === LogEntryStatuses.REQUESTED &&
|
||||||
|
!entry.hint &&
|
||||||
|
!entry.reason.includes('requested to force hint')
|
||||||
|
) {
|
||||||
entry.manualChanges.pop();
|
entry.manualChanges.pop();
|
||||||
entry.reason = null;
|
entry.reason = null;
|
||||||
filtered.push(entry);
|
filtered.push(entry);
|
||||||
}
|
}
|
||||||
return filtered;
|
return filtered;
|
||||||
}, []);
|
}, []);
|
||||||
this._suggestionsService.removedRedactions = await this.#buildAnnotations(redactionLogCopy, file);
|
const annotations = await this.#buildAnnotations(redactionLogCopy, file);
|
||||||
|
this._suggestionsService.removedRedactions = annotations.filter(a => !a.isSkipped);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { ConfirmationDialogComponent, DialogConfig, DialogService } from '@iqser
|
|||||||
import { ResizeAnnotationDialogComponent } from '../dialogs/resize-annotation-dialog/resize-annotation-dialog.component';
|
import { ResizeAnnotationDialogComponent } from '../dialogs/resize-annotation-dialog/resize-annotation-dialog.component';
|
||||||
import { HighlightActionDialogComponent } from '../dialogs/highlight-action-dialog/highlight-action-dialog.component';
|
import { HighlightActionDialogComponent } from '../dialogs/highlight-action-dialog/highlight-action-dialog.component';
|
||||||
import { RssDialogComponent } from '../dialogs/rss-dialog/rss-dialog.component';
|
import { RssDialogComponent } from '../dialogs/rss-dialog/rss-dialog.component';
|
||||||
|
import { FalsePositiveDialogComponent } from '../dialogs/false-positive-dialog/false-positive-dialog.component';
|
||||||
|
|
||||||
type DialogType =
|
type DialogType =
|
||||||
| 'confirm'
|
| 'confirm'
|
||||||
@ -21,7 +22,8 @@ type DialogType =
|
|||||||
| 'resizeAnnotation'
|
| 'resizeAnnotation'
|
||||||
| 'forceAnnotation'
|
| 'forceAnnotation'
|
||||||
| 'manualAnnotation'
|
| 'manualAnnotation'
|
||||||
| 'highlightAction';
|
| 'highlightAction'
|
||||||
|
| 'falsePositive';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FilePreviewDialogService extends DialogService<DialogType> {
|
export class FilePreviewDialogService extends DialogService<DialogType> {
|
||||||
@ -60,6 +62,9 @@ export class FilePreviewDialogService extends DialogService<DialogType> {
|
|||||||
component: RssDialogComponent,
|
component: RssDialogComponent,
|
||||||
dialogConfig: { width: '90vw' },
|
dialogConfig: { width: '90vw' },
|
||||||
},
|
},
|
||||||
|
falsePositive: {
|
||||||
|
component: FalsePositiveDialogComponent,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(protected readonly _dialog: MatDialog) {
|
constructor(protected readonly _dialog: MatDialog) {
|
||||||
|
|||||||
@ -24,39 +24,47 @@ export class SuggestionsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hideSuggestionsInPreview(annotations: Annotation[]): void {
|
hideSuggestionsInPreview(annotations: Annotation[]): void {
|
||||||
if (!this._userPreferenceService.getDisplaySuggestionsInPreview()) {
|
if (this._readableRedactionsService.active) {
|
||||||
const suggestions = annotations.filter(a => bool(a.getCustomData('suggestion')));
|
if (this._userPreferenceService.getDisplaySuggestionsInPreview()) {
|
||||||
this._annotationManager.hide(suggestions);
|
const suggestionsRemove = annotations.filter(
|
||||||
this.#convertSuggestionsToRedactions(suggestions);
|
a => bool(a.getCustomData('suggestionRemove')) || bool(a.getCustomData('suggestionForceHint')),
|
||||||
|
);
|
||||||
|
this._annotationManager.hide(suggestionsRemove);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
const suggestionsToHide = annotations.filter(
|
||||||
|
a =>
|
||||||
|
(bool(a.getCustomData('suggestionAdd')) && !bool(a.getCustomData('suggestionAddToFalsePositive'))) ||
|
||||||
|
bool(a.getCustomData('notSignatureImage')),
|
||||||
|
);
|
||||||
|
annotations.forEach(a => {
|
||||||
|
if (bool(a.getCustomData('suggestionRemove'))) {
|
||||||
|
const foundRedaction = this.#removedRedactions.find(r => r.id === a.Id);
|
||||||
|
if (!foundRedaction) {
|
||||||
|
suggestionsToHide.push(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._annotationManager.hide(suggestionsToHide);
|
||||||
}
|
}
|
||||||
|
|
||||||
convertWorkloadRemoveSuggestionsToRedactions(annotations: AnnotationWrapper[]): AnnotationWrapper[] {
|
filterWorkloadSuggestionsInPreview(annotations: AnnotationWrapper[]): AnnotationWrapper[] {
|
||||||
if (!this._userPreferenceService.getDisplaySuggestionsInPreview()) {
|
if (this._readableRedactionsService.active) {
|
||||||
annotations = annotations.filter(a => !a.isSuggestion);
|
if (this._userPreferenceService.getDisplaySuggestionsInPreview()) {
|
||||||
annotations = [...annotations, ...this.#removedRedactions];
|
return annotations.filter(a => !a.isSuggestionRemove && !a.isSuggestionForceHint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
annotations = annotations.filter(a => (!a.isSuggestionAdd || a.isSuggestionAddToFalsePositive) && !a.isNotSignatureImage);
|
||||||
|
for (let i = annotations.length - 1; i >= 0; i--) {
|
||||||
|
const foundRemovedRedaction = this.#removedRedactions.find(r => r.id === annotations[i].id);
|
||||||
|
if (foundRemovedRedaction) {
|
||||||
|
annotations[i] = foundRemovedRedaction;
|
||||||
|
} else if (annotations[i].isSuggestionRemove) {
|
||||||
|
annotations.splice(i, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return annotations;
|
return annotations;
|
||||||
}
|
}
|
||||||
|
|
||||||
#convertSuggestionsToRedactions(suggestions: Annotation[]): void {
|
|
||||||
suggestions = this.#filterSuggestions(suggestions);
|
|
||||||
suggestions.forEach(s => s.setCustomData('suggestion', 'false'));
|
|
||||||
this._readableRedactionsService.setPreviewAnnotationsOpacity(suggestions);
|
|
||||||
this._readableRedactionsService.setPreviewAnnotationsColor(suggestions);
|
|
||||||
this._annotationManager.show(suggestions);
|
|
||||||
}
|
|
||||||
|
|
||||||
#filterSuggestions(suggestions: Annotation[]): Annotation[] {
|
|
||||||
const filteredSuggestions = [];
|
|
||||||
|
|
||||||
this.#removedRedactions.forEach(r => {
|
|
||||||
const found = suggestions.find(s => s.Id === r.annotationId);
|
|
||||||
if (found) {
|
|
||||||
filteredSuggestions.push(found);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return filteredSuggestions;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,6 @@ import Quad = Core.Math.Quad;
|
|||||||
|
|
||||||
const DEFAULT_TEXT_ANNOTATION_OPACITY = 1;
|
const DEFAULT_TEXT_ANNOTATION_OPACITY = 1;
|
||||||
const DEFAULT_REMOVED_ANNOTATION_OPACITY = 0.2;
|
const DEFAULT_REMOVED_ANNOTATION_OPACITY = 0.2;
|
||||||
const FINAL_REDACTION_COLOR = '#000000';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AnnotationDrawService {
|
export class AnnotationDrawService {
|
||||||
@ -146,28 +145,43 @@ export class AnnotationDrawService {
|
|||||||
annotation.Id = annotationWrapper.id;
|
annotation.Id = annotationWrapper.id;
|
||||||
annotation.ReadOnly = true;
|
annotation.ReadOnly = true;
|
||||||
|
|
||||||
|
const isOCR = annotationWrapper.isOCR && !annotationWrapper.isSuggestionResize;
|
||||||
|
if (isOCR && !this._annotationManager.isHidden(annotationWrapper.annotationId)) {
|
||||||
|
this._annotationManager.addToHidden(annotationWrapper.annotationId);
|
||||||
|
}
|
||||||
annotation.Hidden =
|
annotation.Hidden =
|
||||||
annotationWrapper.isChangeLogRemoved ||
|
annotationWrapper.isChangeLogRemoved ||
|
||||||
(hideSkipped && annotationWrapper.isSkipped) ||
|
(hideSkipped && annotationWrapper.isSkipped) ||
|
||||||
(annotationWrapper.isOCR && !annotationWrapper.isSuggestionResize) ||
|
|
||||||
this._annotationManager.isHidden(annotationWrapper.annotationId);
|
this._annotationManager.isHidden(annotationWrapper.annotationId);
|
||||||
annotation.setCustomData('redact-manager', 'true');
|
annotation.setCustomData('redact-manager', 'true');
|
||||||
annotation.setCustomData('redaction', String(annotationWrapper.previewAnnotation));
|
annotation.setCustomData('redaction', String(annotationWrapper.previewAnnotation));
|
||||||
annotation.setCustomData('suggestion', String(annotationWrapper.isSuggestion));
|
annotation.setCustomData('suggestion', String(annotationWrapper.isSuggestion));
|
||||||
|
annotation.setCustomData('suggestionAdd', String(annotationWrapper.isSuggestionAdd));
|
||||||
|
annotation.setCustomData('suggestionAddToFalsePositive', String(annotationWrapper.isSuggestionAddToFalsePositive));
|
||||||
annotation.setCustomData('suggestionRemove', String(annotationWrapper.isSuggestionRemove));
|
annotation.setCustomData('suggestionRemove', String(annotationWrapper.isSuggestionRemove));
|
||||||
|
annotation.setCustomData('suggestionRecategorizeImage', String(annotationWrapper.isSuggestionRecategorizeImage));
|
||||||
|
annotation.setCustomData('suggestionForceHint', String(annotationWrapper.isSuggestionForceHint));
|
||||||
annotation.setCustomData('skipped', String(annotationWrapper.isSkipped));
|
annotation.setCustomData('skipped', String(annotationWrapper.isSkipped));
|
||||||
|
annotation.setCustomData('notSignatureImage', String(annotationWrapper.isNotSignatureImage));
|
||||||
annotation.setCustomData('changeLog', String(annotationWrapper.isChangeLogEntry));
|
annotation.setCustomData('changeLog', String(annotationWrapper.isChangeLogEntry));
|
||||||
annotation.setCustomData('changeLogRemoved', String(annotationWrapper.isChangeLogRemoved));
|
annotation.setCustomData('changeLogRemoved', String(annotationWrapper.isChangeLogRemoved));
|
||||||
annotation.setCustomData('opacity', String(annotation.Opacity));
|
annotation.setCustomData('opacity', String(annotation.Opacity));
|
||||||
|
|
||||||
|
const dictionaryRequestColor =
|
||||||
|
annotationWrapper.isSuggestionAddDictionary || (annotationWrapper.isSuggestionResize && annotationWrapper.isModifyDictionary);
|
||||||
|
|
||||||
const redactionColor =
|
const redactionColor =
|
||||||
annotationWrapper.isSuggestion && this._userPreferenceService.getDisplaySuggestionsInPreview()
|
annotationWrapper.isSuggestion && this._userPreferenceService.getDisplaySuggestionsInPreview()
|
||||||
? this._defaultColorsService.getColor(dossierTemplateId, 'requestAddColor')
|
? dictionaryRequestColor
|
||||||
|
? this._defaultColorsService.getColor(dossierTemplateId, 'dictionaryRequestColor')
|
||||||
|
: this._defaultColorsService.getColor(dossierTemplateId, 'requestAddColor')
|
||||||
: this._defaultColorsService.getColor(dossierTemplateId, 'previewColor');
|
: this._defaultColorsService.getColor(dossierTemplateId, 'previewColor');
|
||||||
annotation.setCustomData('redactionColor', String(redactionColor));
|
annotation.setCustomData('redactionColor', String(redactionColor));
|
||||||
annotation.setCustomData('finalRedactionColor', FINAL_REDACTION_COLOR);
|
|
||||||
annotation.setCustomData('annotationColor', String(annotationWrapper.color));
|
annotation.setCustomData('annotationColor', String(annotationWrapper.color));
|
||||||
|
|
||||||
|
const appliedRedactionColor = this._defaultColorsService.getColor(dossierTemplateId, 'appliedRedactionColor');
|
||||||
|
annotation.setCustomData('appliedRedactionColor', String(appliedRedactionColor));
|
||||||
|
|
||||||
return annotation;
|
return annotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Inject, Injectable } from '@angular/core';
|
import { inject, Injectable } from '@angular/core';
|
||||||
import { BASE_HREF_FN, BaseHrefFn, bool } from '@iqser/common-ui';
|
import { BASE_HREF_FN } from '@iqser/common-ui';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { HeaderElements } from '../../file-preview/utils/constants';
|
import { HeaderElements } from '../../file-preview/utils/constants';
|
||||||
@ -13,12 +13,12 @@ import Annotation = Core.Annotations.Annotation;
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class ReadableRedactionsService {
|
export class ReadableRedactionsService {
|
||||||
readonly active$: Observable<boolean>;
|
readonly active$: Observable<boolean>;
|
||||||
|
private readonly _convertPath = inject(BASE_HREF_FN);
|
||||||
readonly #enableIcon = this._convertPath('/assets/icons/general/redaction-preview.svg');
|
readonly #enableIcon = this._convertPath('/assets/icons/general/redaction-preview.svg');
|
||||||
readonly #disableIcon = this._convertPath('/assets/icons/general/redaction-final.svg');
|
readonly #disableIcon = this._convertPath('/assets/icons/general/redaction-final.svg');
|
||||||
readonly #active$ = new BehaviorSubject<boolean>(true);
|
readonly #active$ = new BehaviorSubject<boolean>(true);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
|
|
||||||
private readonly _pdf: PdfViewer,
|
private readonly _pdf: PdfViewer,
|
||||||
private readonly _translateService: TranslateService,
|
private readonly _translateService: TranslateService,
|
||||||
private readonly _annotationManager: REDAnnotationManager,
|
private readonly _annotationManager: REDAnnotationManager,
|
||||||
@ -48,11 +48,39 @@ export class ReadableRedactionsService {
|
|||||||
title: this.toggleReadableRedactionsBtnTitle,
|
title: this.toggleReadableRedactionsBtnTitle,
|
||||||
img: this.toggleReadableRedactionsBtnIcon,
|
img: this.toggleReadableRedactionsBtnIcon,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!this.active) {
|
||||||
|
this.setCustomDrawHandler();
|
||||||
|
} else {
|
||||||
|
this.restoreDraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setCustomDrawHandler(): void {
|
||||||
|
const annotationClass: any = this._pdf.instance.Core.Annotations.TextHighlightAnnotation;
|
||||||
|
this._pdf.instance.Core.Annotations.setCustomDrawHandler(
|
||||||
|
annotationClass,
|
||||||
|
(ctx: CanvasRenderingContext2D, pageMatrix, rotation, options) => {
|
||||||
|
const annotation = options.annotation;
|
||||||
|
ctx.globalCompositeOperation = 'source-over';
|
||||||
|
ctx.fillStyle = annotation.getCustomData('appliedRedactionColor');
|
||||||
|
ctx.fillRect(annotation.getX(), annotation.getY(), annotation.getWidth(), annotation.getHeight());
|
||||||
|
},
|
||||||
|
{
|
||||||
|
generateAppearance: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreDraw(): void {
|
||||||
|
const annotationClass: any = this._pdf.instance.Core.Annotations.TextHighlightAnnotation;
|
||||||
|
this._pdf.instance.Core.Annotations.restoreDraw(annotationClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
setAnnotationsOpacity(annotations: Annotation[], restoreToOriginal = false) {
|
setAnnotationsOpacity(annotations: Annotation[], restoreToOriginal = false) {
|
||||||
annotations.forEach(annotation => {
|
annotations.forEach(annotation => {
|
||||||
annotation['Opacity'] = restoreToOriginal ? parseFloat(annotation.getCustomData('opacity')) : 0.5;
|
const isSuggestion = annotation.getCustomData('suggestion');
|
||||||
|
annotation['Opacity'] = restoreToOriginal || isSuggestion ? parseFloat(annotation.getCustomData('opacity')) : 0.5;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,20 +91,4 @@ export class ReadableRedactionsService {
|
|||||||
annotation['FillColor'] = color;
|
annotation['FillColor'] = color;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setPreviewAnnotationsOpacity(annotations: Annotation[]) {
|
|
||||||
annotations.forEach(annotation => {
|
|
||||||
const isSuggestion = bool(annotation.getCustomData('suggestion'));
|
|
||||||
const restoreToOriginal = !this.active && !isSuggestion;
|
|
||||||
this.setAnnotationsOpacity([annotation], restoreToOriginal);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setPreviewAnnotationsColor(annotations: Annotation[]) {
|
|
||||||
annotations.forEach(annotation => {
|
|
||||||
const isSuggestion = bool(annotation.getCustomData('suggestion'));
|
|
||||||
const color = this.active || isSuggestion ? 'redactionColor' : 'finalRedactionColor';
|
|
||||||
this.setAnnotationsColor([annotation], color);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { Inject, Injectable } from '@angular/core';
|
|||||||
import { IHeaderElement, RotationTypes } from '@red/domain';
|
import { IHeaderElement, RotationTypes } from '@red/domain';
|
||||||
import { HeaderElements, HeaderElementType } from '../../file-preview/utils/constants';
|
import { HeaderElements, HeaderElementType } from '../../file-preview/utils/constants';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { BASE_HREF_FN, BaseHrefFn, IqserPermissionsService } from '@iqser/common-ui';
|
import { BASE_HREF_FN, BaseHrefFn } from '@iqser/common-ui';
|
||||||
import { TooltipsService } from './tooltips.service';
|
import { TooltipsService } from './tooltips.service';
|
||||||
import { PageRotationService } from './page-rotation.service';
|
import { PageRotationService } from './page-rotation.service';
|
||||||
import { PdfViewer } from './pdf-viewer.service';
|
import { PdfViewer } from './pdf-viewer.service';
|
||||||
@ -13,7 +13,6 @@ import { UserPreferenceService } from '@users/user-preference.service';
|
|||||||
import { fromEvent, Observable, Subject } from 'rxjs';
|
import { fromEvent, Observable, Subject } from 'rxjs';
|
||||||
import { ViewerEvent, VisibilityChangedEvent } from '../utils/types';
|
import { ViewerEvent, VisibilityChangedEvent } from '../utils/types';
|
||||||
import { ReadableRedactionsService } from './readable-redactions.service';
|
import { ReadableRedactionsService } from './readable-redactions.service';
|
||||||
import { ROLES } from '@users/roles';
|
|
||||||
import { filter, map, tap } from 'rxjs/operators';
|
import { filter, map, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
const divider: IHeaderElement = {
|
const divider: IHeaderElement = {
|
||||||
@ -23,6 +22,7 @@ const divider: IHeaderElement = {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class ViewerHeaderService {
|
export class ViewerHeaderService {
|
||||||
readonly events$: Observable<ViewerEvent>;
|
readonly events$: Observable<ViewerEvent>;
|
||||||
|
toggleLoadAnnotations$: Observable<boolean>;
|
||||||
#buttons: Map<HeaderElementType, IHeaderElement>;
|
#buttons: Map<HeaderElementType, IHeaderElement>;
|
||||||
readonly #config = new Map<HeaderElementType, boolean>([
|
readonly #config = new Map<HeaderElementType, boolean>([
|
||||||
[HeaderElements.SHAPE_TOOL_GROUP_BUTTON, true],
|
[HeaderElements.SHAPE_TOOL_GROUP_BUTTON, true],
|
||||||
@ -38,7 +38,6 @@ export class ViewerHeaderService {
|
|||||||
]);
|
]);
|
||||||
#docBeforeCompare: Blob;
|
#docBeforeCompare: Blob;
|
||||||
readonly #events$ = new Subject<ViewerEvent>();
|
readonly #events$ = new Subject<ViewerEvent>();
|
||||||
toggleLoadAnnotations$: Observable<boolean>;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
|
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
|
||||||
@ -50,7 +49,6 @@ export class ViewerHeaderService {
|
|||||||
private readonly _tooltipsService: TooltipsService,
|
private readonly _tooltipsService: TooltipsService,
|
||||||
private readonly _readableRedactionsService: ReadableRedactionsService,
|
private readonly _readableRedactionsService: ReadableRedactionsService,
|
||||||
private readonly _userPreferenceService: UserPreferenceService,
|
private readonly _userPreferenceService: UserPreferenceService,
|
||||||
private readonly _iqserPermissionsService: IqserPermissionsService,
|
|
||||||
) {
|
) {
|
||||||
this.events$ = this.#events$.asObservable();
|
this.events$ = this.#events$.asObservable();
|
||||||
}
|
}
|
||||||
@ -169,11 +167,6 @@ export class ViewerHeaderService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#discardRotation(): void {
|
|
||||||
this._rotationService.discardRotation();
|
|
||||||
this.disable(ROTATION_ACTION_BUTTONS);
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _rotateRight(): IHeaderElement {
|
private get _rotateRight(): IHeaderElement {
|
||||||
return {
|
return {
|
||||||
type: 'actionButton',
|
type: 'actionButton',
|
||||||
@ -305,9 +298,7 @@ export class ViewerHeaderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enableRotationButtons(): void {
|
enableRotationButtons(): void {
|
||||||
if (this._iqserPermissionsService.has(ROLES.files.rotatePage)) {
|
this.enable(ROTATION_BUTTONS);
|
||||||
this.enable(ROTATION_BUTTONS);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
disableRotationButtons(): void {
|
disableRotationButtons(): void {
|
||||||
@ -320,6 +311,11 @@ export class ViewerHeaderService {
|
|||||||
this.enable([HeaderElements.COMPARE_BUTTON]);
|
this.enable([HeaderElements.COMPARE_BUTTON]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#discardRotation(): void {
|
||||||
|
this._rotationService.discardRotation();
|
||||||
|
this.disable(ROTATION_ACTION_BUTTONS);
|
||||||
|
}
|
||||||
|
|
||||||
#toggleRotationActionButtons() {
|
#toggleRotationActionButtons() {
|
||||||
if (this._rotationService.hasRotations) {
|
if (this._rotationService.hasRotations) {
|
||||||
this.enable(ROTATION_ACTION_BUTTONS);
|
this.enable(ROTATION_ACTION_BUTTONS);
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import { ViewerHeaderService } from '../../../pdf-viewer/services/viewer-header.
|
|||||||
import { ROTATION_ACTION_BUTTONS } from '../../../pdf-viewer/utils/constants';
|
import { ROTATION_ACTION_BUTTONS } from '../../../pdf-viewer/utils/constants';
|
||||||
import { ROLES } from '@users/roles';
|
import { ROLES } from '@users/roles';
|
||||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||||
|
import { setLocalStorageDataByFileId } from '@utils/local-storage';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-file-actions [file] [type] [dossier]',
|
selector: 'redaction-file-actions [file] [type] [dossier]',
|
||||||
@ -166,7 +167,7 @@ export class FileActionsComponent implements OnChanges {
|
|||||||
{
|
{
|
||||||
id: 'toggle-document-info-btn-' + fileId,
|
id: 'toggle-document-info-btn-' + fileId,
|
||||||
type: ActionTypes.circleBtn,
|
type: ActionTypes.circleBtn,
|
||||||
action: () => this._documentInfoService.toggle(),
|
action: () => this.#toggleDocumentInfo(),
|
||||||
tooltip: _('file-preview.document-info'),
|
tooltip: _('file-preview.document-info'),
|
||||||
ariaExpanded: this._documentInfoService?.shown$,
|
ariaExpanded: this._documentInfoService?.shown$,
|
||||||
icon: 'red:status-info',
|
icon: 'red:status-info',
|
||||||
@ -175,7 +176,7 @@ export class FileActionsComponent implements OnChanges {
|
|||||||
{
|
{
|
||||||
id: 'toggle-exclude-pages-btn-' + fileId,
|
id: 'toggle-exclude-pages-btn-' + fileId,
|
||||||
type: ActionTypes.circleBtn,
|
type: ActionTypes.circleBtn,
|
||||||
action: () => this._excludedPagesService.toggle(),
|
action: () => this.#toggleExcludePages(),
|
||||||
tooltip: _('file-preview.exclude-pages'),
|
tooltip: _('file-preview.exclude-pages'),
|
||||||
ariaExpanded: this._excludedPagesService?.shown$,
|
ariaExpanded: this._excludedPagesService?.shown$,
|
||||||
showDot: !!this.file.excludedPages?.length,
|
showDot: !!this.file.excludedPages?.length,
|
||||||
@ -481,4 +482,22 @@ export class FileActionsComponent implements OnChanges {
|
|||||||
await this._filesService.setToNew(this.file);
|
await this._filesService.setToNew(this.file);
|
||||||
this._loadingService.stop();
|
this._loadingService.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async #toggleExcludePages() {
|
||||||
|
this._excludedPagesService.toggle();
|
||||||
|
const shown = await this._excludedPagesService.shown();
|
||||||
|
setLocalStorageDataByFileId(this.file.id, 'show-exclude-pages', shown);
|
||||||
|
if (shown) {
|
||||||
|
setLocalStorageDataByFileId(this.file.id, 'show-document-info', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async #toggleDocumentInfo() {
|
||||||
|
this._documentInfoService.toggle();
|
||||||
|
const shown = await this._documentInfoService.shown();
|
||||||
|
setLocalStorageDataByFileId(this.file.id, 'show-document-info', shown);
|
||||||
|
if (shown) {
|
||||||
|
setLocalStorageDataByFileId(this.file.id, 'show-exclude-pages', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<div [translateParams]="{ type: mode }" [translate]="'assign-owner.dialog.title'" class="dialog-header heading-l"></div>
|
<div [innerHTML]="'assign-owner.dialog.title' | translate : { type: mode }" class="dialog-header heading-l"></div>
|
||||||
|
|
||||||
<form (submit)="save()" [formGroup]="form">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
|
|||||||
@ -90,28 +90,11 @@ export class AssignReviewerApproverDialogComponent {
|
|||||||
return this.permissionsService.canUnassignUser(this.data.files, this.dossier);
|
return this.permissionsService.canUnassignUser(this.data.files, this.dossier);
|
||||||
}
|
}
|
||||||
|
|
||||||
get #uniqueReviewers(): Set<string> {
|
|
||||||
const uniqueReviewers = new Set<string>();
|
|
||||||
|
|
||||||
for (const file of this.data.files) {
|
|
||||||
if (file.assignee) {
|
|
||||||
uniqueReviewers.add(file.assignee);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return uniqueReviewers;
|
|
||||||
}
|
|
||||||
|
|
||||||
get #user(): string {
|
get #user(): string {
|
||||||
const userOptions = this.userOptions;
|
if (this.data.files.every(file => !file.assignee)) {
|
||||||
|
return null;
|
||||||
if (this.data.withCurrentUserAsDefault && userOptions.includes(this.currentUser.id)) {
|
|
||||||
return this.currentUser.id;
|
|
||||||
}
|
}
|
||||||
|
return this.data.files.length === 1 ? this.data.files[0].assignee : this.currentUser.id;
|
||||||
const uniqueReviewers = [...this.#uniqueReviewers.values()];
|
|
||||||
const user = uniqueReviewers.length === 1 ? uniqueReviewers[0] : this.currentUser.id;
|
|
||||||
return userOptions.indexOf(user) >= 0 ? userOptions[userOptions.indexOf(user)] : user;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get #form() {
|
get #form() {
|
||||||
@ -145,14 +128,10 @@ export class AssignReviewerApproverDialogComponent {
|
|||||||
#customSort(ids: string[]) {
|
#customSort(ids: string[]) {
|
||||||
let sorted = ids.sort((a, b) => this.userService.getName(a).localeCompare(this.userService.getName(b)));
|
let sorted = ids.sort((a, b) => this.userService.getName(a).localeCompare(this.userService.getName(b)));
|
||||||
|
|
||||||
const fileHasAssignee = this.data.files.length === 1 && this.data.files[0].assignee;
|
sorted = moveElementInArray(sorted, this.currentUser.id, 0);
|
||||||
|
|
||||||
if (fileHasAssignee) {
|
|
||||||
sorted = moveElementInArray(sorted, this.data.files[0].assignee, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sorted.includes('undefined')) {
|
if (sorted.includes('undefined')) {
|
||||||
sorted = moveElementInArray(sorted, 'undefined', fileHasAssignee ? 1 : 0);
|
sorted = moveElementInArray(sorted, 'undefined', 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sorted;
|
return sorted;
|
||||||
|
|||||||
@ -50,17 +50,14 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
|
|||||||
|
|
||||||
async save(): EditDossierSaveResult {
|
async save(): EditDossierSaveResult {
|
||||||
try {
|
try {
|
||||||
await firstValueFrom(
|
await this._dictionaryService.saveEntries(
|
||||||
this._dictionaryService.saveEntries(
|
this._dictionaryManager.editor.currentEntries,
|
||||||
this._dictionaryManager.editor.currentEntries,
|
this._dictionaryManager.initialEntries,
|
||||||
this._dictionaryManager.initialEntries,
|
this.dossier.dossierTemplateId,
|
||||||
this.dossier.dossierTemplateId,
|
this.dossierDictionary.type,
|
||||||
this.dossierDictionary.type,
|
this.dossier.id,
|
||||||
this.dossier.id,
|
false,
|
||||||
false,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
await this._updateDossierDictionary();
|
await this._updateDossierDictionary();
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -10,18 +10,20 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-container *ngIf="selectedApproversList.length">
|
<ng-container *ngIf="selectedApprovers$ | async as selectedApprovers">
|
||||||
<div class="all-caps-label mt-16" id="approversLabel" translate="assign-dossier-owner.dialog.approvers"></div>
|
<ng-container *ngIf="selectedApprovers.length">
|
||||||
<redaction-team-members
|
<div class="all-caps-label mt-16" id="approversLabel" translate="assign-dossier-owner.dialog.approvers"></div>
|
||||||
(remove)="toggleSelected($event)"
|
<redaction-team-members
|
||||||
[canAdd]="false"
|
(remove)="toggleSelected($event)"
|
||||||
[canRemove]="hasOwner && !disabled"
|
[canAdd]="false"
|
||||||
[dossierId]="dossier.id"
|
[canRemove]="hasOwner && !disabled"
|
||||||
[largeSpacing]="true"
|
[dossierId]="dossier.id"
|
||||||
[memberIds]="selectedApproversList"
|
[largeSpacing]="true"
|
||||||
[perLine]="13"
|
[memberIds]="selectedApprovers"
|
||||||
[unremovableMembers]="[selectedOwnerId]"
|
[perLine]="13"
|
||||||
></redaction-team-members>
|
[unremovableMembers]="[this.form.value.owner]"
|
||||||
|
></redaction-team-members>
|
||||||
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<div class="all-caps-label mt-16" id="reviewersLabel" translate="assign-dossier-owner.dialog.reviewers"></div>
|
<div class="all-caps-label mt-16" id="reviewersLabel" translate="assign-dossier-owner.dialog.reviewers"></div>
|
||||||
@ -34,7 +36,7 @@
|
|||||||
[largeSpacing]="true"
|
[largeSpacing]="true"
|
||||||
[memberIds]="selectedReviewers$ | async"
|
[memberIds]="selectedReviewers$ | async"
|
||||||
[perLine]="13"
|
[perLine]="13"
|
||||||
[unremovableMembers]="[selectedOwnerId]"
|
[unremovableMembers]="[this.form.value.owner]"
|
||||||
></redaction-team-members>
|
></redaction-team-members>
|
||||||
|
|
||||||
<ng-container *ngIf="!(selectedReviewers$ | async)?.length">
|
<ng-container *ngIf="!(selectedReviewers$ | async)?.length">
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, inject, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||||
import { UserService } from '@users/user.service';
|
import { UserService } from '@users/user.service';
|
||||||
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||||
import { Dossier, IDossierRequest, User } from '@red/domain';
|
import { Dossier, IDossierRequest } from '@red/domain';
|
||||||
import { EditDossierSaveResult, EditDossierSectionInterface } from '../edit-dossier-section.interface';
|
import { EditDossierSaveResult, EditDossierSectionInterface } from '../edit-dossier-section.interface';
|
||||||
import { BehaviorSubject, firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import { DossiersService } from '@services/dossiers/dossiers.service';
|
import { DossiersService } from '@services/dossiers/dossiers.service';
|
||||||
import { compareLists } from '@utils/functions';
|
import { compareLists } from '@utils/functions';
|
||||||
import { FilesService } from '@services/files/files.service';
|
import { FilesService } from '@services/files/files.service';
|
||||||
import { getCurrentUser } from '@iqser/common-ui';
|
import { Debounce } from '@iqser/common-ui';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-edit-dossier-team',
|
selector: 'redaction-edit-dossier-team',
|
||||||
@ -16,43 +17,26 @@ import { getCurrentUser } from '@iqser/common-ui';
|
|||||||
styleUrls: ['./edit-dossier-team.component.scss'],
|
styleUrls: ['./edit-dossier-team.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class EditDossierTeamComponent implements EditDossierSectionInterface, OnInit {
|
export class EditDossierTeamComponent implements EditDossierSectionInterface, OnChanges {
|
||||||
form: UntypedFormGroup;
|
form = this.#getForm();
|
||||||
searchQuery = '';
|
searchQuery = '';
|
||||||
|
|
||||||
@Input() dossier: Dossier;
|
@Input() dossier: Dossier;
|
||||||
|
|
||||||
membersSelectOptions: string[] = [];
|
membersSelectOptions: string[] = [];
|
||||||
readonly ownersSelectOptions = this.userService.all.filter(u => u.isManager).map(m => m.id);
|
readonly #userService = inject(UserService);
|
||||||
readonly selectedReviewers$ = new BehaviorSubject<string[]>([]);
|
readonly #dossiersService = inject(DossiersService);
|
||||||
readonly #currentUser = getCurrentUser<User>();
|
readonly #permissionsService = inject(PermissionsService);
|
||||||
|
readonly #filesService = inject(FilesService);
|
||||||
constructor(
|
readonly ownersSelectOptions = this.#userService.all.filter(u => u.isManager).map(m => m.id);
|
||||||
readonly userService: UserService,
|
readonly #formValue$ = this.form.valueChanges;
|
||||||
private readonly _formBuilder: UntypedFormBuilder,
|
readonly selectedReviewers$ = this.#formValue$.pipe(map(v => v.members.filter(m => !v.approvers.includes(m))));
|
||||||
private readonly _dossiersService: DossiersService,
|
readonly selectedApprovers$ = this.#formValue$.pipe(map(v => v.approvers));
|
||||||
private readonly _permissionsService: PermissionsService,
|
|
||||||
private readonly _filesService: FilesService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
get selectedOwnerId(): string {
|
|
||||||
return this.form.get('owner').value;
|
|
||||||
}
|
|
||||||
|
|
||||||
get selectedApproversList(): string[] {
|
|
||||||
return this.form.get('approvers').value;
|
|
||||||
}
|
|
||||||
|
|
||||||
get selectedMembersList(): string[] {
|
|
||||||
return this.form.get('members').value;
|
|
||||||
}
|
|
||||||
|
|
||||||
get valid(): boolean {
|
get valid(): boolean {
|
||||||
return this.form.valid;
|
return this.form.valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
get disabled() {
|
get disabled() {
|
||||||
return !this._permissionsService.canEditTeamMembers() || !this.form.get('owner').value;
|
return !this.#permissionsService.canEditTeamMembers() || !this.form.get('owner').value;
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasOwner() {
|
get hasOwner() {
|
||||||
@ -60,15 +44,17 @@ export class EditDossierTeamComponent implements EditDossierSectionInterface, On
|
|||||||
}
|
}
|
||||||
|
|
||||||
get changed() {
|
get changed() {
|
||||||
if (this.dossier.ownerId !== this.selectedOwnerId) {
|
const { owner, members, approvers } = this.form.value;
|
||||||
|
|
||||||
|
if (this.dossier.ownerId !== owner) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialMembers = [...this.dossier.memberIds].sort();
|
const initialMembers = [...this.dossier.memberIds].sort();
|
||||||
const currentMembers = this.selectedMembersList.sort();
|
const currentMembers = members.sort();
|
||||||
|
|
||||||
const initialApprovers = [...this.dossier.approverIds].sort();
|
const initialApprovers = [...this.dossier.approverIds].sort();
|
||||||
const currentApprovers = this.selectedApproversList.sort();
|
const currentApprovers = approvers.sort();
|
||||||
return compareLists(initialMembers, currentMembers) || compareLists(initialApprovers, currentApprovers);
|
return compareLists(initialMembers, currentMembers) || compareLists(initialApprovers, currentApprovers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,105 +64,113 @@ export class EditDossierTeamComponent implements EditDossierSectionInterface, On
|
|||||||
if (!this.isApprover(ownerId)) {
|
if (!this.isApprover(ownerId)) {
|
||||||
this.toggleApprover(ownerId);
|
this.toggleApprover(ownerId);
|
||||||
}
|
}
|
||||||
this._updateLists();
|
this.#updateLists();
|
||||||
}
|
}
|
||||||
|
const { owner, members, approvers } = this.form.value;
|
||||||
const dossier = {
|
const dossier = {
|
||||||
...this.dossier,
|
...this.dossier,
|
||||||
memberIds: this.selectedMembersList,
|
memberIds: members,
|
||||||
approverIds: this.selectedApproversList,
|
approverIds: approvers,
|
||||||
ownerId: this.selectedOwnerId,
|
ownerId: owner,
|
||||||
} as IDossierRequest;
|
} as IDossierRequest;
|
||||||
|
|
||||||
const updatedDossier = await firstValueFrom(this._dossiersService.createOrUpdate(dossier));
|
const updatedDossier = await firstValueFrom(this.#dossiersService.createOrUpdate(dossier));
|
||||||
await firstValueFrom(this._filesService.loadAll(updatedDossier.dossierId));
|
await firstValueFrom(this.#filesService.loadAll(updatedDossier.dossierId));
|
||||||
return { success: !!updatedDossier };
|
return { success: !!updatedDossier };
|
||||||
}
|
}
|
||||||
|
|
||||||
isMemberSelected(userId: string): boolean {
|
isMemberSelected(userId: string): boolean {
|
||||||
return this.selectedMembersList.indexOf(userId) !== -1;
|
return this.form.value.members.includes(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
isApprover(userId: string): boolean {
|
isApprover(userId: string): boolean {
|
||||||
return this.selectedApproversList.indexOf(userId) !== -1;
|
return this.form.value.approvers.includes(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleApprover(userId: string, $event?: MouseEvent) {
|
toggleApprover(userId: string, event?) {
|
||||||
$event?.stopPropagation();
|
event?.stopPropagation();
|
||||||
|
const currentApprovers = this.form.value.approvers;
|
||||||
|
const approversControl = this.form.controls.approvers;
|
||||||
if (this.isApprover(userId)) {
|
if (this.isApprover(userId)) {
|
||||||
this.selectedApproversList.splice(this.selectedApproversList.indexOf(userId), 1);
|
approversControl.patchValue(currentApprovers.filter(a => a !== userId));
|
||||||
} else {
|
} else {
|
||||||
this.selectedApproversList.push(userId);
|
approversControl.patchValue([...currentApprovers, userId]);
|
||||||
if (!this.isMemberSelected(userId)) {
|
if (!this.isMemberSelected(userId)) {
|
||||||
this.toggleSelected(userId);
|
this.toggleSelected(userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.#updateLists();
|
||||||
this._updateLists();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleSelected(userId: string) {
|
toggleSelected(userId: string) {
|
||||||
|
const { members, approvers } = this.form.value;
|
||||||
|
const { members: membersControl, approvers: approversControl } = this.form.controls;
|
||||||
if (this.isMemberSelected(userId)) {
|
if (this.isMemberSelected(userId)) {
|
||||||
this.selectedMembersList.splice(this.selectedMembersList.indexOf(userId), 1);
|
membersControl.patchValue(members.filter(m => m !== userId));
|
||||||
|
|
||||||
if (this.isApprover(userId)) {
|
if (this.isApprover(userId)) {
|
||||||
this.selectedApproversList.splice(this.selectedApproversList.indexOf(userId), 1);
|
approversControl.patchValue(approvers.filter(a => a !== userId));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.selectedMembersList.push(userId);
|
membersControl.patchValue([...members, userId]);
|
||||||
}
|
}
|
||||||
this._updateLists();
|
this.#updateLists();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
this._loadData();
|
if (changes.dossier.isFirstChange()) {
|
||||||
|
setTimeout(() => this.#resetForm());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
revert() {
|
revert() {
|
||||||
this._loadData();
|
this.#resetForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
setMembersSelectOptions(value = this.searchQuery): void {
|
setMembersSelectOptions(value = this.searchQuery): void {
|
||||||
const possibleMembers = this.userService.all.filter(user => user.isUser || user.isManager);
|
const possibleMembers = this.#userService.all.filter(user => user.isUser || user.isManager);
|
||||||
this.membersSelectOptions = possibleMembers
|
this.membersSelectOptions = possibleMembers
|
||||||
.filter(user => this.userService.getName(user.id).toLowerCase().includes(value.toLowerCase()))
|
.filter(user => this.#userService.getName(user.id).toLowerCase().includes(value.toLowerCase()))
|
||||||
.filter(user => this.selectedOwnerId !== user.id)
|
.filter(user => this.form.value.owner !== user.id)
|
||||||
.map(user => user.id);
|
.map(user => user.id)
|
||||||
|
.sort((a, b) => this.#userService.getName(a).localeCompare(this.#userService.getName(b)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Debounce(0)
|
||||||
onChangeOwner(ownerId: string) {
|
onChangeOwner(ownerId: string) {
|
||||||
if (this.hasOwner) {
|
if (this.hasOwner) {
|
||||||
if (!this.isApprover(ownerId)) {
|
if (!this.isApprover(ownerId)) {
|
||||||
this.toggleApprover(ownerId);
|
this.toggleApprover(ownerId);
|
||||||
}
|
}
|
||||||
// If it is an approver, it is already a member, no need to check
|
// If it is an approver, it is already a member, no need to check
|
||||||
this._updateLists();
|
this.#updateLists();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setSelectedReviewersList() {
|
#updateLists() {
|
||||||
const selectedReviewers = this.selectedMembersList.filter(m => this.selectedApproversList.indexOf(m) === -1);
|
|
||||||
this.selectedReviewers$.next(selectedReviewers);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _loadData() {
|
|
||||||
this.form = this._formBuilder.group({
|
|
||||||
owner: [
|
|
||||||
{
|
|
||||||
value: this.dossier.ownerId,
|
|
||||||
disabled: !this._permissionsService.canEditTeamMembers(),
|
|
||||||
},
|
|
||||||
Validators.required,
|
|
||||||
],
|
|
||||||
approvers: [[...this.dossier.approverIds]],
|
|
||||||
members: [[...this.dossier.memberIds]],
|
|
||||||
});
|
|
||||||
this._updateLists();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _updateLists() {
|
|
||||||
this._setSelectedReviewersList();
|
|
||||||
this.setMembersSelectOptions();
|
this.setMembersSelectOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#sortByName(ids: string[]) {
|
||||||
|
return ids.sort((a, b) => this.#userService.getName(a).localeCompare(this.#userService.getName(b)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#getForm() {
|
||||||
|
return new FormGroup({
|
||||||
|
owner: new FormControl({ value: '', disabled: false }, Validators.required),
|
||||||
|
approvers: new FormControl([]),
|
||||||
|
members: new FormControl([]),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#resetForm() {
|
||||||
|
this.form.reset(
|
||||||
|
{
|
||||||
|
owner: { value: this.dossier.ownerId, disabled: !this.#permissionsService.canEditTeamMembers() },
|
||||||
|
approvers: this.#sortByName([...this.dossier.approverIds]),
|
||||||
|
members: this.#sortByName([...this.dossier.memberIds]),
|
||||||
|
} as unknown,
|
||||||
|
{ emitEvent: true },
|
||||||
|
);
|
||||||
|
this.#updateLists();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -94,6 +94,7 @@
|
|||||||
<div *ngIf="withFloatingActions && !!editor?.hasChanges && canEdit && !isLeavingPage" [class.offset]="compare" class="changes-box">
|
<div *ngIf="withFloatingActions && !!editor?.hasChanges && canEdit && !isLeavingPage" [class.offset]="compare" class="changes-box">
|
||||||
<iqser-icon-button
|
<iqser-icon-button
|
||||||
(action)="saveDictionary.emit()"
|
(action)="saveDictionary.emit()"
|
||||||
|
[disabled]="!!(_loadingService.isLoading$ | async)"
|
||||||
[label]="'dictionary-overview.save-changes' | translate"
|
[label]="'dictionary-overview.save-changes' | translate"
|
||||||
[type]="iconButtonTypes.primary"
|
[type]="iconButtonTypes.primary"
|
||||||
icon="iqser:check"
|
icon="iqser:check"
|
||||||
|
|||||||
@ -56,7 +56,7 @@ export class DictionaryManagerComponent implements OnChanges {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly _dictionaryService: DictionaryService,
|
private readonly _dictionaryService: DictionaryService,
|
||||||
private readonly _dictionariesMapService: DictionariesMapService,
|
private readonly _dictionariesMapService: DictionariesMapService,
|
||||||
private readonly _loadingService: LoadingService,
|
protected readonly _loadingService: LoadingService,
|
||||||
private readonly _changeRef: ChangeDetectorRef,
|
private readonly _changeRef: ChangeDetectorRef,
|
||||||
readonly activeDossiersService: ActiveDossiersService,
|
readonly activeDossiersService: ActiveDossiersService,
|
||||||
readonly dossierTemplatesService: DossierTemplatesService,
|
readonly dossierTemplatesService: DossierTemplatesService,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
<ngx-monaco-editor
|
<ngx-monaco-editor
|
||||||
(init)="onCodeEditorInit($event)"
|
(init)="onCodeEditorInit($event)"
|
||||||
(ngModelChange)="codeEditorTextChanged()"
|
(ngModelChange)="_editorTextChanged$.next($event)"
|
||||||
|
(paste)="onPaste($event)"
|
||||||
*ngIf="!showDiffEditor"
|
*ngIf="!showDiffEditor"
|
||||||
[(ngModel)]="value"
|
[(ngModel)]="value"
|
||||||
[options]="editorOptions"
|
[options]="editorOptions"
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
|
import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
|
||||||
import { Debounce, List, OnChange } from '@iqser/common-ui';
|
import { List, LoadingService, OnChange } from '@iqser/common-ui';
|
||||||
import { UserPreferenceService } from '@users/user-preference.service';
|
|
||||||
import { EditorThemeService } from '@services/editor-theme.service';
|
import { EditorThemeService } from '@services/editor-theme.service';
|
||||||
|
import { debounceTime, filter, tap } from 'rxjs/operators';
|
||||||
|
import { Subject, Subscription } from 'rxjs';
|
||||||
import IStandaloneEditorConstructionOptions = monaco.editor.IStandaloneEditorConstructionOptions;
|
import IStandaloneEditorConstructionOptions = monaco.editor.IStandaloneEditorConstructionOptions;
|
||||||
import ICodeEditor = monaco.editor.ICodeEditor;
|
import ICodeEditor = monaco.editor.ICodeEditor;
|
||||||
import IDiffEditor = monaco.editor.IDiffEditor;
|
import IDiffEditor = monaco.editor.IDiffEditor;
|
||||||
@ -26,7 +27,7 @@ const notZero = (lineChange: ILineChange) => lineChange.originalEndLineNumber !=
|
|||||||
templateUrl: './editor.component.html',
|
templateUrl: './editor.component.html',
|
||||||
styleUrls: ['./editor.component.scss'],
|
styleUrls: ['./editor.component.scss'],
|
||||||
})
|
})
|
||||||
export class EditorComponent implements OnInit, OnChanges {
|
export class EditorComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
@Input() showDiffEditor = false;
|
@Input() showDiffEditor = false;
|
||||||
@Input() diffEditorText: string;
|
@Input() diffEditorText: string;
|
||||||
@Input() @OnChange<List, EditorComponent>('revert') initialEntries: List;
|
@Input() @OnChange<List, EditorComponent>('revert') initialEntries: List;
|
||||||
@ -40,10 +41,25 @@ export class EditorComponent implements OnInit, OnChanges {
|
|||||||
editorOptions: IStandaloneEditorConstructionOptions = {};
|
editorOptions: IStandaloneEditorConstructionOptions = {};
|
||||||
codeEditor: ICodeEditor;
|
codeEditor: ICodeEditor;
|
||||||
value: string;
|
value: string;
|
||||||
|
protected readonly _editorTextChanged$ = new Subject<string>();
|
||||||
private _diffEditor: IDiffEditor;
|
private _diffEditor: IDiffEditor;
|
||||||
private _decorations: string[] = [];
|
private _decorations: string[] = [];
|
||||||
|
private readonly _sub$ = new Subscription();
|
||||||
|
private _initialEntriesSet = new Set<string>();
|
||||||
|
|
||||||
constructor(private readonly _userPreferenceService: UserPreferenceService, private readonly _editorThemeService: EditorThemeService) {}
|
constructor(private readonly _loadingService: LoadingService, private readonly _editorThemeService: EditorThemeService) {
|
||||||
|
const textChanged$ = this._editorTextChanged$.pipe(
|
||||||
|
debounceTime(300), // prevent race condition with onPaste event
|
||||||
|
filter(text => text.length > 0),
|
||||||
|
tap(newText => {
|
||||||
|
const newDecorations = this._getDecorations(newText);
|
||||||
|
this._decorations = this.codeEditor.deltaDecorations(this._decorations, newDecorations);
|
||||||
|
this.diffValue = this.value;
|
||||||
|
this._loadingService.stop();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
this._sub$.add(textChanged$.subscribe());
|
||||||
|
}
|
||||||
|
|
||||||
get currentEntries(): string[] {
|
get currentEntries(): string[] {
|
||||||
return this.value.split('\n');
|
return this.value.split('\n');
|
||||||
@ -57,6 +73,16 @@ export class EditorComponent implements OnInit, OnChanges {
|
|||||||
return this.currentEntries.length;
|
return this.currentEntries.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this._sub$.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
onPaste(event: ClipboardEvent) {
|
||||||
|
if ((event.target as HTMLTextAreaElement).ariaRoleDescription === 'editor') {
|
||||||
|
this._loadingService.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
if (changes.diffEditorText) {
|
if (changes.diffEditorText) {
|
||||||
this._diffEditor?.getOriginalEditor().setValue(this.diffEditorText);
|
this._diffEditor?.getOriginalEditor().setValue(this.diffEditorText);
|
||||||
@ -92,17 +118,33 @@ export class EditorComponent implements OnInit, OnChanges {
|
|||||||
this._setTheme();
|
this._setTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Debounce()
|
_getDecorations(text: string) {
|
||||||
codeEditorTextChanged() {
|
const currentEntries = text.split('\n');
|
||||||
const newDecorations = this.currentEntries.filter(entry => this._isNew(entry)).map(entry => this._getDecoration(entry));
|
const newDecorations: IModelDeltaDecoration[] = [];
|
||||||
this._decorations = this.codeEditor.deltaDecorations(this._decorations, newDecorations);
|
|
||||||
this.diffValue = this.value;
|
for (let index = 0; index < currentEntries.length; index++) {
|
||||||
|
const entry = currentEntries.at(index)?.trim();
|
||||||
|
if (!entry || entry.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._initialEntriesSet.has(entry)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const line = index + 1;
|
||||||
|
newDecorations.push(this._getDecoration(entry, line));
|
||||||
|
}
|
||||||
|
|
||||||
|
return newDecorations;
|
||||||
}
|
}
|
||||||
|
|
||||||
revert() {
|
revert() {
|
||||||
this.value = this.initialEntries.join('\n');
|
this.value = this.initialEntries.join('\n');
|
||||||
|
this._initialEntriesSet = new Set<string>(this.initialEntries);
|
||||||
this.diffValue = this.value;
|
this.diffValue = this.value;
|
||||||
this._diffEditor?.getModifiedEditor().setValue(this.diffValue);
|
this._diffEditor?.getModifiedEditor().setValue(this.diffValue);
|
||||||
|
this._editorTextChanged$.next(this.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _defineThemes(): void {
|
private _defineThemes(): void {
|
||||||
@ -157,12 +199,7 @@ export class EditorComponent implements OnInit, OnChanges {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _isNew(entry: string): boolean {
|
private _getDecoration(entry: string, line: number): IModelDeltaDecoration {
|
||||||
return this.initialEntries.indexOf(entry) < 0 && entry?.trim().length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _getDecoration(entry: string): IModelDeltaDecoration {
|
|
||||||
const line = this.currentEntries.indexOf(entry) + 1;
|
|
||||||
const cssClass = entry.length < MIN_WORD_LENGTH ? 'too-short-marker' : 'changed-row-marker';
|
const cssClass = entry.length < MIN_WORD_LENGTH ? 'too-short-marker' : 'changed-row-marker';
|
||||||
const range = new monaco.Range(line, 1, line, 1);
|
const range = new monaco.Range(line, 1, line, 1);
|
||||||
|
|
||||||
|
|||||||
@ -13,10 +13,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="primaryAttribute" class="small-label">
|
<div *ngIf="ctx.primaryAttribute" class="small-label">
|
||||||
<div class="primary-attribute">
|
<div class="primary-attribute">
|
||||||
<span [matTooltip]="primaryAttribute" matTooltipPosition="above">
|
<span [matTooltip]="ctx.primaryAttribute" matTooltipPosition="above">
|
||||||
{{ primaryAttribute }}
|
{{ ctx.primaryAttribute }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
|
||||||
import { PrimaryFileAttributeService } from '@services/primary-file-attribute.service';
|
import { PrimaryFileAttributeService } from '@services/primary-file-attribute.service';
|
||||||
import { FileAttributes } from '@red/domain';
|
import { FileAttributes } from '@red/domain';
|
||||||
import { ContextComponent, ScrollableParentView, ScrollableParentViews } from '@iqser/common-ui';
|
import { ContextComponent, ScrollableParentView, ScrollableParentViews } from '@iqser/common-ui';
|
||||||
import { FileAttributesConfigMap, FileAttributesService } from '@services/entity-services/file-attributes.service';
|
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||||
import { tap } from 'rxjs/operators';
|
import { combineLatest, map, ReplaySubject } from 'rxjs';
|
||||||
import { BehaviorSubject, combineLatestWith, map } from 'rxjs';
|
|
||||||
|
|
||||||
interface PartialFile {
|
interface PartialFile {
|
||||||
readonly isError: boolean;
|
readonly isError: boolean;
|
||||||
@ -18,7 +17,7 @@ interface PartialFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface FileNameColumnContext {
|
interface FileNameColumnContext {
|
||||||
fileAttributesConfig: FileAttributesConfigMap;
|
primaryAttribute: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -30,36 +29,28 @@ interface FileNameColumnContext {
|
|||||||
export class FileNameColumnComponent extends ContextComponent<FileNameColumnContext> implements OnInit, OnChanges {
|
export class FileNameColumnComponent extends ContextComponent<FileNameColumnContext> implements OnInit, OnChanges {
|
||||||
@Input() file: PartialFile;
|
@Input() file: PartialFile;
|
||||||
@Input() dossierTemplateId: string;
|
@Input() dossierTemplateId: string;
|
||||||
primaryAttribute: string;
|
readonly #reloadAttribute = new ReplaySubject<void>(1);
|
||||||
readonly #reloadAttribute = new BehaviorSubject(null);
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _fileAttributeService: FileAttributesService,
|
private readonly _fileAttributeService: FileAttributesService,
|
||||||
private readonly _primaryFileAttributeService: PrimaryFileAttributeService,
|
private readonly _primaryFileAttributeService: PrimaryFileAttributeService,
|
||||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
const fileAttributesConfig$ = this._fileAttributeService.fileAttributesConfig$.pipe(
|
const primaryAttribute$ = combineLatest([this._fileAttributeService.fileAttributesConfig$, this.#reloadAttribute]).pipe(
|
||||||
combineLatestWith(this.#reloadAttribute),
|
map(() => this._primaryFileAttributeService.getPrimaryFileAttributeValue(this.file, this.dossierTemplateId)),
|
||||||
tap(() => {
|
|
||||||
this.primaryAttribute = this._primaryFileAttributeService.getPrimaryFileAttributeValue(this.file, this.dossierTemplateId);
|
|
||||||
this._changeDetectorRef.detectChanges();
|
|
||||||
}),
|
|
||||||
map(([attributes]) => attributes),
|
|
||||||
);
|
);
|
||||||
super._initContext({
|
super._initContext({
|
||||||
fileAttributesConfig: fileAttributesConfig$,
|
primaryAttribute: primaryAttribute$,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
if (!changes.file) {
|
if (changes.file) {
|
||||||
return;
|
this.#reloadAttribute.next();
|
||||||
}
|
}
|
||||||
this.#reloadAttribute.next(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get scrollableParentView(): ScrollableParentView {
|
get scrollableParentView(): ScrollableParentView {
|
||||||
|
|||||||
@ -1,18 +1,20 @@
|
|||||||
import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
|
import { Component, ElementRef, EventEmitter, inject, Input, OnChanges, Output, ViewChild } from '@angular/core';
|
||||||
import { CircleButtonTypes, getCurrentUser, List } from '@iqser/common-ui';
|
import { CircleButtonTypes, getCurrentUser, List } from '@iqser/common-ui';
|
||||||
import { DossiersDialogService } from '../../../shared-dossiers/services/dossiers-dialog.service';
|
import { DossiersDialogService } from '../../../shared-dossiers/services/dossiers-dialog.service';
|
||||||
import { ROLES } from '@users/roles';
|
import { ROLES } from '@users/roles';
|
||||||
import { User } from '@red/domain';
|
import { User } from '@red/domain';
|
||||||
|
import { UserService } from '@users/user.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-team-members',
|
selector: 'redaction-team-members',
|
||||||
templateUrl: './team-members.component.html',
|
templateUrl: './team-members.component.html',
|
||||||
styleUrls: ['./team-members.component.scss'],
|
styleUrls: ['./team-members.component.scss'],
|
||||||
})
|
})
|
||||||
export class TeamMembersComponent {
|
export class TeamMembersComponent implements OnChanges {
|
||||||
readonly circleButtonTypes = CircleButtonTypes;
|
readonly circleButtonTypes = CircleButtonTypes;
|
||||||
readonly roles = ROLES;
|
readonly roles = ROLES;
|
||||||
readonly currentUser = getCurrentUser<User>();
|
readonly currentUser = getCurrentUser<User>();
|
||||||
|
readonly userService = inject(UserService);
|
||||||
|
|
||||||
@Input() memberIds: List;
|
@Input() memberIds: List;
|
||||||
@Input() perLine: number;
|
@Input() perLine: number;
|
||||||
@ -29,6 +31,11 @@ export class TeamMembersComponent {
|
|||||||
|
|
||||||
constructor(private readonly _dialogService: DossiersDialogService) {}
|
constructor(private readonly _dialogService: DossiersDialogService) {}
|
||||||
|
|
||||||
|
ngOnChanges() {
|
||||||
|
this.memberIds ??= [];
|
||||||
|
this.memberIds = [...this.memberIds].sort((a, b) => this.userService.getName(a).localeCompare(this.userService.getName(b)));
|
||||||
|
}
|
||||||
|
|
||||||
get maxTeamMembersBeforeExpand(): number {
|
get maxTeamMembersBeforeExpand(): number {
|
||||||
return this.perLine - (this.canAdd ? 1 : 0);
|
return this.perLine - (this.canAdd ? 1 : 0);
|
||||||
}
|
}
|
||||||
@ -46,7 +53,7 @@ export class TeamMembersComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
canRemoveMember(userId: string) {
|
canRemoveMember(userId: string) {
|
||||||
return this.canRemove && this.unremovableMembers.indexOf(userId) === -1;
|
return this.canRemove && !this.unremovableMembers.includes(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
openEditDossierDialog(): void {
|
openEditDossierDialog(): void {
|
||||||
|
|||||||
@ -2,10 +2,10 @@ import { Component, Inject } from '@angular/core';
|
|||||||
import { Dossier, DownloadFileType, DownloadFileTypes, IReportTemplate, File, WorkflowFileStatuses } from '@red/domain';
|
import { Dossier, DownloadFileType, DownloadFileTypes, IReportTemplate, File, WorkflowFileStatuses } from '@red/domain';
|
||||||
import { downloadTypesForDownloadTranslations } from '@translations/download-types-translations';
|
import { downloadTypesForDownloadTranslations } from '@translations/download-types-translations';
|
||||||
import { ReportTemplateService } from '@services/report-template.service';
|
import { ReportTemplateService } from '@services/report-template.service';
|
||||||
import { AbstractControl, FormBuilder } from '@angular/forms';
|
import { AbstractControl } from '@angular/forms';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { DefaultColorsService } from '@services/entity-services/default-colors.service';
|
import { DefaultColorsService } from '@services/entity-services/default-colors.service';
|
||||||
import { IconButtonTypes, List } from '@iqser/common-ui';
|
import { BaseDialogComponent, IconButtonTypes, List } from '@iqser/common-ui';
|
||||||
|
|
||||||
export interface DownloadDialogData {
|
export interface DownloadDialogData {
|
||||||
readonly dossier: Dossier;
|
readonly dossier: Dossier;
|
||||||
@ -23,19 +23,21 @@ export interface DownloadDialogResult {
|
|||||||
templateUrl: './download-dialog.component.html',
|
templateUrl: './download-dialog.component.html',
|
||||||
styleUrls: ['./download-dialog.component.scss'],
|
styleUrls: ['./download-dialog.component.scss'],
|
||||||
})
|
})
|
||||||
export class DownloadDialogComponent {
|
export class DownloadDialogComponent extends BaseDialogComponent {
|
||||||
readonly iconButtonTypes = IconButtonTypes;
|
readonly iconButtonTypes = IconButtonTypes;
|
||||||
readonly downloadTypes: { key: DownloadFileType; label: string }[] = this._formDownloadTypes;
|
readonly downloadTypes: { key: DownloadFileType; label: string }[] = this._formDownloadTypes;
|
||||||
readonly availableReportTypes = this._availableReportTypes;
|
readonly availableReportTypes = this._availableReportTypes;
|
||||||
readonly form = this._getForm();
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _defaultColorsService: DefaultColorsService,
|
private readonly _defaultColorsService: DefaultColorsService,
|
||||||
private readonly _reportTemplateController: ReportTemplateService,
|
private readonly _reportTemplateController: ReportTemplateService,
|
||||||
private readonly _formBuilder: FormBuilder,
|
protected readonly _dialogRef: MatDialogRef<DownloadDialogComponent, DownloadDialogResult>,
|
||||||
private readonly _dialogRef: MatDialogRef<DownloadDialogComponent, DownloadDialogResult>,
|
|
||||||
@Inject(MAT_DIALOG_DATA) readonly data: DownloadDialogData,
|
@Inject(MAT_DIALOG_DATA) readonly data: DownloadDialogData,
|
||||||
) {}
|
) {
|
||||||
|
super(_dialogRef);
|
||||||
|
|
||||||
|
this.form = this._getForm();
|
||||||
|
}
|
||||||
|
|
||||||
get reportTypesLength() {
|
get reportTypesLength() {
|
||||||
return this.form.controls.reportTemplateIds?.value?.length || 0;
|
return this.form.controls.reportTemplateIds?.value?.length || 0;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { GenericService, List, QueryParam } from '@iqser/common-ui';
|
import { GenericService, LAST_CHECKED_OFFSET, List, QueryParam, ROOT_CHANGES_KEY } from '@iqser/common-ui';
|
||||||
import { Dossier, DossierStats, IDossierChanges } from '@red/domain';
|
import { Dossier, DossierStats, IDossierChanges } from '@red/domain';
|
||||||
import { forkJoin, Observable, of, throwError, timer } from 'rxjs';
|
import { forkJoin, Observable, of, throwError, timer } from 'rxjs';
|
||||||
import { catchError, filter, map, switchMap, take, tap } from 'rxjs/operators';
|
import { catchError, filter, map, switchMap, take, tap } from 'rxjs/operators';
|
||||||
@ -43,11 +43,11 @@ export class DossiersChangesService extends GenericService<Dossier> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hasChangesDetails$(): Observable<IDossierChanges> {
|
hasChangesDetails$(): Observable<IDossierChanges> {
|
||||||
const body = { value: this._lastCheckedForChanges.get('root') };
|
const body = { value: this._lastCheckedForChanges.get(ROOT_CHANGES_KEY) };
|
||||||
const dateBeforeRequest = new Date().toISOString();
|
const dateBeforeRequest = new Date(Date.now() - LAST_CHECKED_OFFSET).toISOString();
|
||||||
return this._post<IDossierChanges>(body, `${this._defaultModelPath}/changes/details`).pipe(
|
return this._post<IDossierChanges>(body, `${this._defaultModelPath}/changes/details`).pipe(
|
||||||
filter(changes => changes.length > 0),
|
filter(changes => changes.length > 0),
|
||||||
tap(() => this._lastCheckedForChanges.set('root', dateBeforeRequest)),
|
tap(() => this._lastCheckedForChanges.set(ROOT_CHANGES_KEY, dateBeforeRequest)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { forkJoin, Observable, of, throwError, zip } from 'rxjs';
|
import { firstValueFrom, forkJoin, Observable, of, throwError } from 'rxjs';
|
||||||
import { EntitiesService, List, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
|
import { EntitiesService, List, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
|
||||||
import { Dictionary, DictionaryEntryType, DictionaryEntryTypes, IDictionary, IUpdateDictionary, SuperTypes } from '@red/domain';
|
import { Dictionary, DictionaryEntryType, DictionaryEntryTypes, IDictionary, IUpdateDictionary, SuperTypes } from '@red/domain';
|
||||||
import { catchError, map, switchMap, tap } from 'rxjs/operators';
|
import { catchError, map, switchMap, tap } from 'rxjs/operators';
|
||||||
@ -95,7 +95,7 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
saveEntries(
|
async saveEntries(
|
||||||
entries: List,
|
entries: List,
|
||||||
initialEntries: List,
|
initialEntries: List,
|
||||||
dossierTemplateId: string,
|
dossierTemplateId: string,
|
||||||
@ -103,42 +103,52 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
|||||||
dossierId: string,
|
dossierId: string,
|
||||||
showToast = true,
|
showToast = true,
|
||||||
dictionaryEntryType = DictionaryEntryTypes.ENTRY,
|
dictionaryEntryType = DictionaryEntryTypes.ENTRY,
|
||||||
): Observable<unknown> {
|
removeCurrent = true,
|
||||||
const entriesToAdd = entries.map(e => e.trim()).filter(e => !!e);
|
) {
|
||||||
const deletedEntries = initialEntries.filter(e => !entries.includes(e));
|
const entriesToAdd: Array<string> = [];
|
||||||
console.log({ entriesToAdd, deletedEntries });
|
const initialEntriesSet = new Set(initialEntries);
|
||||||
// remove empty lines
|
let hasInvalidRows = false;
|
||||||
const invalidRowsExist = entriesToAdd.filter(e => e.length < MIN_WORD_LENGTH);
|
for (let i = 0; i < entries.length; i++) {
|
||||||
if (invalidRowsExist.length === 0) {
|
const entry = entries.at(i);
|
||||||
// can add at least 1 - block UI
|
if (!entry.trim() || initialEntriesSet.has(entry)) {
|
||||||
const obs: Observable<IDictionary>[] = [];
|
continue;
|
||||||
if (deletedEntries.length) {
|
|
||||||
obs.push(this._deleteEntries(deletedEntries, dossierTemplateId, type, dictionaryEntryType, dossierId));
|
|
||||||
}
|
}
|
||||||
if (entriesToAdd.filter(e => !initialEntries.includes(e)).length) {
|
hasInvalidRows ||= entry.length < MIN_WORD_LENGTH;
|
||||||
obs.push(this._addEntries(entriesToAdd, dossierTemplateId, type, dictionaryEntryType, dossierId));
|
entriesToAdd.push(entry);
|
||||||
}
|
|
||||||
return zip(obs).pipe(
|
|
||||||
switchMap(dictionary => this._dossierTemplateStatsService.getFor([dossierTemplateId]).pipe(map(() => dictionary))),
|
|
||||||
tap({
|
|
||||||
next: () => {
|
|
||||||
if (showToast) {
|
|
||||||
this._toaster.success(_('dictionary-overview.success.generic'));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: error => {
|
|
||||||
if (error.status === 400) {
|
|
||||||
this._toaster.error(_('dictionary-overview.error.400'));
|
|
||||||
} else {
|
|
||||||
this._toaster.error(_('dictionary-overview.error.generic'));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
this._toaster.error(_('dictionary-overview.error.entries-too-short'));
|
if (hasInvalidRows) {
|
||||||
|
this._toaster.error(_('dictionary-overview.error.entries-too-short'));
|
||||||
|
|
||||||
return throwError(() => 'Entries too short');
|
throw new Error('Entries too short');
|
||||||
|
}
|
||||||
|
const deletedEntries: Array<string> = [];
|
||||||
|
const entriesSet = new Set(entries);
|
||||||
|
for (let i = 0; i < initialEntries.length; i++) {
|
||||||
|
const entry = initialEntries.at(i);
|
||||||
|
if (entriesSet.has(entry)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
deletedEntries.push(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (deletedEntries.length) {
|
||||||
|
await this._deleteEntries(deletedEntries, dossierTemplateId, type, dictionaryEntryType, dossierId);
|
||||||
|
}
|
||||||
|
if (entriesToAdd.length) {
|
||||||
|
await this._addEntries(entriesToAdd, dossierTemplateId, type, dictionaryEntryType, dossierId, removeCurrent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showToast) {
|
||||||
|
this._toaster.success(_('dictionary-overview.success.generic'));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if ((error as HttpErrorResponse).status === 400) {
|
||||||
|
this._toaster.error(_('dictionary-overview.error.400'));
|
||||||
|
} else {
|
||||||
|
this._toaster.error(_('dictionary-overview.error.generic'));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hasManualType(dossierTemplateId: string): boolean {
|
hasManualType(dossierTemplateId: string): boolean {
|
||||||
@ -248,14 +258,15 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
|||||||
type: string,
|
type: string,
|
||||||
dictionaryEntryType: DictionaryEntryType,
|
dictionaryEntryType: DictionaryEntryType,
|
||||||
dossierId: string,
|
dossierId: string,
|
||||||
|
removeCurrent = true,
|
||||||
) {
|
) {
|
||||||
const queryParams: List<QueryParam> = [
|
const queryParams: List<QueryParam> = [
|
||||||
{ key: 'dossierId', value: dossierId },
|
{ key: 'dossierId', value: dossierId },
|
||||||
{ key: 'dictionaryEntryType', value: dictionaryEntryType },
|
{ key: 'dictionaryEntryType', value: dictionaryEntryType },
|
||||||
{ key: 'removeCurrent', value: true },
|
{ key: 'removeCurrent', value: removeCurrent },
|
||||||
];
|
];
|
||||||
const url = `${this._defaultModelPath}/${type}/${dossierTemplateId}`;
|
const url = `${this._defaultModelPath}/${type}/${dossierTemplateId}`;
|
||||||
return this._post(entries, url, queryParams);
|
return firstValueFrom(this._post(entries, url, queryParams));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -273,6 +284,6 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
|||||||
? [{ key: 'dossierId', value: dossierId }]
|
? [{ key: 'dossierId', value: dossierId }]
|
||||||
: [{ key: 'dictionaryEntryType', value: dictionaryEntryType }];
|
: [{ key: 'dictionaryEntryType', value: dictionaryEntryType }];
|
||||||
const url = `${this._defaultModelPath}/delete/${type}/${dossierTemplateId}`;
|
const url = `${this._defaultModelPath}/delete/${type}/${dossierTemplateId}`;
|
||||||
return this._post(entries, url, queryParams);
|
return firstValueFrom(this._post(entries, url, queryParams));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,8 +50,8 @@ export class LicenseService extends GenericService<ILicenseReport> {
|
|||||||
readonly selectedLicense$: Observable<ILicense>;
|
readonly selectedLicense$: Observable<ILicense>;
|
||||||
activeLicenseId: string;
|
activeLicenseId: string;
|
||||||
totalLicensedNumberOfPages = 0;
|
totalLicensedNumberOfPages = 0;
|
||||||
currentInfo: ILicenseReport = {};
|
currentLicenseInfo: ILicenseReport = {};
|
||||||
annualInfo: ILicenseReport = {};
|
allLicensesInfo: ILicenseReport = {};
|
||||||
unlicensedPages = 0;
|
unlicensedPages = 0;
|
||||||
analyzedPagesInCurrentLicensingPeriod = 0;
|
analyzedPagesInCurrentLicensingPeriod = 0;
|
||||||
protected readonly _defaultModelPath = 'report';
|
protected readonly _defaultModelPath = 'report';
|
||||||
@ -96,27 +96,27 @@ export class LicenseService extends GenericService<ILicenseReport> {
|
|||||||
const startDate = dayjs(license.validFrom);
|
const startDate = dayjs(license.validFrom);
|
||||||
const endDate = dayjs(license.validUntil);
|
const endDate = dayjs(license.validUntil);
|
||||||
|
|
||||||
const currentConfig = {
|
const currentLicenseConfig = {
|
||||||
startDate: startDate.toDate(),
|
startDate: startDate.toDate(),
|
||||||
endDate: endDate.toDate(),
|
endDate: endDate.toDate(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const thisYearConfig = {
|
const allLicensesConfig = {
|
||||||
startDate: `${startDate.year()}-01-01T00:00:00.000Z`,
|
startDate: '2020-01-01T00:00:00.000Z',
|
||||||
endDate: `${startDate.year()}-12-31T00:00:00.000Z`,
|
endDate: '2023-12-31T00:00:00.000Z',
|
||||||
};
|
};
|
||||||
const configs = [currentConfig, thisYearConfig];
|
|
||||||
|
const configs = [currentLicenseConfig, allLicensesConfig];
|
||||||
const reports = configs.map(config => this.getReport(config));
|
const reports = configs.map(config => this.getReport(config));
|
||||||
|
|
||||||
[this.currentInfo, this.annualInfo] = await Promise.all(reports);
|
[this.currentLicenseInfo, this.allLicensesInfo] = await Promise.all(reports);
|
||||||
|
|
||||||
if (this.currentInfo.numberOfAnalyzedPages > this.totalLicensedNumberOfPages) {
|
if (this.currentLicenseInfo.numberOfAnalyzedPages > this.totalLicensedNumberOfPages) {
|
||||||
this.unlicensedPages = this.currentInfo.numberOfAnalyzedPages - this.totalLicensedNumberOfPages;
|
this.unlicensedPages = this.currentLicenseInfo.numberOfAnalyzedPages - this.totalLicensedNumberOfPages;
|
||||||
this.analyzedPagesInCurrentLicensingPeriod = this.totalLicensedNumberOfPages;
|
|
||||||
} else {
|
} else {
|
||||||
this.unlicensedPages = 0;
|
this.unlicensedPages = 0;
|
||||||
this.analyzedPagesInCurrentLicensingPeriod = this.currentInfo.numberOfAnalyzedPages;
|
|
||||||
}
|
}
|
||||||
|
this.analyzedPagesInCurrentLicensingPeriod = this.currentLicenseInfo.numberOfAnalyzedPages;
|
||||||
}
|
}
|
||||||
|
|
||||||
getTotalLicensedNumberOfPages(license: ILicense) {
|
getTotalLicensedNumberOfPages(license: ILicense) {
|
||||||
|
|||||||
@ -274,11 +274,11 @@ export class PermissionsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
canHardDeleteDossier(dossier: IDossier): boolean {
|
canHardDeleteDossier(dossier: IDossier): boolean {
|
||||||
return this._iqserPermissionsService.has(ROLES.dossiers.delete) && this.isOwner(dossier);
|
return this.canSoftDeleteDossier(dossier);
|
||||||
}
|
}
|
||||||
|
|
||||||
canRestoreDossier(dossier: IDossier): boolean {
|
canRestoreDossier(dossier: IDossier): boolean {
|
||||||
return this._iqserPermissionsService.has(ROLES.dossiers.delete) && (this.isOwner(dossier) || this.isDossierMember(dossier));
|
return this.canSoftDeleteDossier(dossier);
|
||||||
}
|
}
|
||||||
|
|
||||||
canCreateDossier(dossierTemplate: DossierTemplate | DashboardStats): boolean {
|
canCreateDossier(dossierTemplate: DossierTemplate | DashboardStats): boolean {
|
||||||
@ -349,6 +349,10 @@ export class PermissionsService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canRotatePage(file: File) {
|
||||||
|
return this.isFileAssignee(file) && this._iqserPermissionsService.has(ROLES.files.rotatePage);
|
||||||
|
}
|
||||||
|
|
||||||
canDownloadRedactedFile() {
|
canDownloadRedactedFile() {
|
||||||
return this._iqserPermissionsService.has(ROLES.files.processDownload);
|
return this._iqserPermissionsService.has(ROLES.files.processDownload);
|
||||||
}
|
}
|
||||||
|
|||||||
12
apps/red-ui/src/app/utils/local-storage.ts
Normal file
12
apps/red-ui/src/app/utils/local-storage.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export function setLocalStorageDataByFileId(fileId: string, key: string, value: string | number | boolean): void {
|
||||||
|
let data = localStorage.getItem(key) ?? '{}';
|
||||||
|
data = JSON.parse(data);
|
||||||
|
data[fileId] = value;
|
||||||
|
localStorage.setItem(key, JSON.stringify(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLocalStorageDataByFileId(fileId: string, key: string): string | number | boolean {
|
||||||
|
let data = localStorage.getItem(key) ?? '{}';
|
||||||
|
data = JSON.parse(data);
|
||||||
|
return data[fileId];
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"ADMIN_CONTACT_NAME": null,
|
"ADMIN_CONTACT_NAME": null,
|
||||||
"ADMIN_CONTACT_URL": null,
|
"ADMIN_CONTACT_URL": null,
|
||||||
"API_URL": "https://dev-08.iqser.cloud/redaction-gateway-v1",
|
"API_URL": "https://red-staging.iqser.cloud/redaction-gateway-v1",
|
||||||
"APP_NAME": "RedactManager",
|
"APP_NAME": "RedactManager",
|
||||||
"AUTO_READ_TIME": 3,
|
"AUTO_READ_TIME": 3,
|
||||||
"BACKEND_APP_VERSION": "4.4.40",
|
"BACKEND_APP_VERSION": "4.4.40",
|
||||||
@ -11,7 +11,7 @@
|
|||||||
"MAX_RETRIES_ON_SERVER_ERROR": 3,
|
"MAX_RETRIES_ON_SERVER_ERROR": 3,
|
||||||
"OAUTH_CLIENT_ID": "redaction",
|
"OAUTH_CLIENT_ID": "redaction",
|
||||||
"OAUTH_IDP_HINT": null,
|
"OAUTH_IDP_HINT": null,
|
||||||
"OAUTH_URL": "https://dev-08.iqser.cloud/auth/realms/redaction",
|
"OAUTH_URL": "https://red-staging.iqser.cloud/auth/realms/redaction",
|
||||||
"RECENT_PERIOD_IN_HOURS": 24,
|
"RECENT_PERIOD_IN_HOURS": 24,
|
||||||
"SELECTION_MODE": "structural",
|
"SELECTION_MODE": "structural",
|
||||||
"MANUAL_BASE_URL": "https://docs.redactmanager.com/preview",
|
"MANUAL_BASE_URL": "https://docs.redactmanager.com/preview",
|
||||||
|
|||||||
@ -526,5 +526,11 @@
|
|||||||
"de": "",
|
"de": "",
|
||||||
"it": "",
|
"it": "",
|
||||||
"fr": ""
|
"fr": ""
|
||||||
|
},
|
||||||
|
"edit-file-attributes": {
|
||||||
|
"en": "/en/index-en.html?contextId=document_list",
|
||||||
|
"de": "",
|
||||||
|
"it": "",
|
||||||
|
"fr": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1633,7 +1633,6 @@
|
|||||||
"table-header": "{length} {length, plural, one{Begründung} other{Begründung}}"
|
"table-header": "{length} {length, plural, one{Begründung} other{Begründung}}"
|
||||||
},
|
},
|
||||||
"license-info-screen": {
|
"license-info-screen": {
|
||||||
"analyzed-pages": "Analysierte Seiten",
|
|
||||||
"backend-version": "Backend-Version der Anwendung",
|
"backend-version": "Backend-Version der Anwendung",
|
||||||
"chart": {
|
"chart": {
|
||||||
"cumulative": "Seiten insgesamt",
|
"cumulative": "Seiten insgesamt",
|
||||||
@ -1666,6 +1665,7 @@
|
|||||||
"inactive": ""
|
"inactive": ""
|
||||||
},
|
},
|
||||||
"total-analyzed": "Seit {date} insgesamt analysierte Seiten",
|
"total-analyzed": "Seit {date} insgesamt analysierte Seiten",
|
||||||
|
"total-ocr-analyzed": "",
|
||||||
"unlicensed-analyzed": "Über Lizenz hinaus analysierte Seiten",
|
"unlicensed-analyzed": "Über Lizenz hinaus analysierte Seiten",
|
||||||
"usage-details": "Nutzungsdetails"
|
"usage-details": "Nutzungsdetails"
|
||||||
},
|
},
|
||||||
@ -1998,6 +1998,17 @@
|
|||||||
"annotations": "",
|
"annotations": "",
|
||||||
"title": ""
|
"title": ""
|
||||||
},
|
},
|
||||||
|
"false-positive-dialog": {
|
||||||
|
"actions": {
|
||||||
|
"cancel": "",
|
||||||
|
"save": ""
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"comment": "",
|
||||||
|
"body-text": ""
|
||||||
|
},
|
||||||
|
"header": ""
|
||||||
|
},
|
||||||
"rules-screen": {
|
"rules-screen": {
|
||||||
"error": {
|
"error": {
|
||||||
"generic": "Es ist ein Fehler aufgetreten ... Die Regeln konnten nicht aktualisiert werden!"
|
"generic": "Es ist ein Fehler aufgetreten ... Die Regeln konnten nicht aktualisiert werden!"
|
||||||
|
|||||||
@ -1633,7 +1633,6 @@
|
|||||||
"table-header": "{length} {length, plural, one{justification} other{justifications}}"
|
"table-header": "{length} {length, plural, one{justification} other{justifications}}"
|
||||||
},
|
},
|
||||||
"license-info-screen": {
|
"license-info-screen": {
|
||||||
"analyzed-pages": "Analyzed pages",
|
|
||||||
"backend-version": "Backend Application Version",
|
"backend-version": "Backend Application Version",
|
||||||
"chart": {
|
"chart": {
|
||||||
"cumulative": "Cumulative Pages",
|
"cumulative": "Cumulative Pages",
|
||||||
@ -1643,7 +1642,7 @@
|
|||||||
},
|
},
|
||||||
"copyright-claim-text": "Copyright © 2020 - {currentYear} knecon AG (powered by IQSER)",
|
"copyright-claim-text": "Copyright © 2020 - {currentYear} knecon AG (powered by IQSER)",
|
||||||
"copyright-claim-title": "Copyright Claim",
|
"copyright-claim-title": "Copyright Claim",
|
||||||
"current-analyzed": "Analyzed Pages in Current Licensing Period",
|
"current-analyzed": "Analyzed Pages in Licensing Period",
|
||||||
"custom-app-title": "Custom Application Title",
|
"custom-app-title": "Custom Application Title",
|
||||||
"email-report": "Email Report",
|
"email-report": "Email Report",
|
||||||
"email": {
|
"email": {
|
||||||
@ -1656,16 +1655,17 @@
|
|||||||
"end-user-license-text": "The use of this product is subject to the terms of the Redaction End User Agreement, unless otherwise specified therein.",
|
"end-user-license-text": "The use of this product is subject to the terms of the Redaction End User Agreement, unless otherwise specified therein.",
|
||||||
"end-user-license-title": "End User License Agreement",
|
"end-user-license-title": "End User License Agreement",
|
||||||
"license-title": "License Title",
|
"license-title": "License Title",
|
||||||
"licensed-page-count": "Number of licensed pages",
|
"licensed-page-count": "Licensed Pages",
|
||||||
"licensed-to": "Licensed to",
|
"licensed-to": "Licensed to",
|
||||||
"licensing-details": "Licensing Details",
|
"licensing-details": "Licensing Details",
|
||||||
"licensing-period": "Licensing Period",
|
"licensing-period": "Licensing Period",
|
||||||
"ocr-analyzed-pages": "OCR Analyzed Pages",
|
"ocr-analyzed-pages": "OCR Processed Pages in Licensing Period",
|
||||||
"status": {
|
"status": {
|
||||||
"active": "Active",
|
"active": "Active",
|
||||||
"inactive": "Inactive"
|
"inactive": "Inactive"
|
||||||
},
|
},
|
||||||
"total-analyzed": "Total Analyzed Pages Since {date}",
|
"total-analyzed": "Total Analyzed Pages",
|
||||||
|
"total-ocr-analyzed": "Total OCR Processed Pages",
|
||||||
"unlicensed-analyzed": "Unlicensed Analyzed Pages",
|
"unlicensed-analyzed": "Unlicensed Analyzed Pages",
|
||||||
"usage-details": "Usage Details"
|
"usage-details": "Usage Details"
|
||||||
},
|
},
|
||||||
@ -1847,7 +1847,7 @@
|
|||||||
"save": "Save changes"
|
"save": "Save changes"
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
"auto-expand-filters-on-action": "Auto expand filters on my actions",
|
"auto-expand-filters-on-action": "Auto-expand filters on my actions",
|
||||||
"load-all-annotations-warning": "Warning regarding loading all annotations at once in file preview",
|
"load-all-annotations-warning": "Warning regarding loading all annotations at once in file preview",
|
||||||
"show-suggestions-in-preview": "Display suggestions in document preview",
|
"show-suggestions-in-preview": "Display suggestions in document preview",
|
||||||
"unapproved-suggestions-warning": "Warning regarding unapproved suggestions in document Preview mode"
|
"unapproved-suggestions-warning": "Warning regarding unapproved suggestions in document Preview mode"
|
||||||
@ -1998,6 +1998,17 @@
|
|||||||
"annotations": "",
|
"annotations": "",
|
||||||
"title": "Structured Component Management"
|
"title": "Structured Component Management"
|
||||||
},
|
},
|
||||||
|
"false-positive-dialog": {
|
||||||
|
"actions": {
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"save": "Yes, proceed"
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"comment": "Comment",
|
||||||
|
"body-text": "''{value}'' is a false positive in this context: {context}"
|
||||||
|
},
|
||||||
|
"header": "False Positive"
|
||||||
|
},
|
||||||
"rules-screen": {
|
"rules-screen": {
|
||||||
"error": {
|
"error": {
|
||||||
"generic": "Something went wrong... Rules update failed!"
|
"generic": "Something went wrong... Rules update failed!"
|
||||||
|
|||||||
@ -1633,7 +1633,6 @@
|
|||||||
"table-header": "{length} {length, plural, one{Begründung} other{Begründung}}"
|
"table-header": "{length} {length, plural, one{Begründung} other{Begründung}}"
|
||||||
},
|
},
|
||||||
"license-info-screen": {
|
"license-info-screen": {
|
||||||
"analyzed-pages": "Analysierte Seiten",
|
|
||||||
"backend-version": "Backend-Version der Anwendung",
|
"backend-version": "Backend-Version der Anwendung",
|
||||||
"chart": {
|
"chart": {
|
||||||
"cumulative": "Seiten insgesamt",
|
"cumulative": "Seiten insgesamt",
|
||||||
@ -1666,6 +1665,7 @@
|
|||||||
"inactive": ""
|
"inactive": ""
|
||||||
},
|
},
|
||||||
"total-analyzed": "Seit {date} insgesamt analysierte Seiten",
|
"total-analyzed": "Seit {date} insgesamt analysierte Seiten",
|
||||||
|
"total-ocr-analyzed": "",
|
||||||
"unlicensed-analyzed": "Über Lizenz hinaus analysierte Seiten",
|
"unlicensed-analyzed": "Über Lizenz hinaus analysierte Seiten",
|
||||||
"usage-details": "Nutzungsdetails"
|
"usage-details": "Nutzungsdetails"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1633,7 +1633,6 @@
|
|||||||
"table-header": "{length} {length, plural, one{justification} other{justifications}}"
|
"table-header": "{length} {length, plural, one{justification} other{justifications}}"
|
||||||
},
|
},
|
||||||
"license-info-screen": {
|
"license-info-screen": {
|
||||||
"analyzed-pages": "Analyzed pages",
|
|
||||||
"backend-version": "Backend Application Version",
|
"backend-version": "Backend Application Version",
|
||||||
"chart": {
|
"chart": {
|
||||||
"cumulative": "Cumulative Pages",
|
"cumulative": "Cumulative Pages",
|
||||||
@ -1643,7 +1642,7 @@
|
|||||||
},
|
},
|
||||||
"copyright-claim-text": "Copyright © 2020 - {currentYear} knecon AG (powered by IQSER)",
|
"copyright-claim-text": "Copyright © 2020 - {currentYear} knecon AG (powered by IQSER)",
|
||||||
"copyright-claim-title": "Copyright Claim",
|
"copyright-claim-title": "Copyright Claim",
|
||||||
"current-analyzed": "Analyzed Pages in Current Licensing Period",
|
"current-analyzed": "Analyzed Pages in Licensing Period",
|
||||||
"custom-app-title": "Custom Application Title",
|
"custom-app-title": "Custom Application Title",
|
||||||
"email-report": "Email Report",
|
"email-report": "Email Report",
|
||||||
"email": {
|
"email": {
|
||||||
@ -1656,16 +1655,17 @@
|
|||||||
"end-user-license-text": "The use of this product is subject to the terms of the Component End User Agreement, unless otherwise specified therein.",
|
"end-user-license-text": "The use of this product is subject to the terms of the Component End User Agreement, unless otherwise specified therein.",
|
||||||
"end-user-license-title": "End User License Agreement",
|
"end-user-license-title": "End User License Agreement",
|
||||||
"license-title": "License Title",
|
"license-title": "License Title",
|
||||||
"licensed-page-count": "Number of licensed pages",
|
"licensed-page-count": "Licensed pages",
|
||||||
"licensed-to": "Licensed to",
|
"licensed-to": "Licensed to",
|
||||||
"licensing-details": "Licensing Details",
|
"licensing-details": "Licensing Details",
|
||||||
"licensing-period": "Licensing Period",
|
"licensing-period": "Licensing Period",
|
||||||
"ocr-analyzed-pages": "OCR Analyzed Pages",
|
"ocr-analyzed-pages": "OCR Processed Pages in Licensing Period",
|
||||||
"status": {
|
"status": {
|
||||||
"active": "Active",
|
"active": "Active",
|
||||||
"inactive": "Inactive"
|
"inactive": "Inactive"
|
||||||
},
|
},
|
||||||
"total-analyzed": "Total Analyzed Pages Since {date}",
|
"total-analyzed": "Total Analyzed Pages Since {date}",
|
||||||
|
"total-ocr-analyzed": "Total OCR Processed Pages Since {date}",
|
||||||
"unlicensed-analyzed": "Unlicensed Analyzed Pages",
|
"unlicensed-analyzed": "Unlicensed Analyzed Pages",
|
||||||
"usage-details": "Usage Details"
|
"usage-details": "Usage Details"
|
||||||
},
|
},
|
||||||
|
|||||||
1
bamboo-specs/.gitignore
vendored
1
bamboo-specs/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
target/
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<parent>
|
|
||||||
<groupId>com.atlassian.bamboo</groupId>
|
|
||||||
<artifactId>bamboo-specs-parent</artifactId>
|
|
||||||
<version>8.1.1</version>
|
|
||||||
<relativePath/>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>bamboo-specs</artifactId>
|
|
||||||
<version>1.0.0-SNAPSHOT</version>
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.atlassian.bamboo</groupId>
|
|
||||||
<artifactId>bamboo-specs-api</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.atlassian.bamboo</groupId>
|
|
||||||
<artifactId>bamboo-specs</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Test dependencies -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>junit</groupId>
|
|
||||||
<artifactId>junit</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
<!-- run 'mvn test' to perform offline validation of the plan -->
|
|
||||||
<!-- run 'mvn -Ppublish-specs' to upload the plan to your Bamboo server -->
|
|
||||||
</project>
|
|
||||||
@ -1,128 +0,0 @@
|
|||||||
package buildjob;
|
|
||||||
|
|
||||||
import com.atlassian.bamboo.specs.api.BambooSpec;
|
|
||||||
import com.atlassian.bamboo.specs.api.builders.BambooKey;
|
|
||||||
import com.atlassian.bamboo.specs.api.builders.credentials.SharedCredentialsIdentifier;
|
|
||||||
import com.atlassian.bamboo.specs.api.builders.credentials.SharedCredentialsScope;
|
|
||||||
import com.atlassian.bamboo.specs.api.builders.docker.DockerConfiguration;
|
|
||||||
import com.atlassian.bamboo.specs.api.builders.permission.PermissionType;
|
|
||||||
import com.atlassian.bamboo.specs.api.builders.permission.Permissions;
|
|
||||||
import com.atlassian.bamboo.specs.api.builders.permission.PlanPermissions;
|
|
||||||
import com.atlassian.bamboo.specs.api.builders.plan.Job;
|
|
||||||
import com.atlassian.bamboo.specs.api.builders.plan.Plan;
|
|
||||||
import com.atlassian.bamboo.specs.api.builders.plan.PlanIdentifier;
|
|
||||||
import com.atlassian.bamboo.specs.api.builders.plan.Stage;
|
|
||||||
import com.atlassian.bamboo.specs.api.builders.plan.artifact.Artifact;
|
|
||||||
import com.atlassian.bamboo.specs.api.builders.plan.branches.BranchCleanup;
|
|
||||||
import com.atlassian.bamboo.specs.api.builders.plan.branches.PlanBranchManagement;
|
|
||||||
import com.atlassian.bamboo.specs.api.builders.project.Project;
|
|
||||||
import com.atlassian.bamboo.specs.api.builders.repository.VcsRepositoryIdentifier;
|
|
||||||
import com.atlassian.bamboo.specs.builders.repository.git.GitRepository;
|
|
||||||
import com.atlassian.bamboo.specs.builders.task.*;
|
|
||||||
import com.atlassian.bamboo.specs.builders.trigger.BitbucketServerTrigger;
|
|
||||||
import com.atlassian.bamboo.specs.model.task.ScriptTaskProperties;
|
|
||||||
import com.atlassian.bamboo.specs.util.BambooServer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plan configuration for Bamboo.
|
|
||||||
* Learn more on: <a href="https://confluence.atlassian.com/display/BAMBOO/Bamboo+Specs">https://confluence.atlassian.com/display/BAMBOO/Bamboo+Specs</a>
|
|
||||||
*/
|
|
||||||
@BambooSpec
|
|
||||||
public class PlanSpec {
|
|
||||||
/**
|
|
||||||
* Run main to publish plan on Bamboo
|
|
||||||
*/
|
|
||||||
public static void main(final String[] args) throws Exception {
|
|
||||||
//By default credentials are read from the '.credentials' file.
|
|
||||||
BambooServer bambooServer = new BambooServer("http://localhost:8085");
|
|
||||||
|
|
||||||
Plan buildPlan = new PlanSpec().createDockerBuildPlan();
|
|
||||||
bambooServer.publish(buildPlan);
|
|
||||||
PlanPermissions buildPlanPermissions = new PlanSpec().createPlanPermission(buildPlan.getIdentifier());
|
|
||||||
bambooServer.publish(buildPlanPermissions);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private PlanPermissions createPlanPermission(PlanIdentifier planIdentifier) {
|
|
||||||
Permissions permission = new Permissions()
|
|
||||||
.userPermissions("atlbamboo", PermissionType.EDIT, PermissionType.VIEW, PermissionType.ADMIN, PermissionType.CLONE, PermissionType.BUILD)
|
|
||||||
.userPermissions("tbejan", PermissionType.ADMIN, PermissionType.EDIT, PermissionType.VIEW, PermissionType.CLONE, PermissionType.BUILD)
|
|
||||||
.groupPermissions("devplant", PermissionType.EDIT, PermissionType.VIEW, PermissionType.BUILD)
|
|
||||||
.groupPermissions("Documentation", PermissionType.VIEW)
|
|
||||||
.loggedInUserPermissions(PermissionType.VIEW).anonymousUserPermissionView();
|
|
||||||
return new PlanPermissions(planIdentifier.getProjectKey(), planIdentifier.getPlanKey()).permissions(permission);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Project project() {
|
|
||||||
return new Project().name("RED").key(new BambooKey("RED"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Plan createDockerBuildPlan() {
|
|
||||||
return new Plan(project(), "Redaction UI", new BambooKey("UI"))
|
|
||||||
.description("Docker build for Redaction UI.")
|
|
||||||
.stages(new Stage("UI Build Stage")
|
|
||||||
.jobs(creatGinCloudPlatformImagesJob("red-ui")))
|
|
||||||
.stages(new Stage("Release")
|
|
||||||
.manual(true)
|
|
||||||
.jobs(createRelease()))
|
|
||||||
.linkedRepositories("RED / ui")
|
|
||||||
.linkedRepositories("Shared Libraries / common-ui")
|
|
||||||
.triggers(new BitbucketServerTrigger().selectedTriggeringRepositories(new VcsRepositoryIdentifier("RED / ui")))
|
|
||||||
.planBranchManagement(new PlanBranchManagement().createForVcsBranch().delete(new BranchCleanup().whenInactiveInRepositoryAfterDays(30)).notificationForCommitters());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Job creatGinCloudPlatformImagesJob(String project) {
|
|
||||||
return new Job("Build Job UI" , new BambooKey("UIBUILD"))
|
|
||||||
.tasks(
|
|
||||||
new CleanWorkingDirectoryTask().description("My clean working directory task"),
|
|
||||||
// Checkout
|
|
||||||
new VcsCheckoutTask().description("Checkout Default Repository")
|
|
||||||
.checkoutItems(new CheckoutItem().defaultRepository().path("redaction-ui")),
|
|
||||||
|
|
||||||
// Build
|
|
||||||
new ScriptTask().description("Build")
|
|
||||||
.location(ScriptTaskProperties.Location.FILE)
|
|
||||||
.workingSubdirectory("redaction-ui")
|
|
||||||
.fileFromPath("bamboo-specs/src/main/resources/scripts/build.sh")
|
|
||||||
.environmentVariables(
|
|
||||||
"PROJECT=\"" + project + "\" " +
|
|
||||||
"BAMBOO_DOWNLOAD_PASS=\"${bamboo.bamboo_download_pass}\" " +
|
|
||||||
"BAMBOO_DOWNLOAD_USER=\"${bamboo.bamboo_download_user}\" "),
|
|
||||||
// read version from artifact
|
|
||||||
new InjectVariablesTask().path("redaction-ui/version.properties"),
|
|
||||||
// commit release
|
|
||||||
new VcsCommitTask().commitMessage("chore(release)").repository("RED / ui"),
|
|
||||||
// create tag with this version
|
|
||||||
new VcsTagTask().tagName("${bamboo.inject.APP_VERSION}").repository("RED / ui")
|
|
||||||
).dockerConfiguration(
|
|
||||||
new DockerConfiguration().image("nexus.iqser.com:5001/infra/release_build:4.2.0")
|
|
||||||
.volume("/var/run/docker.sock", "/var/run/docker.sock"))
|
|
||||||
.artifacts(new Artifact("version").location(".").copyPattern("**/version.properties").shared(true),
|
|
||||||
new Artifact("paligo-theme.tar.gz").location(".").copyPattern("**/paligo-theme.tar.gz").shared(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Job createRelease() {
|
|
||||||
return new Job("Create Release", new BambooKey("CRLS"))
|
|
||||||
.tasks(
|
|
||||||
new CleanWorkingDirectoryTask().description("My clean working directory task"),
|
|
||||||
new VcsCheckoutTask().description("Checkout Default Repository")
|
|
||||||
.checkoutItems(new CheckoutItem().defaultRepository()).cleanCheckout(true),
|
|
||||||
|
|
||||||
new ArtifactDownloaderTask().description("Download version artifact")
|
|
||||||
.sourcePlan(new PlanIdentifier("RED", "UI"))
|
|
||||||
.artifacts(new DownloadItem().artifact("version")),
|
|
||||||
// read version from artifact
|
|
||||||
new InjectVariablesTask().path("redaction-ui/version.properties"),
|
|
||||||
|
|
||||||
new ScriptTask().description("checkout tag").inlineBody("git checkout tags/${bamboo.inject.APP_VERSION}"),
|
|
||||||
|
|
||||||
|
|
||||||
new VcsBranchTask().branchName("release/${bamboo.inject.APP_VERSION}").repository("RED / ui"))
|
|
||||||
.dockerConfiguration(new DockerConfiguration().image("nexus.iqser.com:5001/infra/release_build:2.9.1")
|
|
||||||
.volume("/var/run/docker.sock", "/var/run/docker.sock"));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,62 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
|
|
||||||
imageName="nexus.iqser.com:5001/red/$PROJECT"
|
|
||||||
dockerfileLocation="docker/$PROJECT/Dockerfile"
|
|
||||||
|
|
||||||
echo "submodule status"
|
|
||||||
git submodule status
|
|
||||||
|
|
||||||
echo "On branch $bamboo_planRepository_branchName building project $PROJECT"
|
|
||||||
# shellcheck disable=SC2154
|
|
||||||
if [[ "$bamboo_planRepository_branchName" == "master" ]]
|
|
||||||
then
|
|
||||||
./versions.sh minor
|
|
||||||
version=$(jq -r '.version' < package.json)
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
if [[ "$bamboo_planRepository_branchName" == release* ]]
|
|
||||||
then
|
|
||||||
./versions.sh patch
|
|
||||||
version=$(jq -r '.version' < package.json)
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
echo "Building version $version"
|
|
||||||
docker build --build-arg bamboo_sonarqube_api_token_secret=${bamboo_sonarqube_api_token_secret} -t "$imageName":latest -f "$dockerfileLocation" .
|
|
||||||
|
|
||||||
if [[ -n ${version+z} ]]
|
|
||||||
then
|
|
||||||
|
|
||||||
echo "APP_VERSION=${version}" > version.properties
|
|
||||||
|
|
||||||
echo "Publishing Images with version $version"
|
|
||||||
echo "$BAMBOO_DOWNLOAD_PASS" | docker login -u "$BAMBOO_DOWNLOAD_USER" --password-stdin nexus.iqser.com:5001
|
|
||||||
|
|
||||||
# re-build intermediate build stage from layer cache, run image and get artifacts ( paligo theme )
|
|
||||||
docker build --build-arg bamboo_sonarqube_api_token_secret=${bamboo_sonarqube_api_token_secret} --target builder -t builder-image:latest -f "$dockerfileLocation" .
|
|
||||||
mkdir -p ./paligo-theme
|
|
||||||
docker run -v "$(pwd)"/paligo-theme:/tmp/styles-export builder-image:latest
|
|
||||||
tar -czvf paligo-theme.tar.gz ./paligo-theme
|
|
||||||
|
|
||||||
docker push "$imageName:latest"
|
|
||||||
docker tag "$imageName:latest" "$imageName:$version"
|
|
||||||
docker push "$imageName:$version"
|
|
||||||
|
|
||||||
else
|
|
||||||
echo "Not on a relevant branch $bamboo_planRepository_branchName ... skipping."
|
|
||||||
echo "APP_VERSION=BRANCH-$bamboo_planRepository_branchName-$bamboo_buildNumber" > version.properties
|
|
||||||
|
|
||||||
if [[ ! -z "$bamboo_version_tag" ]]
|
|
||||||
then
|
|
||||||
echo "$BAMBOO_DOWNLOAD_PASS" | docker login -u "$BAMBOO_DOWNLOAD_USER" --password-stdin nexus.iqser.com:5001
|
|
||||||
echo "Pushing custom tag: $bamboo_version_tag"
|
|
||||||
docker tag "$imageName:latest" "$imageName:$bamboo_version_tag"
|
|
||||||
docker push "$imageName:$bamboo_version_tag"
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
package buildjob;
|
|
||||||
|
|
||||||
|
|
||||||
import com.atlassian.bamboo.specs.api.builders.plan.Plan;
|
|
||||||
import com.atlassian.bamboo.specs.api.exceptions.PropertiesValidationException;
|
|
||||||
import com.atlassian.bamboo.specs.api.util.EntityPropertiesBuilders;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
public class PlanSpecTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void checkYourPlanOffline() throws PropertiesValidationException {
|
|
||||||
Plan plan = new PlanSpec().createDockerBuildPlan();
|
|
||||||
EntityPropertiesBuilders.build(plan);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -30,12 +30,12 @@ COPY angular.json angular.json
|
|||||||
COPY nx.json nx.json
|
COPY nx.json nx.json
|
||||||
COPY .eslintrc.json .eslintrc.json
|
COPY .eslintrc.json .eslintrc.json
|
||||||
COPY tsconfig.json tsconfig.json
|
COPY tsconfig.json tsconfig.json
|
||||||
COPY versions.sh version.sh
|
|
||||||
COPY paligo-styles paligo-styles
|
COPY paligo-styles paligo-styles
|
||||||
COPY sonar.js sonar.js
|
COPY sonar.js sonar.js
|
||||||
|
|
||||||
## Build the angular app in production mode and store the artifacts in dist folder
|
## Build the angular app in production mode and store the artifacts in dist folder
|
||||||
RUN node sonar.js
|
## Fix Sonar Auth problem the uncomment
|
||||||
|
## RUN node sonar.js
|
||||||
RUN yarn run build-lint-all
|
RUN yarn run build-lint-all
|
||||||
RUN yarn run build-paligo-styles
|
RUN yarn run build-paligo-styles
|
||||||
|
|
||||||
@ -59,9 +59,7 @@ RUN chmod o+r -R /usr/share/nginx/html
|
|||||||
RUN chmod g+r -R /usr/share/nginx/html
|
RUN chmod g+r -R /usr/share/nginx/html
|
||||||
|
|
||||||
## Change permissions to enable openShift functionality
|
## Change permissions to enable openShift functionality
|
||||||
RUN chmod -R g+rwx /var/cache/nginx /var/run /var/log/nginx /usr/share /etc/nginx
|
# RUN chmod -R g+rwx /var/cache/nginx /var/run /var/log/nginx /usr/share /etc/nginx
|
||||||
|
|
||||||
USER 1001
|
|
||||||
|
|
||||||
COPY docker/red-ui/docker-entrypoint.sh /
|
COPY docker/red-ui/docker-entrypoint.sh /
|
||||||
CMD ["/docker-entrypoint.sh"]
|
CMD ["/docker-entrypoint.sh"]
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
Subproject commit d09078e44c8c294c78c44080d0bb8403d0ec6c34
|
Subproject commit 406f7b1fdd025b4e87273c2867ea6fdbc16bab3b
|
||||||
@ -45,7 +45,7 @@ export class DownloadStatus implements IDownloadStatus, IListable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private get _size() {
|
private get _size() {
|
||||||
const i = this.fileSize === 0 ? 0 : Math.floor(Math.log(this.fileSize) / Math.log(1024));
|
const i = this.fileSize === 0 ? 0 : Math.floor(Math.log(this.fileSize) / Math.log(1000));
|
||||||
return (this.fileSize / Math.pow(1024, i)).toFixed(2) + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
|
return (this.fileSize / Math.pow(1000, i)).toFixed(2) + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "redaction",
|
"name": "redaction",
|
||||||
"version": "3.988.0",
|
"version": "3.988.46",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -39,7 +39,7 @@
|
|||||||
"@ngx-translate/core": "^14.0.0",
|
"@ngx-translate/core": "^14.0.0",
|
||||||
"@ngx-translate/http-loader": "^7.0.0",
|
"@ngx-translate/http-loader": "^7.0.0",
|
||||||
"@nrwl/angular": "15.6.3",
|
"@nrwl/angular": "15.6.3",
|
||||||
"@pdftron/webviewer": "8.11.0",
|
"@pdftron/webviewer": "10.1.0",
|
||||||
"@swimlane/ngx-charts": "^20.0.1",
|
"@swimlane/ngx-charts": "^20.0.1",
|
||||||
"dayjs": "^1.11.5",
|
"dayjs": "^1.11.5",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
|
|||||||
Binary file not shown.
1
version.properties
Normal file
1
version.properties
Normal file
@ -0,0 +1 @@
|
|||||||
|
APP_VERSION=3.988.46
|
||||||
47
versions.sh
47
versions.sh
@ -1,47 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
function bump() {
|
|
||||||
|
|
||||||
if [ "$1" == "major" ] || [ "$1" == "minor" ] || [ "$1" == "patch" ]; then
|
|
||||||
current_version=$(cat package.json | jq -r '.version')
|
|
||||||
|
|
||||||
IFS='.' read -a version_parts <<< "$current_version"
|
|
||||||
|
|
||||||
major=${version_parts[0]}
|
|
||||||
minor=${version_parts[1]}
|
|
||||||
patch=${version_parts[2]}
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
"major")
|
|
||||||
major=$((major + 1))
|
|
||||||
minor=0
|
|
||||||
patch=0
|
|
||||||
;;
|
|
||||||
"minor")
|
|
||||||
minor=$((minor + 1))
|
|
||||||
patch=0
|
|
||||||
;;
|
|
||||||
"patch")
|
|
||||||
patch=$((patch + 1))
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
new_version="$major.$minor.$patch"
|
|
||||||
echo "New Version is $new_version"
|
|
||||||
|
|
||||||
cat package.json | jq ".version = \"$new_version\"" > temp.json
|
|
||||||
mv temp.json package.json
|
|
||||||
|
|
||||||
cat package-lock.json | jq ".version = \"$new_version\"" > temp.json
|
|
||||||
mv temp.json package-lock.json
|
|
||||||
|
|
||||||
else
|
|
||||||
echo >&2 "No patch type set. Aborting."
|
|
||||||
fi
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "Bumping version ... "
|
|
||||||
bump $1
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -3424,10 +3424,10 @@
|
|||||||
node-addon-api "^3.2.1"
|
node-addon-api "^3.2.1"
|
||||||
node-gyp-build "^4.3.0"
|
node-gyp-build "^4.3.0"
|
||||||
|
|
||||||
"@pdftron/webviewer@8.11.0":
|
"@pdftron/webviewer@10.1.0":
|
||||||
version "8.11.0"
|
version "10.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@pdftron/webviewer/-/webviewer-8.11.0.tgz#11f948f93bcb701c5d32357d650a17455bfa14b7"
|
resolved "https://registry.yarnpkg.com/@pdftron/webviewer/-/webviewer-10.1.0.tgz#51bdcc91185629223340600efa400150b1f0132a"
|
||||||
integrity sha512-TWtvSDAvig/7IQq9gqn1SSYL0cpWSfWo5gEsqmNqRc/qUEEsuZUqpSDXi5zQFX0xr5wWnLSQvrnGKK0iJdKWPg==
|
integrity sha512-ZGpVO02qfM9u/kAZpG5io6xHiwhz4IKWIsUYX+HgPbotaQpeLYJjZjUni/99UPQCMOVVkC2mNZcIlOlJWKK3UQ==
|
||||||
|
|
||||||
"@phenomnomnominal/tsquery@4.1.1":
|
"@phenomnomnominal/tsquery@4.1.1":
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user