Merge branch 'master' into RED-2769
This commit is contained in:
commit
a1726be3f9
@ -1,6 +1,6 @@
|
||||
{
|
||||
"cli": {
|
||||
"analytics": "4b8eed12-a1e6-4b7a-9ea2-925b27941271"
|
||||
"analytics": false
|
||||
},
|
||||
"version": 1,
|
||||
"projects": {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
<router-outlet></router-outlet>
|
||||
<iqser-full-page-loading-indicator></iqser-full-page-loading-indicator>
|
||||
<iqser-connection-status></iqser-connection-status>
|
||||
<iqser-full-page-error></iqser-full-page-error>
|
||||
|
||||
@ -1,30 +1,29 @@
|
||||
<form (submit)="executeCurrentAction()">
|
||||
<iqser-input-with-action
|
||||
(click)="openMenuIfValue()"
|
||||
(valueChange)="valueChanges$.next($event)"
|
||||
[placeholder]="placeholder"
|
||||
></iqser-input-with-action>
|
||||
<iqser-input-with-action
|
||||
(action)="executeCurrentAction()"
|
||||
(click)="openMenuIfValue()"
|
||||
(valueChange)="valueChanges$.next($event)"
|
||||
[placeholder]="placeholder"
|
||||
></iqser-input-with-action>
|
||||
|
||||
<mat-menu #menu="matMenu" class="search-menu" xPosition="after">
|
||||
<ng-template matMenuContent>
|
||||
<div class="wrapper">
|
||||
<button
|
||||
(click)="item.action(valueChanges$.getValue())"
|
||||
*ngFor="let item of shownActions; let index = index"
|
||||
[class.highlight]="(currentActionIdx$ | async) === index"
|
||||
class="spotlight-row pointer"
|
||||
>
|
||||
<mat-icon [svgIcon]="item.icon"></mat-icon>
|
||||
<span>{{ item.text }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</mat-menu>
|
||||
<mat-menu #menu="matMenu" class="search-menu" xPosition="after">
|
||||
<ng-template matMenuContent>
|
||||
<div class="wrapper">
|
||||
<button
|
||||
(click)="item.action(valueChanges$.getValue())"
|
||||
*ngFor="let item of shownActions; let index = index"
|
||||
[class.highlight]="(currentActionIdx$ | async) === index"
|
||||
class="spotlight-row pointer"
|
||||
>
|
||||
<mat-icon [svgIcon]="item.icon"></mat-icon>
|
||||
<span>{{ item.text }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</mat-menu>
|
||||
|
||||
<!-- https://material.angular.io/components/menu/overview#toggling-the-menu-programmatically -->
|
||||
<!-- To toggle menu programmatically a matMenuTriggerFor directive is needed -->
|
||||
<div [matMenuTriggerFor]="menu"></div>
|
||||
<!-- https://material.angular.io/components/menu/overview#toggling-the-menu-programmatically -->
|
||||
<!-- To toggle menu programmatically a matMenuTriggerFor directive is needed -->
|
||||
<div [matMenuTriggerFor]="menu"></div>
|
||||
|
||||
<!-- A hack to avoid subscribing in component -->
|
||||
<ng-container *ngIf="showActions$ | async"></ng-container>
|
||||
</form>
|
||||
<!-- A hack to avoid subscribing in component -->
|
||||
<ng-container *ngIf="showActions$ | async"></ng-container>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { AnnotationWrapper } from './annotation.wrapper';
|
||||
import { isArray } from 'rxjs/internal-compatibility';
|
||||
import { User } from '@red/domain';
|
||||
import { isArray } from 'lodash';
|
||||
|
||||
export class AnnotationPermissions {
|
||||
canUndo = true;
|
||||
|
||||
@ -283,11 +283,7 @@ export class AnnotationWrapper {
|
||||
|
||||
private static _setSuperType(annotationWrapper: AnnotationWrapper, redactionLogEntryWrapper: RedactionLogEntryWrapper) {
|
||||
if (redactionLogEntryWrapper.recommendation) {
|
||||
if (redactionLogEntryWrapper.redacted) {
|
||||
annotationWrapper.superType = 'recommendation';
|
||||
} else {
|
||||
annotationWrapper.superType = 'skipped';
|
||||
}
|
||||
annotationWrapper.superType = 'recommendation';
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -1,34 +1,46 @@
|
||||
import { Dictionary, File, IRedactionLog, IRedactionLogEntry, IViewedPage, User, ViewMode } from '@red/domain';
|
||||
import { Dictionary, File, IRedactionLog, IRedactionLogEntry, IViewedPage, ViewMode } from '@red/domain';
|
||||
import { AnnotationWrapper } from './annotation.wrapper';
|
||||
import { RedactionLogEntryWrapper } from './redaction-log-entry.wrapper';
|
||||
import * as moment from 'moment';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
export class FileDataModel {
|
||||
static readonly DELTA_VIEW_TIME = 10 * 60 * 1000; // 10 minutes;
|
||||
|
||||
hasChangeLog: boolean;
|
||||
allAnnotations: AnnotationWrapper[];
|
||||
readonly hasChangeLog$ = new BehaviorSubject<boolean>(false);
|
||||
readonly blob$ = new BehaviorSubject<Blob>(undefined);
|
||||
readonly file$ = new BehaviorSubject<File>(undefined);
|
||||
|
||||
constructor(
|
||||
public file: File,
|
||||
public fileData: Blob,
|
||||
private readonly _file: File,
|
||||
private readonly _blob: Blob,
|
||||
private _redactionLog: IRedactionLog,
|
||||
public viewedPages?: IViewedPage[],
|
||||
private _dictionaryData?: { [p: string]: Dictionary },
|
||||
private _areDevFeaturesEnabled?: boolean,
|
||||
) {
|
||||
this.file$.next(_file);
|
||||
this.blob$.next(_blob);
|
||||
this._buildAllAnnotations();
|
||||
}
|
||||
|
||||
get file(): File {
|
||||
return this.file$.value;
|
||||
}
|
||||
|
||||
set file(file: File) {
|
||||
this.file$.next(file);
|
||||
}
|
||||
|
||||
get redactionLog(): IRedactionLog {
|
||||
return this._redactionLog;
|
||||
}
|
||||
|
||||
set redactionLog(redactionLog: IRedactionLog) {
|
||||
this._redactionLog = redactionLog;
|
||||
this._buildAllAnnotations();
|
||||
}
|
||||
|
||||
get redactionLog() {
|
||||
return this._redactionLog;
|
||||
}
|
||||
|
||||
getVisibleAnnotations(viewMode: ViewMode) {
|
||||
return this.allAnnotations.filter(annotation => {
|
||||
if (viewMode === 'STANDARD') {
|
||||
@ -47,7 +59,7 @@ export class FileDataModel {
|
||||
const previousAnnotations = this.allAnnotations || [];
|
||||
this.allAnnotations = entries
|
||||
.map(entry => AnnotationWrapper.fromData(entry))
|
||||
.filter(ann => ann.manual || !this.file.excludedPages.includes(ann.pageNumber));
|
||||
.filter(ann => ann.manual || !this._file.excludedPages.includes(ann.pageNumber));
|
||||
|
||||
if (!this._areDevFeaturesEnabled) {
|
||||
this.allAnnotations = this.allAnnotations.filter(annotation => !annotation.isFalsePositive);
|
||||
@ -121,11 +133,11 @@ export class FileDataModel {
|
||||
}
|
||||
|
||||
private _isChangeLogEntry(redactionLogEntry: IRedactionLogEntry, wrapper: RedactionLogEntryWrapper) {
|
||||
if (this.file.numberOfAnalyses > 1) {
|
||||
redactionLogEntry.changes.sort((a, b) => moment(a.dateTime).valueOf() - moment(b.dateTime).valueOf());
|
||||
if (this._file.numberOfAnalyses > 1) {
|
||||
const viableChanges = redactionLogEntry.changes.filter(c => c.analysisNumber > 1);
|
||||
viableChanges.sort((a, b) => moment(a.dateTime).valueOf() - moment(b.dateTime).valueOf());
|
||||
|
||||
const lastChange =
|
||||
redactionLogEntry.changes.length >= 1 ? redactionLogEntry.changes[redactionLogEntry.changes.length - 1] : undefined;
|
||||
const lastChange = viableChanges.length >= 1 ? viableChanges[viableChanges.length - 1] : undefined;
|
||||
const page = redactionLogEntry.positions?.[0].page;
|
||||
|
||||
const viewedPage = this.viewedPages.filter(p => p.page === page).pop();
|
||||
@ -134,14 +146,14 @@ export class FileDataModel {
|
||||
if (viewedPage) {
|
||||
const viewTime = moment(viewedPage.viewedTime).valueOf() - FileDataModel.DELTA_VIEW_TIME;
|
||||
// these are all unseen changes
|
||||
const relevantChanges = redactionLogEntry.changes.filter(change => moment(change.dateTime).valueOf() > viewTime);
|
||||
const relevantChanges = viableChanges.filter(change => moment(change.dateTime).valueOf() > viewTime);
|
||||
// at least one unseen change
|
||||
if (relevantChanges.length > 0) {
|
||||
// at least 1 relevant change
|
||||
wrapper.changeLogType = relevantChanges[relevantChanges.length - 1].type;
|
||||
wrapper.isChangeLogEntry = true;
|
||||
viewedPage.showAsUnseen = moment(viewedPage.viewedTime).valueOf() < moment(lastChange.dateTime).valueOf();
|
||||
this.hasChangeLog = true;
|
||||
this.hasChangeLog$.next(true);
|
||||
} else {
|
||||
// no relevant changes - hide removed anyway
|
||||
wrapper.isChangeLogEntry = false;
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
export const NotificationCategories = {
|
||||
inAppNotifications: 'inAppNotifications',
|
||||
emailNotifications: 'emailNotifications',
|
||||
} as const;
|
||||
|
||||
export const NotificationCategoriesValues = Object.values(NotificationCategories);
|
||||
|
||||
export const DossierNotificationsTypes = {
|
||||
dossierOwnerSet: 'DOSSIER_OWNER_SET',
|
||||
dossierOwnerRemoved: 'DOSSIER_OWNER_REMOVED',
|
||||
userBecomseDossierMember: 'USER_BECOMES_DOSSIER_MEMBER',
|
||||
userRemovedAsDossierMember: 'USER_REMOVED_AS_DOSSIER_MEMBER',
|
||||
userPromotedToApprover: 'USER_PROMOTED_TO_APPROVER',
|
||||
userDegradedToReviewer: 'USER_DEGRADED_TO_REVIEWER',
|
||||
dossierOwnerDeleted: 'DOSSIER_OWNER_DELETED',
|
||||
dossierDeleted: 'DOSSIER_DELETED',
|
||||
} as const;
|
||||
|
||||
export const DossierNotificationsTypesValues = Object.values(DossierNotificationsTypes);
|
||||
|
||||
export const DocumentNotificationsTypes = {
|
||||
assignReviewer: 'ASSIGN_REVIEWER',
|
||||
assignApprover: 'ASSIGN_APPROVER',
|
||||
unassignedFromFile: 'UNASSIGNED_FROM_FILE',
|
||||
// documentUnderReview: 'DOCUMENT_UNDER_REVIEW',
|
||||
// documentUnderApproval: 'DOCUMENT_UNDER_APPROVAL',
|
||||
documentApproved: 'DOCUMENT_APPROVED',
|
||||
} as const;
|
||||
|
||||
export const DocumentNotificationsTypesValues = Object.values(DocumentNotificationsTypes);
|
||||
|
||||
export const OtherNotificationsTypes = {
|
||||
downloadReady: 'DOWNLOAD_READY',
|
||||
} as const;
|
||||
|
||||
export const OtherNotificationsTypesValues = Object.values(OtherNotificationsTypes);
|
||||
|
||||
export const NotificationGroupsKeys = ['dossier', 'document', 'other'] as const;
|
||||
export const NotificationGroupsValues = [
|
||||
DossierNotificationsTypesValues,
|
||||
DocumentNotificationsTypesValues,
|
||||
OtherNotificationsTypesValues,
|
||||
] as const;
|
||||
@ -7,25 +7,25 @@
|
||||
}}</mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
<div class="options-content" *ngIf="isCategoryActive(category)">
|
||||
<div class="radio-container" *ngIf="category === 'emailNotifications'">
|
||||
<div class="radio-button" *ngFor="let type of emailNotificationScheduleTypes">
|
||||
<iqser-round-checkbox [active]="getEmailNotificationType() === type" (click)="setEmailNotificationType(type)">
|
||||
</iqser-round-checkbox>
|
||||
<span> {{ translations[type.toLocaleLowerCase()] | translate }} </span>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="isCategoryActive(category)" class="options-content">
|
||||
<!-- <div class="radio-container" *ngIf="category === 'emailNotifications'">-->
|
||||
<!-- <div class="radio-button" *ngFor="let type of emailNotificationScheduleTypes">-->
|
||||
<!-- <iqser-round-checkbox [active]="getEmailNotificationType() === type" (click)="setEmailNotificationType(type)">-->
|
||||
<!-- </iqser-round-checkbox>-->
|
||||
<!-- <span> {{ translations[type.toLocaleLowerCase()] | translate }} </span>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<div class="statement" translate="notifications-screen.options-title"></div>
|
||||
|
||||
<div class="group" *ngFor="let key of notificationGroupsKeys; let i = index">
|
||||
<div class="group-title" [translate]="translations[key]"></div>
|
||||
<div *ngFor="let key of notificationGroupsKeys; let i = index" class="group">
|
||||
<div [translate]="translations[key]" class="group-title"></div>
|
||||
<div class="iqser-input-group">
|
||||
<mat-checkbox
|
||||
*ngFor="let preference of notificationGroupsValues[i]"
|
||||
color="primary"
|
||||
[checked]="isPreferenceChecked(category, preference)"
|
||||
(change)="addRemovePreference($event.checked, category, preference)"
|
||||
*ngFor="let preference of notificationGroupsValues[i]"
|
||||
[checked]="isPreferenceChecked(category, preference)"
|
||||
color="primary"
|
||||
>
|
||||
{{ translations[preference] | translate }}
|
||||
</mat-checkbox>
|
||||
|
||||
@ -4,8 +4,12 @@ import { notificationsTranslations } from '../../../translations/notifications-t
|
||||
import { NotificationPreferencesService } from '../../../services/notification-preferences.service';
|
||||
import { LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { NotificationCategoriesValues, NotificationGroupsKeys, NotificationGroupsValues } from '../constants';
|
||||
import { EmailNotificationScheduleTypesValues } from '@red/domain';
|
||||
import {
|
||||
EmailNotificationScheduleTypesValues,
|
||||
NotificationCategoriesValues,
|
||||
NotificationGroupsKeys,
|
||||
NotificationGroupsValues,
|
||||
} from '@red/domain';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-notifications-screen',
|
||||
@ -33,16 +37,6 @@ export class NotificationsScreenComponent implements OnInit {
|
||||
await this._initializeForm();
|
||||
}
|
||||
|
||||
private _getForm(): FormGroup {
|
||||
return this._formBuilder.group({
|
||||
inAppNotificationsEnabled: [undefined],
|
||||
emailNotificationsEnabled: [undefined],
|
||||
emailNotificationType: [undefined],
|
||||
emailNotifications: [undefined],
|
||||
inAppNotifications: [undefined],
|
||||
});
|
||||
}
|
||||
|
||||
isCategoryActive(category: string) {
|
||||
return this.formGroup.get(`${category}Enabled`).value;
|
||||
}
|
||||
@ -80,6 +74,16 @@ export class NotificationsScreenComponent implements OnInit {
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
private _getForm(): FormGroup {
|
||||
return this._formBuilder.group({
|
||||
inAppNotificationsEnabled: [undefined],
|
||||
emailNotificationsEnabled: [undefined],
|
||||
emailNotificationType: [undefined],
|
||||
emailNotifications: [undefined],
|
||||
inAppNotifications: [undefined],
|
||||
});
|
||||
}
|
||||
|
||||
private async _initializeForm() {
|
||||
this._loadingService.start();
|
||||
|
||||
|
||||
@ -73,7 +73,7 @@ export class UserProfileScreenComponent implements OnInit {
|
||||
}
|
||||
|
||||
if (this.profileChanged) {
|
||||
const value = this.form.value as IProfile;
|
||||
const value = this.form.getRawValue() as IProfile;
|
||||
delete value.language;
|
||||
|
||||
await this._userService
|
||||
|
||||
@ -12,7 +12,18 @@ import {
|
||||
|
||||
import { curveLinear } from 'd3-shape';
|
||||
import { scaleBand, scaleLinear, scalePoint, scaleTime } from 'd3-scale';
|
||||
import { BaseChartComponent, calculateViewDimensions, ColorHelper, LineSeriesComponent, ViewDimensions } from '@swimlane/ngx-charts';
|
||||
import {
|
||||
BaseChartComponent,
|
||||
calculateViewDimensions,
|
||||
Color,
|
||||
ColorHelper,
|
||||
LegendPosition,
|
||||
LineSeriesComponent,
|
||||
Orientation,
|
||||
ScaleType,
|
||||
ViewDimensions,
|
||||
} from '@swimlane/ngx-charts';
|
||||
import { ILineChartSeries } from './models';
|
||||
|
||||
@Component({
|
||||
// eslint-disable-next-line @angular-eslint/component-selector
|
||||
@ -25,7 +36,7 @@ export class ComboChartComponent extends BaseChartComponent {
|
||||
@Input() curve: any = curveLinear;
|
||||
@Input() legend = false;
|
||||
@Input() legendTitle = 'Legend';
|
||||
@Input() legendPosition = 'right';
|
||||
@Input() legendPosition: LegendPosition = LegendPosition.Right;
|
||||
@Input() xAxis;
|
||||
@Input() yAxis;
|
||||
@Input() showXAxisLabel;
|
||||
@ -38,33 +49,33 @@ export class ComboChartComponent extends BaseChartComponent {
|
||||
@Input() gradient: boolean;
|
||||
@Input() showGridLines = true;
|
||||
@Input() activeEntries: any[] = [];
|
||||
@Input() schemeType: string;
|
||||
@Input() schemeType: ScaleType;
|
||||
@Input() xAxisTickFormatting: any;
|
||||
@Input() yAxisTickFormatting: any;
|
||||
@Input() yRightAxisTickFormatting: any;
|
||||
@Input() roundDomains = false;
|
||||
@Input() colorSchemeLine: any;
|
||||
@Input() colorSchemeLine: Color;
|
||||
@Input() autoScale;
|
||||
@Input() lineChart: any;
|
||||
@Input() lineChart: ILineChartSeries[];
|
||||
@Input() yLeftAxisScaleFactor: any;
|
||||
@Input() yRightAxisScaleFactor: any;
|
||||
@Input() rangeFillOpacity: number;
|
||||
@Input() animations = true;
|
||||
@Input() noBarWhenZero = true;
|
||||
|
||||
@Output() activate: EventEmitter<any> = new EventEmitter();
|
||||
@Output() deactivate: EventEmitter<any> = new EventEmitter();
|
||||
@Output() activate = new EventEmitter<{ value; entries: unknown[] }>();
|
||||
@Output() deactivate = new EventEmitter<{ value; entries: unknown[] }>();
|
||||
|
||||
@ContentChild('tooltipTemplate') tooltipTemplate: TemplateRef<any>;
|
||||
@ContentChild('seriesTooltipTemplate') seriesTooltipTemplate: TemplateRef<any>;
|
||||
@ContentChild('tooltipTemplate') tooltipTemplate: TemplateRef<unknown>;
|
||||
@ContentChild('seriesTooltipTemplate') seriesTooltipTemplate: TemplateRef<unknown>;
|
||||
|
||||
@ViewChild(LineSeriesComponent) lineSeriesComponent: LineSeriesComponent;
|
||||
|
||||
dims: ViewDimensions;
|
||||
xScale: any;
|
||||
yScale: any;
|
||||
xDomain: any;
|
||||
yDomain: any;
|
||||
xDomain: string[] | number[];
|
||||
yDomain: string[] | number[];
|
||||
transform: string;
|
||||
colors: ColorHelper;
|
||||
colorsLine: ColorHelper;
|
||||
@ -72,19 +83,19 @@ export class ComboChartComponent extends BaseChartComponent {
|
||||
xAxisHeight = 0;
|
||||
yAxisWidth = 0;
|
||||
legendOptions: any;
|
||||
scaleType = 'linear';
|
||||
scaleType: ScaleType = ScaleType.Linear;
|
||||
xScaleLine;
|
||||
yScaleLine;
|
||||
xDomainLine;
|
||||
yDomainLine;
|
||||
seriesDomain;
|
||||
scaledAxis;
|
||||
combinedSeries;
|
||||
combinedSeries: ILineChartSeries[];
|
||||
xSet;
|
||||
filteredDomain;
|
||||
hoveredVertical;
|
||||
yOrientLeft = 'left';
|
||||
yOrientRight = 'right';
|
||||
yOrientLeft: Orientation = Orientation.Left;
|
||||
yOrientRight: Orientation = Orientation.Right;
|
||||
legendSpacing = 0;
|
||||
bandwidth: number;
|
||||
barPadding = 8;
|
||||
@ -176,15 +187,11 @@ export class ComboChartComponent extends BaseChartComponent {
|
||||
return this.combinedSeries.map(d => d.name);
|
||||
}
|
||||
|
||||
isDate(value): boolean {
|
||||
if (value instanceof Date) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
isDate(value): value is Date {
|
||||
return value instanceof Date;
|
||||
}
|
||||
|
||||
getScaleType(values): string {
|
||||
getScaleType(values): ScaleType {
|
||||
let date = true;
|
||||
let num = true;
|
||||
|
||||
@ -199,16 +206,16 @@ export class ComboChartComponent extends BaseChartComponent {
|
||||
}
|
||||
|
||||
if (date) {
|
||||
return 'time';
|
||||
return ScaleType.Time;
|
||||
}
|
||||
if (num) {
|
||||
return 'linear';
|
||||
return ScaleType.Linear;
|
||||
}
|
||||
return 'ordinal';
|
||||
return ScaleType.Ordinal;
|
||||
}
|
||||
|
||||
getXDomainLine(): any[] {
|
||||
let values = [];
|
||||
let values: number[] = [];
|
||||
|
||||
for (const results of this.lineChart) {
|
||||
for (const d of results.series) {
|
||||
@ -239,7 +246,7 @@ export class ComboChartComponent extends BaseChartComponent {
|
||||
}
|
||||
|
||||
getYDomainLine(): any[] {
|
||||
const domain = [];
|
||||
const domain: number[] = [];
|
||||
|
||||
for (const results of this.lineChart) {
|
||||
for (const d of results.series) {
|
||||
@ -263,7 +270,7 @@ export class ComboChartComponent extends BaseChartComponent {
|
||||
const max = Math.max(...domain);
|
||||
if (this.yRightAxisScaleFactor) {
|
||||
const minMax = this.yRightAxisScaleFactor(min, max);
|
||||
return [Math.min(0, minMax.min), minMax.max];
|
||||
return [Math.min(0, minMax.min as number), minMax.max];
|
||||
} else {
|
||||
min = Math.min(0, min);
|
||||
return [min, max];
|
||||
@ -317,12 +324,12 @@ export class ComboChartComponent extends BaseChartComponent {
|
||||
}
|
||||
|
||||
getYDomain() {
|
||||
const values = this.results.map(d => d.value);
|
||||
const values: number[] = this.results.map(d => d.value);
|
||||
const min = Math.min(0, ...values);
|
||||
const max = Math.max(...values);
|
||||
if (this.yLeftAxisScaleFactor) {
|
||||
const minMax = this.yLeftAxisScaleFactor(min, max);
|
||||
return [Math.min(0, minMax.min), minMax.max];
|
||||
return [Math.min(0, minMax.min as number), minMax.max];
|
||||
} else {
|
||||
return [min, max];
|
||||
}
|
||||
@ -333,7 +340,7 @@ export class ComboChartComponent extends BaseChartComponent {
|
||||
}
|
||||
|
||||
setColors(): void {
|
||||
let domain;
|
||||
let domain: number[] | string[];
|
||||
if (this.schemeType === 'ordinal') {
|
||||
domain = this.xDomain;
|
||||
} else {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
|
||||
import { animate, style, transition, trigger } from '@angular/animations';
|
||||
import { formatLabel } from '@swimlane/ngx-charts';
|
||||
import { Bar, BarOrientation, formatLabel, PlacementTypes, StyleTypes } from '@swimlane/ngx-charts';
|
||||
|
||||
@Component({
|
||||
// eslint-disable-next-line @angular-eslint/component-selector
|
||||
@ -17,7 +17,7 @@ import { formatLabel } from '@swimlane/ngx-charts';
|
||||
[fill]="bar.color"
|
||||
[stops]="bar.gradientStops"
|
||||
[data]="bar.data"
|
||||
[orientation]="'vertical'"
|
||||
[orientation]="orientations.Vertical"
|
||||
[roundEdges]="bar.roundEdges"
|
||||
[gradient]="gradient"
|
||||
[isActive]="isActive(bar.data)"
|
||||
@ -27,8 +27,8 @@ import { formatLabel } from '@swimlane/ngx-charts';
|
||||
(deactivate)="deactivate.emit($event)"
|
||||
ngx-tooltip
|
||||
[tooltipDisabled]="tooltipDisabled"
|
||||
[tooltipPlacement]="0"
|
||||
[tooltipType]="1"
|
||||
[tooltipPlacement]="tooltipPlacements.Top"
|
||||
[tooltipType]="tooltipTypes.tooltip"
|
||||
[tooltipTitle]="bar.tooltipText"
|
||||
></svg:g>
|
||||
`,
|
||||
@ -67,6 +67,9 @@ export class ComboSeriesVerticalComponent implements OnChanges {
|
||||
bars: any;
|
||||
x: any;
|
||||
y: any;
|
||||
readonly tooltipTypes = StyleTypes;
|
||||
readonly tooltipPlacements = PlacementTypes;
|
||||
readonly orientations = BarOrientation;
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.update();
|
||||
@ -91,7 +94,7 @@ export class ComboSeriesVerticalComponent implements OnChanges {
|
||||
const formattedLabel = formatLabel(label);
|
||||
const roundEdges = this.type === 'standard';
|
||||
|
||||
const bar: any = {
|
||||
const bar: Bar = {
|
||||
value,
|
||||
label,
|
||||
roundEdges,
|
||||
@ -101,8 +104,15 @@ export class ComboSeriesVerticalComponent implements OnChanges {
|
||||
height: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
ariaLabel: label,
|
||||
tooltipText: label,
|
||||
color: undefined,
|
||||
gradientStops: undefined,
|
||||
};
|
||||
|
||||
let offset0 = d0;
|
||||
let offset1 = offset0 + value;
|
||||
|
||||
if (this.type === 'standard') {
|
||||
bar.height = Math.abs(this.yScale(value) - this.yScale(0));
|
||||
bar.x = this.xScale(label);
|
||||
@ -113,18 +123,14 @@ export class ComboSeriesVerticalComponent implements OnChanges {
|
||||
bar.y = this.yScale(value);
|
||||
}
|
||||
} else if (this.type === 'stacked') {
|
||||
const offset0 = d0;
|
||||
const offset1 = offset0 + value;
|
||||
d0 += value;
|
||||
|
||||
bar.height = this.yScale(offset0) - this.yScale(offset1);
|
||||
bar.x = 0;
|
||||
bar.y = this.yScale(offset1);
|
||||
bar.offset0 = offset0;
|
||||
bar.offset1 = offset1;
|
||||
// bar.offset0 = offset0;
|
||||
// bar.offset1 = offset1;
|
||||
} else if (this.type === 'normalized') {
|
||||
let offset0 = d0;
|
||||
let offset1 = offset0 + value;
|
||||
d0 += value;
|
||||
|
||||
if (total > 0) {
|
||||
@ -138,8 +144,8 @@ export class ComboSeriesVerticalComponent implements OnChanges {
|
||||
bar.height = this.yScale(offset0) - this.yScale(offset1);
|
||||
bar.x = 0;
|
||||
bar.y = this.yScale(offset1);
|
||||
bar.offset0 = offset0;
|
||||
bar.offset1 = offset1;
|
||||
// bar.offset0 = offset0;
|
||||
// bar.offset1 = offset1;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
value = (offset1 - offset0).toFixed(2) + '%';
|
||||
@ -152,8 +158,8 @@ export class ComboSeriesVerticalComponent implements OnChanges {
|
||||
bar.color = this.colors.getColor(value);
|
||||
bar.gradientStops = this.colors.getLinearGradientStops(value);
|
||||
} else {
|
||||
bar.color = this.colors.getColor(bar.offset1);
|
||||
bar.gradientStops = this.colors.getLinearGradientStops(bar.offset1, bar.offset0);
|
||||
bar.color = this.colors.getColor(offset1);
|
||||
bar.gradientStops = this.colors.getLinearGradientStops(offset1, offset0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
export interface ISeries {
|
||||
name: number;
|
||||
value: number;
|
||||
min: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
export interface ILineChartSeries {
|
||||
name: string;
|
||||
series: ISeries[];
|
||||
}
|
||||
@ -95,11 +95,11 @@
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button (click)="save()" [disabled]="!valid || !changed" color="primary" mat-flat-button type="button">
|
||||
<button (click)="save()" [disabled]="disabled" color="primary" mat-flat-button type="button">
|
||||
{{ 'add-edit-dictionary.save' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||
</section>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, Inject, Injector } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { Observable } from 'rxjs';
|
||||
@ -6,7 +6,7 @@ import { BaseDialogComponent, shareDistinctLast, Toaster } from '@iqser/common-u
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { toKebabCase } from '@utils/functions';
|
||||
import { toSnakeCase } from '@utils/functions';
|
||||
import { DictionaryService } from '@shared/services/dictionary.service';
|
||||
import { Dictionary, IDictionary } from '@red/domain';
|
||||
import { UserService } from '@services/user.service';
|
||||
@ -21,7 +21,6 @@ import { HttpStatusCode } from '@angular/common/http';
|
||||
})
|
||||
export class AddEditDictionaryDialogComponent extends BaseDialogComponent {
|
||||
readonly dictionary = this._data.dictionary;
|
||||
readonly form: FormGroup = this._getForm(this.dictionary);
|
||||
readonly canEditLabel$ = this._canEditLabel$;
|
||||
readonly technicalName$: Observable<string>;
|
||||
readonly dialogHeader = this._translateService.instant('add-edit-dictionary.title', {
|
||||
@ -29,7 +28,6 @@ export class AddEditDictionaryDialogComponent extends BaseDialogComponent {
|
||||
name: this._data.dictionary?.label,
|
||||
});
|
||||
readonly hasColor$: Observable<boolean>;
|
||||
readonly disabled = false;
|
||||
private readonly _dossierTemplateId = this._data.dossierTemplateId;
|
||||
|
||||
constructor(
|
||||
@ -39,37 +37,18 @@ export class AddEditDictionaryDialogComponent extends BaseDialogComponent {
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
private readonly _dialogRef: MatDialogRef<AddEditDictionaryDialogComponent>,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<AddEditDictionaryDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
private readonly _data: { readonly dictionary: Dictionary; readonly dossierTemplateId: string },
|
||||
) {
|
||||
super();
|
||||
super(_injector, _dialogRef);
|
||||
this.form = this._getForm(this.dictionary);
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
this.hasColor$ = this._colorEmpty$;
|
||||
this.technicalName$ = this.form.get('label').valueChanges.pipe(map(value => this._toTechnicalName(value)));
|
||||
}
|
||||
|
||||
get valid(): boolean {
|
||||
return this.form.valid;
|
||||
}
|
||||
|
||||
get changed(): boolean {
|
||||
if (!this.dictionary) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const key of Object.keys(this.form.getRawValue())) {
|
||||
if (key === 'caseSensitive') {
|
||||
if (this.getDictCaseSensitive(this.dictionary) !== this.form.get(key).value) {
|
||||
return true;
|
||||
}
|
||||
} else if (this.dictionary[key] !== this.form.get(key).value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private get _canEditLabel$() {
|
||||
return this.userService.currentUser$.pipe(
|
||||
map(user => user.isAdmin || !this._data.dictionary),
|
||||
@ -126,11 +105,11 @@ export class AddEditDictionaryDialogComponent extends BaseDialogComponent {
|
||||
|
||||
private _toTechnicalName(value: string) {
|
||||
const existingTechnicalNames = Object.keys(this._appStateService.dictionaryData[this._dossierTemplateId]);
|
||||
const baseTechnicalName = toKebabCase(value.trim());
|
||||
const baseTechnicalName = toSnakeCase(value.trim());
|
||||
let technicalName = baseTechnicalName;
|
||||
let suffix = 1;
|
||||
while (existingTechnicalNames.includes(technicalName)) {
|
||||
technicalName = [baseTechnicalName, suffix++].join('-');
|
||||
technicalName = [baseTechnicalName, suffix++].join('_');
|
||||
}
|
||||
return technicalName;
|
||||
}
|
||||
|
||||
@ -35,11 +35,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="dialog-actions">
|
||||
<button (click)="save()" [disabled]="form.invalid || !changed" color="primary" mat-flat-button type="button">
|
||||
<button (click)="save()" [disabled]="disabled" color="primary" mat-flat-button type="button">
|
||||
{{ 'add-edit-dossier-attribute.save' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||
</section>
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { Component, HostListener, Inject, OnDestroy } from '@angular/core';
|
||||
import { Component, HostListener, Inject, Injector, OnDestroy } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { DossierAttributeConfigTypes, FileAttributeConfigTypes, IDossierAttributeConfig } from '@red/domain';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { AutoUnsubscribe, IqserEventTarget, LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { BaseDialogComponent, IqserEventTarget, LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { DossierAttributesService } from '@shared/services/controller-wrappers/dossier-attributes.service';
|
||||
import { dossierAttributeTypesTranslations } from '../../translations/dossier-attribute-types-translations';
|
||||
@ -12,9 +12,8 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
templateUrl: './add-edit-dossier-attribute-dialog.component.html',
|
||||
styleUrls: ['./add-edit-dossier-attribute-dialog.component.scss'],
|
||||
})
|
||||
export class AddEditDossierAttributeDialogComponent extends AutoUnsubscribe implements OnDestroy {
|
||||
export class AddEditDossierAttributeDialogComponent extends BaseDialogComponent implements OnDestroy {
|
||||
dossierAttribute: IDossierAttributeConfig = this.data.dossierAttribute;
|
||||
readonly form: FormGroup = this._getForm(this.dossierAttribute);
|
||||
readonly translations = dossierAttributeTypesTranslations;
|
||||
readonly typeOptions = Object.keys(DossierAttributeConfigTypes);
|
||||
|
||||
@ -23,11 +22,14 @@ export class AddEditDossierAttributeDialogComponent extends AutoUnsubscribe impl
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _dossierAttributesService: DossierAttributesService,
|
||||
private readonly _toaster: Toaster,
|
||||
readonly dialogRef: MatDialogRef<AddEditDossierAttributeDialogComponent>,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<AddEditDossierAttributeDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
readonly data: { readonly dossierAttribute: IDossierAttributeConfig },
|
||||
) {
|
||||
super();
|
||||
super(_injector, _dialogRef);
|
||||
this.form = this._getForm(this.dossierAttribute);
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
}
|
||||
|
||||
get changed(): boolean {
|
||||
@ -55,7 +57,7 @@ export class AddEditDossierAttributeDialogComponent extends AutoUnsubscribe impl
|
||||
|
||||
this._dossierAttributesService.createOrUpdate(attribute).subscribe(
|
||||
() => {
|
||||
this.dialogRef.close(true);
|
||||
this._dialogRef.close(true);
|
||||
},
|
||||
(error: HttpErrorResponse) => {
|
||||
this._loadingService.stop();
|
||||
|
||||
@ -33,16 +33,11 @@
|
||||
|
||||
<div class="validity">
|
||||
<div>
|
||||
<mat-checkbox
|
||||
(change)="hasValidFrom = !hasValidFrom"
|
||||
[checked]="hasValidFrom"
|
||||
class="filter-menu-checkbox"
|
||||
color="primary"
|
||||
>
|
||||
<mat-checkbox (change)="toggleHasValid('from')" [checked]="hasValidFrom" class="filter-menu-checkbox" color="primary">
|
||||
{{ 'add-edit-dossier-template.form.valid-from' | translate }}
|
||||
</mat-checkbox>
|
||||
|
||||
<mat-checkbox (change)="hasValidTo = !hasValidTo" [checked]="hasValidTo" class="filter-menu-checkbox" color="primary">
|
||||
<mat-checkbox (change)="toggleHasValid('to')" [checked]="hasValidTo" class="filter-menu-checkbox" color="primary">
|
||||
{{ 'add-edit-dossier-template.form.valid-to' | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
@ -87,11 +82,11 @@
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button (click)="save()" [disabled]="!valid || !changed" color="primary" mat-flat-button type="button">
|
||||
<button (click)="save()" [disabled]="disabled" color="primary" mat-flat-button type="button">
|
||||
{{ 'add-edit-dossier-template.save' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||
</section>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { Component, Inject, Injector } from '@angular/core';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import * as moment from 'moment';
|
||||
import { Moment } from 'moment';
|
||||
@ -17,7 +17,6 @@ import { HttpStatusCode } from '@angular/common/http';
|
||||
styleUrls: ['./add-edit-dossier-template-dialog.component.scss'],
|
||||
})
|
||||
export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent {
|
||||
readonly form: FormGroup = this._getForm();
|
||||
hasValidFrom: boolean;
|
||||
hasValidTo: boolean;
|
||||
downloadTypesEnum: DownloadFileType[] = ['ORIGINAL', 'PREVIEW', 'REDACTED'];
|
||||
@ -25,66 +24,50 @@ export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent {
|
||||
key: type,
|
||||
label: downloadTypesTranslations[type],
|
||||
}));
|
||||
readonly disabled = false;
|
||||
private _previousValidFrom: Moment;
|
||||
private _previousValidTo: Moment;
|
||||
private _lastValidFrom: Moment;
|
||||
private _lastValidTo: Moment;
|
||||
|
||||
constructor(
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<AddEditDossierTemplateDialogComponent>,
|
||||
private readonly _loadingService: LoadingService,
|
||||
public dialogRef: MatDialogRef<AddEditDossierTemplateDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) readonly dossierTemplate: IDossierTemplate,
|
||||
) {
|
||||
super();
|
||||
super(_injector, _dialogRef);
|
||||
this.form = this._getForm();
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
this.hasValidFrom = !!this.dossierTemplate?.validFrom;
|
||||
this.hasValidTo = !!this.dossierTemplate?.validTo;
|
||||
|
||||
this._previousValidFrom = this.form.get('validFrom').value;
|
||||
this._previousValidTo = this.form.get('validTo').value;
|
||||
this._previousValidFrom = this._lastValidFrom = this.form.get('validFrom').value;
|
||||
this._previousValidTo = this._lastValidTo = this.form.get('validTo').value;
|
||||
|
||||
this.form.valueChanges.subscribe(value => {
|
||||
this.addSubscription = this.form.valueChanges.subscribe(value => {
|
||||
this._applyValidityIntervalConstraints(value);
|
||||
});
|
||||
|
||||
this.addSubscription = this.form.controls['validFrom'].valueChanges.subscribe(value => {
|
||||
this._lastValidFrom = value ? value : this._lastValidFrom;
|
||||
});
|
||||
this.addSubscription = this.form.controls['validTo'].valueChanges.subscribe(value => {
|
||||
this._lastValidFrom = value ? value : this._lastValidFrom;
|
||||
});
|
||||
}
|
||||
|
||||
get valid(): boolean {
|
||||
return this.form.valid;
|
||||
}
|
||||
|
||||
get changed(): boolean {
|
||||
if (!this.dossierTemplate) {
|
||||
return true;
|
||||
toggleHasValid(extremity: string) {
|
||||
if (extremity === 'from') {
|
||||
this.hasValidFrom = !this.hasValidFrom;
|
||||
this.form.controls['validFrom'].setValue(this.hasValidFrom ? this._lastValidFrom : null);
|
||||
} else {
|
||||
this.hasValidTo = !this.hasValidTo;
|
||||
this.form.controls['validTo'].setValue(this.hasValidTo ? this._lastValidTo : null);
|
||||
}
|
||||
|
||||
for (const key of Object.keys(this.form.getRawValue())) {
|
||||
const formValue = this.form.get(key).value;
|
||||
const objectValue = this.dossierTemplate[key];
|
||||
if (key === 'validFrom') {
|
||||
if (this.hasValidFrom !== !!objectValue || (this.hasValidFrom && !moment(objectValue).isSame(moment(formValue)))) {
|
||||
return true;
|
||||
}
|
||||
} else if (key === 'validTo') {
|
||||
if (this.hasValidTo !== !!objectValue || (this.hasValidTo && !moment(objectValue).isSame(moment(formValue)))) {
|
||||
return true;
|
||||
}
|
||||
} else if (formValue instanceof Array) {
|
||||
if (objectValue.length !== formValue.length) {
|
||||
return true;
|
||||
}
|
||||
for (const item of objectValue) {
|
||||
if (!formValue.includes(item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (objectValue !== formValue) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async save() {
|
||||
@ -95,10 +78,10 @@ export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent {
|
||||
...this.form.getRawValue(),
|
||||
validFrom: this.hasValidFrom ? this.form.get('validFrom').value : null,
|
||||
validTo: this.hasValidTo ? this.form.get('validTo').value : null,
|
||||
};
|
||||
} as IDossierTemplate;
|
||||
await this._dossierTemplatesService.createOrUpdate(dossierTemplate).toPromise();
|
||||
await this._appStateService.loadDictionaryData();
|
||||
this.dialogRef.close(true);
|
||||
this._dialogRef.close(true);
|
||||
} catch (error: any) {
|
||||
const message =
|
||||
error.status === HttpStatusCode.Conflict
|
||||
@ -136,7 +119,7 @@ export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent {
|
||||
}
|
||||
|
||||
private _requiredIfValidator(predicate) {
|
||||
return formControl => {
|
||||
return (formControl: AbstractControl) => {
|
||||
if (!formControl.parent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -84,11 +84,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="dialog-actions">
|
||||
<button (click)="save()" [disabled]="!valid || !changed" color="primary" mat-flat-button type="button">
|
||||
<button (click)="save()" [disabled]="disabled" color="primary" mat-flat-button type="button">
|
||||
{{ 'add-edit-file-attribute.save' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||
</section>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, Inject, Injector } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { FileAttributeConfigTypes, IFileAttributeConfig } from '@red/domain';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { fileAttributeTypesTranslations } from '../../translations/file-attribute-types-translations';
|
||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||
import { BaseDialogComponent } from '@iqser/common-ui';
|
||||
import { BaseDialogComponent } from '../../../../../../../../libs/common-ui/src';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-add-edit-file-attribute-dialog',
|
||||
@ -13,12 +13,10 @@ import { BaseDialogComponent } from '@iqser/common-ui';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AddEditFileAttributeDialogComponent extends BaseDialogComponent {
|
||||
readonly disabled = false;
|
||||
DISPLAYED_FILTERABLE_LIMIT = 3;
|
||||
translations = fileAttributeTypesTranslations;
|
||||
fileAttribute: IFileAttributeConfig = this.data.fileAttribute;
|
||||
dossierTemplateId: string = this.data.dossierTemplateId;
|
||||
readonly form!: FormGroup;
|
||||
readonly typeOptions = Object.keys(FileAttributeConfigTypes);
|
||||
readonly canSetDisplayed!: boolean;
|
||||
readonly canSetFilterable!: boolean;
|
||||
@ -26,7 +24,8 @@ export class AddEditFileAttributeDialogComponent extends BaseDialogComponent {
|
||||
constructor(
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _fileAttributesService: FileAttributesService,
|
||||
public dialogRef: MatDialogRef<AddEditFileAttributeDialogComponent>,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<AddEditFileAttributeDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
public data: {
|
||||
fileAttribute: IFileAttributeConfig;
|
||||
@ -35,32 +34,11 @@ export class AddEditFileAttributeDialogComponent extends BaseDialogComponent {
|
||||
numberOfFilterableAttrs: number;
|
||||
},
|
||||
) {
|
||||
super();
|
||||
super(_injector, _dialogRef);
|
||||
this.canSetDisplayed = data.numberOfDisplayedAttrs < this.DISPLAYED_FILTERABLE_LIMIT || data.fileAttribute?.displayedInFileList;
|
||||
this.canSetFilterable = data.numberOfFilterableAttrs < this.DISPLAYED_FILTERABLE_LIMIT || data.fileAttribute?.filterable;
|
||||
this.form = this._getForm(this.fileAttribute);
|
||||
}
|
||||
|
||||
get valid(): boolean {
|
||||
return this.form.valid;
|
||||
}
|
||||
|
||||
get changed(): boolean {
|
||||
if (!this.fileAttribute) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const key of Object.keys(this.form.getRawValue())) {
|
||||
if (key === 'readonly') {
|
||||
if (this.fileAttribute.editable === this.form.get(key).value) {
|
||||
return true;
|
||||
}
|
||||
} else if (this.fileAttribute[key] !== this.form.get(key).value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
}
|
||||
|
||||
save() {
|
||||
@ -69,7 +47,7 @@ export class AddEditFileAttributeDialogComponent extends BaseDialogComponent {
|
||||
editable: !this.form.get('readonly').value,
|
||||
...this.form.getRawValue(),
|
||||
};
|
||||
this.dialogRef.close(fileAttribute);
|
||||
this._dialogRef.close(fileAttribute);
|
||||
}
|
||||
|
||||
private _getForm(fileAttribute: IFileAttributeConfig): FormGroup {
|
||||
|
||||
@ -134,13 +134,14 @@ export class UserDetailsComponent extends AutoUnsubscribe implements OnChanges,
|
||||
}
|
||||
|
||||
private _getForm(): FormGroup {
|
||||
console.log(this.user);
|
||||
return this._formBuilder.group({
|
||||
firstName: [this.user?.firstName, Validators.required],
|
||||
lastName: [this.user?.lastName, Validators.required],
|
||||
email: [
|
||||
{
|
||||
value: this.user?.email,
|
||||
disabled: !!this.user,
|
||||
disabled: !!this.user?.email,
|
||||
},
|
||||
[Validators.required, Validators.email],
|
||||
],
|
||||
|
||||
@ -28,11 +28,11 @@
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button (click)="save()" [disabled]="!valid || !changed" color="primary" mat-flat-button type="button">
|
||||
<button (click)="save()" [disabled]="disabled" color="primary" mat-flat-button type="button">
|
||||
{{ 'edit-color-dialog.save' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||
</section>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { Component, Inject, Injector } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { DefaultColorType, IColors } from '@red/domain';
|
||||
import { BaseDialogComponent, Toaster } from '@iqser/common-ui';
|
||||
@ -19,10 +19,7 @@ interface IEditColorData {
|
||||
styleUrls: ['./edit-color-dialog.component.scss'],
|
||||
})
|
||||
export class EditColorDialogComponent extends BaseDialogComponent {
|
||||
readonly form: FormGroup;
|
||||
translations = defaultColorsTranslations;
|
||||
readonly disabled = false;
|
||||
private readonly _initialColor: string;
|
||||
private readonly _dossierTemplateId: string;
|
||||
|
||||
constructor(
|
||||
@ -30,23 +27,16 @@ export class EditColorDialogComponent extends BaseDialogComponent {
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _dialogRef: MatDialogRef<EditColorDialogComponent>,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<EditColorDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
readonly data: IEditColorData,
|
||||
) {
|
||||
super();
|
||||
super(_injector, _dialogRef);
|
||||
this._dossierTemplateId = data.dossierTemplateId;
|
||||
this._initialColor = data.colors[data.colorKey];
|
||||
|
||||
this.form = this._getForm();
|
||||
}
|
||||
|
||||
get changed(): boolean {
|
||||
return this.form.get('color').value !== this._initialColor;
|
||||
}
|
||||
|
||||
get valid(): boolean {
|
||||
return this.form.valid;
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
}
|
||||
|
||||
async save() {
|
||||
|
||||
@ -6,7 +6,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { RouterHistoryService } from '@services/router-history.service';
|
||||
import { DigitalSignatureService } from '../../services/digital-signature.service';
|
||||
import { IDigitalSignature } from '@red/domain';
|
||||
import { IDigitalSignature, IDigitalSignatureRequest } from '@red/domain';
|
||||
import { HttpStatusCode } from '@angular/common/http';
|
||||
|
||||
@Component({
|
||||
@ -40,29 +40,30 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribe implements
|
||||
}
|
||||
|
||||
saveDigitalSignature() {
|
||||
const digitalSignature = {
|
||||
...this.form.getRawValue(),
|
||||
const formValue = this.form.getRawValue();
|
||||
const digitalSignature: IDigitalSignature = {
|
||||
...formValue,
|
||||
};
|
||||
//adjusted for chrome auto-complete / password manager
|
||||
digitalSignature.password = digitalSignature.keySecret;
|
||||
digitalSignature.password = formValue.keySecret;
|
||||
|
||||
const observable = this.digitalSignatureExists
|
||||
? this._digitalSignatureService.update(digitalSignature)
|
||||
: this._digitalSignatureService.save(digitalSignature);
|
||||
|
||||
this.addSubscription = observable.subscribe(
|
||||
() => {
|
||||
this.addSubscription = observable.subscribe({
|
||||
next: () => {
|
||||
this.loadDigitalSignatureAndInitializeForm();
|
||||
this._toaster.success(_('digital-signature-screen.action.save-success'));
|
||||
},
|
||||
error => {
|
||||
error: error => {
|
||||
if (error.status === HttpStatusCode.BadRequest) {
|
||||
this._toaster.error(_('digital-signature-screen.action.certificate-not-valid-error'));
|
||||
} else {
|
||||
this._toaster.error(_('digital-signature-screen.action.save-error'));
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
removeDigitalSignature() {
|
||||
@ -85,23 +86,23 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribe implements
|
||||
this.form.get('certificateName').setValue(file.name);
|
||||
input.value = null;
|
||||
};
|
||||
fileReader.readAsDataURL(file);
|
||||
fileReader.readAsDataURL(file as Blob);
|
||||
}
|
||||
|
||||
loadDigitalSignatureAndInitializeForm() {
|
||||
this._loadingService.start();
|
||||
this.addSubscription = this._digitalSignatureService
|
||||
this._digitalSignatureService
|
||||
.getSignature()
|
||||
.subscribe(
|
||||
digitalSignature => {
|
||||
.subscribe({
|
||||
next: digitalSignature => {
|
||||
this.digitalSignatureExists = true;
|
||||
this.digitalSignature = digitalSignature;
|
||||
},
|
||||
() => {
|
||||
error: () => {
|
||||
this.digitalSignatureExists = false;
|
||||
this.digitalSignature = {};
|
||||
},
|
||||
)
|
||||
})
|
||||
.add(() => {
|
||||
this.form = this._getForm();
|
||||
this._loadingService.stop();
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
<ng-template #bulkActions>
|
||||
<iqser-circle-button
|
||||
(action)="openConfirmDeleteDialog()"
|
||||
*ngIf="userService.currentUser.isAdmin && listingService.areSomeSelected$ | async"
|
||||
*ngIf="userPreferenceService.areDevFeaturesEnabled && userService.currentUser.isAdmin && listingService.areSomeSelected$ | async"
|
||||
[tooltip]="'justifications-listing.bulk.delete' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="iqser:trash"
|
||||
@ -26,7 +26,7 @@
|
||||
<div class="table-header-actions">
|
||||
<iqser-icon-button
|
||||
(action)="openAddJustificationDialog()"
|
||||
*ngIf="userService.currentUser.isAdmin"
|
||||
*ngIf="userPreferenceService.areDevFeaturesEnabled && userService.currentUser.isAdmin"
|
||||
[label]="'justifications-listing.add-new' | translate"
|
||||
[type]="iconButtonTypes.primary"
|
||||
icon="iqser:plus"
|
||||
|
||||
@ -14,6 +14,7 @@ import { JustificationsService } from '@services/entity-services/justifications.
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { JustificationsDialogService } from '../justifications-dialog.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-justifications-screen',
|
||||
@ -43,6 +44,7 @@ export class JustificationsScreenComponent extends ListingComponent<Justificatio
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _dialogService: JustificationsDialogService,
|
||||
readonly userPreferenceService: UserPreferenceService,
|
||||
readonly userService: UserService,
|
||||
) {
|
||||
super(_injector);
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="openConfirmDeleteDialog()"
|
||||
*ngIf="userService.currentUser.isAdmin"
|
||||
*ngIf="userPreferenceService.areDevFeaturesEnabled && userService.currentUser.isAdmin"
|
||||
[tooltip]="'justifications-listing.actions.delete' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="iqser:trash"
|
||||
|
||||
@ -3,6 +3,7 @@ import { Justification } from '@red/domain';
|
||||
import { CircleButtonTypes, ListingService, LoadingService } from '@iqser/common-ui';
|
||||
import { JustificationsDialogService } from '../justifications-dialog.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-table-item',
|
||||
@ -18,6 +19,7 @@ export class TableItemComponent {
|
||||
private readonly _dialogService: JustificationsDialogService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _listingService: ListingService<Justification>,
|
||||
readonly userPreferenceService: UserPreferenceService,
|
||||
readonly userService: UserService,
|
||||
) {}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import { UserService } from '@services/user.service';
|
||||
import { RouterHistoryService } from '@services/router-history.service';
|
||||
import { LicenseReportService } from '../../services/licence-report.service';
|
||||
import { ILicenseReport } from '@red/domain';
|
||||
import { Color, ScaleType } from '@swimlane/ngx-charts';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-license-information-screen',
|
||||
@ -31,14 +32,16 @@ export class LicenseInformationScreenComponent implements OnInit {
|
||||
analysisPercentageOfLicense = 100;
|
||||
barChart: any[];
|
||||
lineChartSeries: any[] = [];
|
||||
lineChartScheme = {
|
||||
lineChartScheme: Color = {
|
||||
name: 'Line chart scheme',
|
||||
selectable: true,
|
||||
group: 'Ordinal',
|
||||
group: ScaleType.Ordinal,
|
||||
domain: ['#dd4d50', '#5ce594', '#0389ec'],
|
||||
};
|
||||
comboBarScheme = {
|
||||
comboBarScheme: Color = {
|
||||
name: 'Combo bar scheme',
|
||||
selectable: true,
|
||||
group: 'Ordinal',
|
||||
group: ScaleType.Ordinal,
|
||||
domain: ['#0389ec'],
|
||||
};
|
||||
|
||||
|
||||
@ -92,6 +92,31 @@ export class ReportsScreenComponent implements OnInit {
|
||||
}
|
||||
|
||||
const dossierTemplateId = this._dossierTemplatesService.activeDossierTemplateId;
|
||||
|
||||
if (this.availableTemplates.some(template => template.fileName === file.name)) {
|
||||
const data = new ConfirmationDialogInput({
|
||||
title: _('confirmation-dialog.report-template-same-name.title'),
|
||||
question: _('confirmation-dialog.report-template-same-name.question'),
|
||||
confirmationText: _('confirmation-dialog.report-template-same-name.confirmation-text'),
|
||||
denyText: _('confirmation-dialog.report-template-same-name.deny-text'),
|
||||
translateParams: {
|
||||
fileName: file.name,
|
||||
},
|
||||
});
|
||||
|
||||
this._dialogService.openDialog('confirm', null, data, null, async result => {
|
||||
if (result) {
|
||||
await this._openConfirmationDialog(file, dossierTemplateId);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await this._openConfirmationDialog(file, dossierTemplateId);
|
||||
}
|
||||
|
||||
this._fileInput.nativeElement.value = null;
|
||||
}
|
||||
|
||||
private async _openConfirmationDialog(file: File, dossierTemplateId: string) {
|
||||
if (this._isExcelFile(file)) {
|
||||
const data = new ConfirmationDialogInput({
|
||||
title: _('confirmation-dialog.upload-report-template.title'),
|
||||
@ -113,7 +138,6 @@ export class ReportsScreenComponent implements OnInit {
|
||||
await this._reportTemplateService.uploadTemplateForm(dossierTemplateId, false, file).toPromise();
|
||||
await this._loadReportTemplates();
|
||||
}
|
||||
this._fileInput.nativeElement.value = null;
|
||||
}
|
||||
|
||||
private async _deleteTemplate(template: IReportTemplate) {
|
||||
|
||||
@ -34,6 +34,7 @@ export class AdminDialogService extends DialogService<DialogType> {
|
||||
protected readonly _config: DialogConfig<DialogType> = {
|
||||
confirm: {
|
||||
component: ConfirmationDialogComponent,
|
||||
dialogConfig: { disableClose: false },
|
||||
},
|
||||
addEditDictionary: {
|
||||
component: AddEditDictionaryDialogComponent,
|
||||
@ -49,18 +50,19 @@ export class AdminDialogService extends DialogService<DialogType> {
|
||||
},
|
||||
deleteFileAttribute: {
|
||||
component: ConfirmDeleteFileAttributeDialogComponent,
|
||||
dialogConfig: { disableClose: false },
|
||||
},
|
||||
importFileAttributes: {
|
||||
component: FileAttributesCsvImportDialogComponent,
|
||||
dialogConfig: largeDialogConfig,
|
||||
dialogConfig: { ...largeDialogConfig, ...{ disableClose: false } },
|
||||
},
|
||||
deleteUsers: {
|
||||
component: ConfirmDeleteUsersDialogComponent,
|
||||
dialogConfig: { autoFocus: true },
|
||||
dialogConfig: { autoFocus: true, disableClose: false },
|
||||
},
|
||||
addEditUser: {
|
||||
component: AddEditUserDialogComponent,
|
||||
dialogConfig: { autoFocus: true },
|
||||
dialogConfig: { autoFocus: true, disableClose: false },
|
||||
},
|
||||
smtpAuthConfig: {
|
||||
component: SmtpAuthDialogComponent,
|
||||
|
||||
@ -28,6 +28,7 @@ export class RedRoleGuard implements CanActivate {
|
||||
if (
|
||||
this._userService.currentUser.isUserAdmin &&
|
||||
!this._userService.currentUser.isAdmin &&
|
||||
!this._userService.currentUser.isUser &&
|
||||
!(state.url.startsWith('/main/admin/users') || state.url.startsWith('/main/my-profile'))
|
||||
) {
|
||||
this._router.navigate(['/main/admin/users']);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<section class="dialog">
|
||||
<form (submit)="saveDossier()" [formGroup]="form">
|
||||
<form (submit)="save()" [formGroup]="form">
|
||||
<div class="dialog-header heading-l" translate="add-dossier-dialog.header-new"></div>
|
||||
|
||||
<div class="dialog-content">
|
||||
@ -91,7 +91,7 @@
|
||||
</button>
|
||||
|
||||
<iqser-icon-button
|
||||
(action)="saveDossier(true)"
|
||||
(action)="save({ addMembers: true })"
|
||||
[disabled]="disabled"
|
||||
[label]="'add-dossier-dialog.actions.save-and-add-members' | translate"
|
||||
[type]="iconButtonTypes.dark"
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, Injector } from '@angular/core';
|
||||
import { MatDialogRef } from '@angular/material/dialog';
|
||||
import { DownloadFileType, IDossierRequest, IDossierTemplate, IReportTemplate } from '@red/domain';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import * as moment from 'moment';
|
||||
import { downloadTypesTranslations } from '../../../../translations/download-types-translations';
|
||||
import { IconButtonTypes } from '@iqser/common-ui';
|
||||
import { BaseDialogComponent, IconButtonTypes, SaveOptions } from '@iqser/common-ui';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { ReportTemplateService } from '@services/report-template.service';
|
||||
@ -13,10 +13,9 @@ import { ReportTemplateService } from '@services/report-template.service';
|
||||
templateUrl: './add-dossier-dialog.component.html',
|
||||
styleUrls: ['./add-dossier-dialog.component.scss'],
|
||||
})
|
||||
export class AddDossierDialogComponent {
|
||||
export class AddDossierDialogComponent extends BaseDialogComponent {
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
|
||||
readonly form: FormGroup;
|
||||
hasDueDate = false;
|
||||
downloadTypes: { key: DownloadFileType; label: string }[] = ['ORIGINAL', 'PREVIEW', 'REDACTED'].map((type: DownloadFileType) => ({
|
||||
key: type,
|
||||
@ -30,10 +29,13 @@ export class AddDossierDialogComponent {
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _reportTemplateController: ReportTemplateService,
|
||||
readonly dialogRef: MatDialogRef<AddDossierDialogComponent>,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<AddDossierDialogComponent>,
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
this._getDossierTemplates();
|
||||
this.form = this._getForm();
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
}
|
||||
|
||||
private _getForm(): FormGroup {
|
||||
@ -75,10 +77,10 @@ export class AddDossierDialogComponent {
|
||||
|
||||
reportTemplateValueMapper = (reportTemplate: IReportTemplate) => reportTemplate.templateId;
|
||||
|
||||
async saveDossier(addMembers = false) {
|
||||
async save(options?: SaveOptions) {
|
||||
const savedDossier = await this._dossiersService.createOrUpdate(this._formToObject()).toPromise();
|
||||
if (savedDossier) {
|
||||
this.dialogRef.close({ dossier: savedDossier, addMembers });
|
||||
this._dialogRef.close({ dossier: savedDossier, addMembers: options?.addMembers });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -38,12 +38,12 @@
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button [disabled]="!form.valid || !changed" color="primary" mat-flat-button type="submit">
|
||||
<button [disabled]="disabled" color="primary" mat-flat-button type="submit">
|
||||
{{ 'change-legal-basis-dialog.actions.save' | translate }}
|
||||
</button>
|
||||
<div class="all-caps-label cancel" mat-dialog-close translate="change-legal-basis-dialog.actions.cancel"></div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
|
||||
</section>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { Component, Inject, Injector, OnInit } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
@ -6,6 +6,7 @@ import { PermissionsService } from '@services/permissions.service';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { JustificationsService } from '@services/entity-services/justifications.service';
|
||||
import { Dossier } from '@red/domain';
|
||||
import { BaseDialogComponent } from '@iqser/common-ui';
|
||||
|
||||
export interface LegalBasisOption {
|
||||
label?: string;
|
||||
@ -16,8 +17,7 @@ export interface LegalBasisOption {
|
||||
@Component({
|
||||
templateUrl: './change-legal-basis-dialog.component.html',
|
||||
})
|
||||
export class ChangeLegalBasisDialogComponent implements OnInit {
|
||||
form: FormGroup = this._getForm();
|
||||
export class ChangeLegalBasisDialogComponent extends BaseDialogComponent implements OnInit {
|
||||
isDocumentAdmin: boolean;
|
||||
legalOptions: LegalBasisOption[] = [];
|
||||
|
||||
@ -26,16 +26,12 @@ export class ChangeLegalBasisDialogComponent implements OnInit {
|
||||
private readonly _dossiersService: DossiersService,
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
readonly dialogRef: MatDialogRef<ChangeLegalBasisDialogComponent>,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<ChangeLegalBasisDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) private readonly _data: { annotations: AnnotationWrapper[]; dossier: Dossier },
|
||||
) {}
|
||||
|
||||
get changed(): boolean {
|
||||
return (
|
||||
this.form.get('reason').value.legalBasis !== this._data.annotations[0].legalBasis ||
|
||||
this.form.get('section').value !== this._data.annotations[0].section ||
|
||||
this.form.get('classification').value !== this._data.annotations[0].value
|
||||
);
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
this.form = this._getForm();
|
||||
}
|
||||
|
||||
get allRectangles(): boolean {
|
||||
@ -43,6 +39,7 @@ export class ChangeLegalBasisDialogComponent implements OnInit {
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
super.ngOnInit();
|
||||
const data = await this._justificationsService.getForDossierTemplate(this._data.dossier.dossierTemplateId).toPromise();
|
||||
|
||||
this.legalOptions = data
|
||||
@ -56,6 +53,7 @@ export class ChangeLegalBasisDialogComponent implements OnInit {
|
||||
this.form.patchValue({
|
||||
reason: this.legalOptions.find(option => option.legalBasis === this._data.annotations[0].legalBasis),
|
||||
});
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
}
|
||||
|
||||
private _getForm(): FormGroup {
|
||||
@ -69,7 +67,7 @@ export class ChangeLegalBasisDialogComponent implements OnInit {
|
||||
}
|
||||
|
||||
save() {
|
||||
this.dialogRef.close({
|
||||
this._dialogRef.close({
|
||||
legalBasis: this.form.get('reason').value.legalBasis,
|
||||
section: this.form.get('section').value,
|
||||
comment: this.form.get('comment').value,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<section *ngIf="!!form" class="dialog">
|
||||
<div class="dialog-header heading-l" translate="document-info.title"></div>
|
||||
|
||||
<form (submit)="saveDocumentInfo()" [formGroup]="form">
|
||||
<form (submit)="save()" [formGroup]="form">
|
||||
<div class="dialog-content">
|
||||
<div *ngFor="let attr of attributes" class="iqser-input-group w-300">
|
||||
<label>{{ attr.label }}</label>
|
||||
@ -9,11 +9,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="dialog-actions">
|
||||
<button [disabled]="form.invalid" color="primary" mat-flat-button type="submit">
|
||||
<button [disabled]="disabled" color="primary" mat-flat-button type="submit">
|
||||
{{ 'document-info.save' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
|
||||
</section>
|
||||
|
||||
@ -1,16 +1,17 @@
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { Component, Inject, Injector, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { Dossier, File, IFileAttributeConfig } from '@red/domain';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { FilesService } from '@services/entity-services/files.service';
|
||||
import { BaseDialogComponent } from '@iqser/common-ui';
|
||||
|
||||
@Component({
|
||||
templateUrl: './document-info-dialog.component.html',
|
||||
styleUrls: ['./document-info-dialog.component.scss'],
|
||||
})
|
||||
export class DocumentInfoDialogComponent implements OnInit {
|
||||
export class DocumentInfoDialogComponent extends BaseDialogComponent implements OnInit {
|
||||
form: FormGroup;
|
||||
attributes: IFileAttributeConfig[];
|
||||
|
||||
@ -21,27 +22,31 @@ export class DocumentInfoDialogComponent implements OnInit {
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _fileAttributesService: FileAttributesService,
|
||||
private readonly _filesService: FilesService,
|
||||
public dialogRef: MatDialogRef<DocumentInfoDialogComponent>,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<DocumentInfoDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) readonly data: File,
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
this._dossier = this._dossiersService.find(this.data.dossierId);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
super.ngOnInit();
|
||||
this.attributes = (
|
||||
await this._fileAttributesService.getFileAttributesConfig(this._dossier.dossierTemplateId).toPromise()
|
||||
).fileAttributeConfigs.filter(attr => attr.editable);
|
||||
this.form = this._getForm();
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
}
|
||||
|
||||
async saveDocumentInfo() {
|
||||
async save() {
|
||||
const attributeIdToValue = {
|
||||
...this.data.fileAttributes?.attributeIdToValue,
|
||||
...this.form.getRawValue(),
|
||||
};
|
||||
await this._fileAttributesService.setFileAttributes({ attributeIdToValue }, this.data.dossierId, this.data.fileId).toPromise();
|
||||
this._filesService.reload(this.data.dossierId, this.data.fileId);
|
||||
this.dialogRef.close(true);
|
||||
this._dialogRef.close(true);
|
||||
}
|
||||
|
||||
private _getForm(): FormGroup {
|
||||
|
||||
@ -66,9 +66,10 @@ export class EditDossierAttributesComponent implements EditDossierSectionInterfa
|
||||
async save(): EditDossierSaveResult {
|
||||
const dossierAttributeList = this.attributes.map(attr => ({
|
||||
dossierAttributeConfigId: attr.id,
|
||||
value: this.isSpecificType(attr, DossierAttributeConfigTypes.DATE)
|
||||
? moment(this.currentAttrValue(attr)).format('YYYY-MM-DD')
|
||||
: this.currentAttrValue(attr),
|
||||
value:
|
||||
this.isSpecificType(attr, DossierAttributeConfigTypes.DATE) && !!this.currentAttrValue(attr)
|
||||
? moment(this.currentAttrValue(attr)).format('YYYY-MM-DD')
|
||||
: this.currentAttrValue(attr),
|
||||
}));
|
||||
try {
|
||||
await this._dossierAttributesService.setAttributes(this.dossier, dossierAttributeList).toPromise();
|
||||
|
||||
@ -1,12 +1,22 @@
|
||||
<div class="header-wrapper">
|
||||
<div class="heading">
|
||||
<div>{{ dossierDictionary?.label }}</div>
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:entries"></mat-icon>
|
||||
{{ 'edit-dossier-dialog.dictionary.entries' | translate: { length: (dossierDictionary?.entries || []).length } }}
|
||||
<div class="header-left">
|
||||
<div class="heading">
|
||||
<div>{{ dossierDictionary?.label }}</div>
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:entries"></mat-icon>
|
||||
{{ 'edit-dossier-dialog.dictionary.entries' | translate: { length: (dossierDictionary?.entries || []).length } }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form [formGroup]="form" *ngIf="form">
|
||||
<div class="iqser-input-group">
|
||||
<mat-checkbox color="primary" formControlName="addToDictionaryAction" name="addToDictionaryAction">
|
||||
{{ 'add-edit-dictionary.form.add-to-dictionary-action' | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div *ngIf="canEdit" class="display-name">
|
||||
|
||||
@ -2,6 +2,14 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
|
||||
.iqser-input-group {
|
||||
margin-left: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.display-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@ -7,6 +7,7 @@ import { DictionaryService } from '@shared/services/dictionary.service';
|
||||
import { CircleButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-edit-dossier-dictionary',
|
||||
@ -15,10 +16,13 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
})
|
||||
export class EditDossierDictionaryComponent implements EditDossierSectionInterface, OnInit {
|
||||
@Input() dossier: Dossier;
|
||||
|
||||
form: FormGroup;
|
||||
canEdit = false;
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
dossierDictionary: IDictionary;
|
||||
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
|
||||
@ViewChild(DictionaryManagerComponent, { static: false }) private readonly _dictionaryManager: DictionaryManagerComponent;
|
||||
|
||||
constructor(
|
||||
@ -26,11 +30,20 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _toaster: Toaster,
|
||||
) {}
|
||||
|
||||
get formChanged() {
|
||||
if (this.form) {
|
||||
return this.form.get('addToDictionaryAction').value !== this.dossierDictionary.addToDictionaryAction;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
get changed(): boolean {
|
||||
return this._dictionaryManager.editor.hasChanges;
|
||||
return this._dictionaryManager.editor.hasChanges || this.formChanged;
|
||||
}
|
||||
|
||||
get disabled(): boolean {
|
||||
@ -38,13 +51,14 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
|
||||
}
|
||||
|
||||
get valid(): boolean {
|
||||
return this._dictionaryManager.editor.hasChanges;
|
||||
return this._dictionaryManager.editor.hasChanges || this.formChanged;
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this._loadingService.start();
|
||||
this.canEdit = this._permissionsService.isDossierMember(this.dossier);
|
||||
await this._updateDossierDictionary();
|
||||
this.form = this._getForm();
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
@ -66,6 +80,15 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
|
||||
|
||||
async save(): EditDossierSaveResult {
|
||||
try {
|
||||
const dictionary: IDictionary = {
|
||||
...this.dossierDictionary,
|
||||
type: 'dossier_redaction',
|
||||
addToDictionaryAction: this.form.get('addToDictionaryAction').value,
|
||||
};
|
||||
await this._dictionaryService
|
||||
.updateDictionary(dictionary, this.dossier.dossierTemplateId, 'dossier_redaction', this.dossier.id)
|
||||
.toPromise();
|
||||
|
||||
await this._dictionaryService
|
||||
.saveEntries(
|
||||
this._dictionaryManager.editor.currentEntries,
|
||||
@ -76,6 +99,7 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
|
||||
false,
|
||||
)
|
||||
.toPromise();
|
||||
|
||||
await this._updateDossierDictionary();
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
@ -85,6 +109,20 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
|
||||
|
||||
revert() {
|
||||
this._dictionaryManager.revert();
|
||||
this.form.reset({
|
||||
addToDictionaryAction: this.dossierDictionary.addToDictionaryAction,
|
||||
});
|
||||
}
|
||||
|
||||
private _getForm(): FormGroup {
|
||||
return this._formBuilder.group({
|
||||
addToDictionaryAction: [
|
||||
{
|
||||
value: this.dossierDictionary.addToDictionaryAction,
|
||||
disabled: !this._permissionsService.isOwner(this.dossier),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
private async _updateDossierDictionary() {
|
||||
|
||||
@ -53,7 +53,7 @@
|
||||
{{ 'edit-dossier-dialog.actions.save' | translate }}
|
||||
</button>
|
||||
<iqser-icon-button
|
||||
(action)="save(true)"
|
||||
(action)="save({ closeAfterSave: true })"
|
||||
[disabled]="disabled || !valid || !changed"
|
||||
[label]="'edit-dossier-dialog.actions.save-and-close' | translate"
|
||||
[type]="iconButtonTypes.dark"
|
||||
@ -65,5 +65,5 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
|
||||
</section>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { ChangeDetectorRef, Component, Inject, ViewChild } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, Inject, Injector, ViewChild } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { Dossier } from '@red/domain';
|
||||
import { EditDossierGeneralInfoComponent } from './general-info/edit-dossier-general-info.component';
|
||||
import { EditDossierDownloadPackageComponent } from './download-package/edit-dossier-download-package.component';
|
||||
import { EditDossierSectionInterface } from './edit-dossier-section.interface';
|
||||
import { BaseDialogComponent, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { BaseDialogComponent, ConfirmOptions, IconButtonTypes, LoadingService, SaveOptions, Toaster } from '@iqser/common-ui';
|
||||
import { EditDossierDictionaryComponent } from './dictionary/edit-dossier-dictionary.component';
|
||||
import { EditDossierAttributesComponent } from './attributes/edit-dossier-attributes.component';
|
||||
|
||||
@ -40,16 +40,17 @@ export class EditDossierDialogComponent extends BaseDialogComponent {
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _dossiersService: DossiersService,
|
||||
private readonly _changeRef: ChangeDetectorRef,
|
||||
private readonly _dialogRef: MatDialogRef<EditDossierDialogComponent>,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<EditDossierDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
private readonly _data: {
|
||||
dossierId: string;
|
||||
section?: Section;
|
||||
},
|
||||
) {
|
||||
super();
|
||||
super(_injector, _dialogRef);
|
||||
this.navItems = [
|
||||
{
|
||||
key: 'dossierInfo',
|
||||
@ -126,7 +127,7 @@ export class EditDossierDialogComponent extends BaseDialogComponent {
|
||||
return this.activeComponent?.disabled;
|
||||
}
|
||||
|
||||
async save(closeAfterSave: boolean = false) {
|
||||
async save(options?: SaveOptions) {
|
||||
this._loadingService.start();
|
||||
const result = await this.activeComponent.save();
|
||||
this._loadingService.stop();
|
||||
@ -135,7 +136,7 @@ export class EditDossierDialogComponent extends BaseDialogComponent {
|
||||
this._toaster.success(_('edit-dossier-dialog.change-successful'), { params: { dossierName: this._dossierName } });
|
||||
}
|
||||
|
||||
if (result.success && closeAfterSave) {
|
||||
if (result.success && options?.closeAfterSave) {
|
||||
this._dialogRef.close();
|
||||
}
|
||||
}
|
||||
@ -146,9 +147,19 @@ export class EditDossierDialogComponent extends BaseDialogComponent {
|
||||
|
||||
changeTab(key: Section) {
|
||||
if (this.changed) {
|
||||
this._toaster.error(_('edit-dossier-dialog.unsaved-changes'));
|
||||
return;
|
||||
this._openConfirmDialog().then(async result => {
|
||||
if (result in ConfirmOptions) {
|
||||
if (result === ConfirmOptions.CONFIRM) {
|
||||
await this.save();
|
||||
} else {
|
||||
this.revert();
|
||||
}
|
||||
this.activeNav = key;
|
||||
}
|
||||
this._waitingForConfirmation = false;
|
||||
});
|
||||
} else {
|
||||
this.activeNav = key;
|
||||
}
|
||||
this.activeNav = key;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<section class="dialog">
|
||||
<form (submit)="handleForceAnnotation()" [formGroup]="redactionForm">
|
||||
<form (submit)="save()" [formGroup]="form">
|
||||
<div class="dialog-header heading-l" translate="manual-annotation.dialog.header.force-redaction" *ngIf="!isHintDialog"></div>
|
||||
<div class="dialog-header heading-l" translate="manual-annotation.dialog.header.force-hint" *ngIf="isHintDialog"></div>
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
|
||||
<div class="iqser-input-group w-400" *ngIf="!isHintDialog">
|
||||
<label translate="manual-annotation.dialog.content.legalBasis"></label>
|
||||
<input [value]="redactionForm.get('reason').value?.legalBasis" disabled type="text" />
|
||||
<input [value]="form.get('reason').value?.legalBasis" disabled type="text" />
|
||||
</div>
|
||||
|
||||
<div [class.required]="!isDocumentAdmin" class="iqser-input-group w-300">
|
||||
@ -29,11 +29,11 @@
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button [disabled]="!redactionForm.valid" color="primary" mat-flat-button type="submit">
|
||||
<button [disabled]="disabled" color="primary" mat-flat-button type="submit">
|
||||
{{ 'manual-annotation.dialog.actions.save' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
|
||||
</section>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { Component, Inject, Injector, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { Toaster } from '@iqser/common-ui';
|
||||
import { BaseDialogComponent, Toaster } from '@iqser/common-ui';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { ManualAnnotationService } from '../../services/manual-annotation.service';
|
||||
@ -21,8 +21,7 @@ export interface LegalBasisOption {
|
||||
templateUrl: './force-annotation-dialog.component.html',
|
||||
styleUrls: ['./force-annotation-dialog.component.scss'],
|
||||
})
|
||||
export class ForceAnnotationDialogComponent implements OnInit {
|
||||
redactionForm: FormGroup;
|
||||
export class ForceAnnotationDialogComponent extends BaseDialogComponent implements OnInit {
|
||||
isDocumentAdmin: boolean;
|
||||
legalOptions: LegalBasisOption[] = [];
|
||||
|
||||
@ -35,10 +34,13 @@ export class ForceAnnotationDialogComponent implements OnInit {
|
||||
private readonly _justificationsService: JustificationsService,
|
||||
private readonly _manualAnnotationService: ManualAnnotationService,
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
public dialogRef: MatDialogRef<ForceAnnotationDialogComponent>,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<ForceAnnotationDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) private readonly _data: { readonly dossier: Dossier; readonly hint: boolean },
|
||||
) {
|
||||
this.redactionForm = this._getForm();
|
||||
super(_injector, _dialogRef);
|
||||
this.form = this._getForm();
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
}
|
||||
|
||||
get isHintDialog() {
|
||||
@ -55,6 +57,7 @@ export class ForceAnnotationDialogComponent implements OnInit {
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
super.ngOnInit();
|
||||
const data = await this._justificationsService.getForDossierTemplate(this._data.dossier.dossierTemplateId).toPromise();
|
||||
|
||||
this.legalOptions = data.map(lbm => ({
|
||||
@ -66,17 +69,17 @@ export class ForceAnnotationDialogComponent implements OnInit {
|
||||
this.legalOptions.sort((a, b) => a.label.localeCompare(b.label));
|
||||
}
|
||||
|
||||
handleForceAnnotation() {
|
||||
this.dialogRef.close(this._createForceRedactionRequest());
|
||||
save() {
|
||||
this._dialogRef.close(this._createForceRedactionRequest());
|
||||
}
|
||||
|
||||
private _createForceRedactionRequest(): ILegalBasisChangeRequest {
|
||||
const request: ILegalBasisChangeRequest = {};
|
||||
|
||||
const legalOption: LegalBasisOption = this.redactionForm.get('reason').value;
|
||||
const legalOption: LegalBasisOption = this.form.get('reason').value;
|
||||
|
||||
request.legalBasis = legalOption.legalBasis;
|
||||
request.comment = this.redactionForm.get('comment').value;
|
||||
request.comment = this.form.get('comment').value;
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<section class="dialog">
|
||||
<form (submit)="handleAddRedaction()" [formGroup]="redactionForm">
|
||||
<form (submit)="save()" [formGroup]="form">
|
||||
<div [translate]="title" class="dialog-header heading-l"></div>
|
||||
|
||||
<div class="dialog-content">
|
||||
@ -54,7 +54,7 @@
|
||||
|
||||
<div *ngIf="!isDictionaryRequest" class="iqser-input-group w-400">
|
||||
<label translate="manual-annotation.dialog.content.legalBasis"></label>
|
||||
<input [value]="redactionForm.get('reason').value?.legalBasis" disabled type="text" />
|
||||
<input [value]="form.get('reason').value?.legalBasis" disabled type="text" />
|
||||
</div>
|
||||
|
||||
<div *ngIf="data.manualRedactionEntryWrapper.manualRedactionEntry.rectangle" class="iqser-input-group w-400">
|
||||
@ -74,11 +74,11 @@
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button [disabled]="!redactionForm.valid" color="primary" mat-flat-button type="submit">
|
||||
<button [disabled]="disabled" color="primary" mat-flat-button type="submit">
|
||||
{{ 'manual-annotation.dialog.actions.save' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
|
||||
</section>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { Component, Inject, Injector, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
@ -9,6 +9,8 @@ import { PermissionsService } from '@services/permissions.service';
|
||||
import { JustificationsService } from '@services/entity-services/justifications.service';
|
||||
import { Dictionary, Dossier, File, IAddRedactionRequest } from '@red/domain';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { BaseDialogComponent } from '@iqser/common-ui';
|
||||
import { DictionaryService } from '@shared/services/dictionary.service';
|
||||
|
||||
export interface LegalBasisOption {
|
||||
label?: string;
|
||||
@ -21,9 +23,7 @@ export interface LegalBasisOption {
|
||||
templateUrl: './manual-annotation-dialog.component.html',
|
||||
styleUrls: ['./manual-annotation-dialog.component.scss'],
|
||||
})
|
||||
export class ManualAnnotationDialogComponent implements OnInit {
|
||||
redactionForm: FormGroup;
|
||||
|
||||
export class ManualAnnotationDialogComponent extends BaseDialogComponent implements OnInit {
|
||||
isDocumentAdmin: boolean;
|
||||
isDictionaryRequest: boolean;
|
||||
isFalsePositiveRequest: boolean;
|
||||
@ -40,18 +40,21 @@ export class ManualAnnotationDialogComponent implements OnInit {
|
||||
private readonly _manualAnnotationService: ManualAnnotationService,
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
private readonly _dossiersService: DossiersService,
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
public dialogRef: MatDialogRef<ManualAnnotationDialogComponent>,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<ManualAnnotationDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: { manualRedactionEntryWrapper: ManualRedactionEntryWrapper; file: File },
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
this._dossier = this._dossiersService.find(this.data.file.dossierId);
|
||||
this.isDocumentAdmin = this._permissionsService.isApprover(this._dossier);
|
||||
|
||||
this.isFalsePositiveRequest = this.data.manualRedactionEntryWrapper.type === 'FALSE_POSITIVE';
|
||||
this.isDictionaryRequest = this.data.manualRedactionEntryWrapper.type === 'DICTIONARY' || this.isFalsePositiveRequest;
|
||||
|
||||
this.redactionForm = this._getForm();
|
||||
|
||||
this.possibleDictionaries = this._possibleDictionaries;
|
||||
this.form = this._getForm();
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
}
|
||||
|
||||
get title() {
|
||||
@ -59,17 +62,21 @@ export class ManualAnnotationDialogComponent implements OnInit {
|
||||
}
|
||||
|
||||
get displayedDictionaryLabel() {
|
||||
const dictType = this.redactionForm.get('dictionary').value;
|
||||
const dictType = this.form.get('dictionary').value;
|
||||
if (dictType) {
|
||||
return this.possibleDictionaries.find(d => d.type === dictType).label;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private get _possibleDictionaries(): Dictionary[] {
|
||||
private async _getPossibleDictionaries(): Promise<Dictionary[]> {
|
||||
const possibleDictionaries: Dictionary[] = [];
|
||||
const dossier = this._dossier;
|
||||
|
||||
const dossierDictionary = await this._dictionaryService
|
||||
.getForType(dossier.dossierTemplateId, 'dossier_redaction', dossier.dossierId)
|
||||
.toPromise();
|
||||
|
||||
for (const key of Object.keys(this._appStateService.dictionaryData[dossier.dossierTemplateId])) {
|
||||
const dictionaryData = this._appStateService.getDictionary(key, dossier.dossierTemplateId);
|
||||
if (!dictionaryData.virtual && dictionaryData.addToDictionaryAction) {
|
||||
@ -77,14 +84,21 @@ export class ManualAnnotationDialogComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
if (dossierDictionary.addToDictionaryAction) {
|
||||
// TODO fix this in the backend
|
||||
possibleDictionaries.push(new Dictionary({ ...dossierDictionary, type: 'dossier_redaction' }));
|
||||
}
|
||||
|
||||
possibleDictionaries.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
return possibleDictionaries;
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
const data = await this._justificationsService.getForDossierTemplate(this._dossier.dossierTemplateId).toPromise();
|
||||
super.ngOnInit();
|
||||
this.possibleDictionaries = await this._getPossibleDictionaries();
|
||||
|
||||
const data = await this._justificationsService.getForDossierTemplate(this._dossier.dossierTemplateId).toPromise();
|
||||
this.legalOptions = data.map(lbm => ({
|
||||
legalBasis: lbm.reason,
|
||||
description: lbm.description,
|
||||
@ -94,11 +108,11 @@ export class ManualAnnotationDialogComponent implements OnInit {
|
||||
this.legalOptions.sort((a, b) => a.label.localeCompare(b.label));
|
||||
}
|
||||
|
||||
handleAddRedaction() {
|
||||
save() {
|
||||
this._enhanceManualRedaction(this.data.manualRedactionEntryWrapper.manualRedactionEntry);
|
||||
this._manualAnnotationService.addAnnotation(this.data.manualRedactionEntryWrapper.manualRedactionEntry, this.data.file).subscribe(
|
||||
response => this.dialogRef.close(new ManualAnnotationResponse(this.data.manualRedactionEntryWrapper, response)),
|
||||
() => this.dialogRef.close(),
|
||||
response => this._dialogRef.close(new ManualAnnotationResponse(this.data.manualRedactionEntryWrapper, response)),
|
||||
() => this._dialogRef.close(),
|
||||
);
|
||||
}
|
||||
|
||||
@ -123,22 +137,26 @@ export class ManualAnnotationDialogComponent implements OnInit {
|
||||
}
|
||||
|
||||
private _enhanceManualRedaction(addRedactionRequest: IAddRedactionRequest) {
|
||||
const legalOption: LegalBasisOption = this.redactionForm.get('reason').value;
|
||||
addRedactionRequest.type = this.redactionForm.get('dictionary').value;
|
||||
const legalOption: LegalBasisOption = this.form.get('reason').value;
|
||||
addRedactionRequest.type = this.form.get('dictionary').value;
|
||||
if (legalOption) {
|
||||
addRedactionRequest.reason = legalOption.description;
|
||||
addRedactionRequest.legalBasis = legalOption.legalBasis;
|
||||
}
|
||||
addRedactionRequest.addToDictionary = this.isDictionaryRequest;
|
||||
// todo fix this in backend
|
||||
addRedactionRequest.addToDictionary = this.isDictionaryRequest && addRedactionRequest.type !== 'dossier_redaction';
|
||||
addRedactionRequest.addToDossierDictionary = this.isDictionaryRequest && addRedactionRequest.type === 'dossier_redaction';
|
||||
|
||||
if (!addRedactionRequest.reason) {
|
||||
addRedactionRequest.reason = 'Dictionary Request';
|
||||
}
|
||||
const commentValue = this.redactionForm.get('comment').value;
|
||||
const commentValue = this.form.get('comment').value;
|
||||
addRedactionRequest.comment = commentValue ? { text: commentValue } : null;
|
||||
addRedactionRequest.section = this.redactionForm.get('section').value;
|
||||
addRedactionRequest.value = addRedactionRequest.rectangle
|
||||
? this.redactionForm.get('classification').value
|
||||
: addRedactionRequest.value;
|
||||
addRedactionRequest.section = this.form.get('section').value;
|
||||
addRedactionRequest.value = addRedactionRequest.rectangle ? this.form.get('classification').value : addRedactionRequest.value;
|
||||
}
|
||||
|
||||
get disabled() {
|
||||
return this.form.invalid;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<section class="dialog">
|
||||
<form (submit)="save()" [formGroup]="recategorizeImageForm">
|
||||
<form (submit)="save()" [formGroup]="form">
|
||||
<div class="dialog-header heading-l" translate="recategorize-image-dialog.header"></div>
|
||||
|
||||
<div class="dialog-content">
|
||||
@ -23,12 +23,12 @@
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button [disabled]="!recategorizeImageForm.valid || !changed" color="primary" mat-flat-button type="submit">
|
||||
<button [disabled]="disabled" color="primary" mat-flat-button type="submit">
|
||||
{{ 'recategorize-image-dialog.actions.save' | translate }}
|
||||
</button>
|
||||
<div class="all-caps-label cancel" mat-dialog-close translate="recategorize-image-dialog.actions.cancel"></div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
|
||||
</section>
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { Component, Inject, Injector, OnInit } from '@angular/core';
|
||||
import { FormBuilder, Validators } from '@angular/forms';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { imageCategoriesTranslations } from '../../translations/image-categories-translations';
|
||||
import { ImageCategory } from '../../models/image-category.model';
|
||||
import { Dossier } from '@red/domain';
|
||||
import { BaseDialogComponent } from '@iqser/common-ui';
|
||||
|
||||
@Component({
|
||||
templateUrl: './recategorize-image-dialog.component.html',
|
||||
})
|
||||
export class RecategorizeImageDialogComponent implements OnInit {
|
||||
recategorizeImageForm: FormGroup;
|
||||
export class RecategorizeImageDialogComponent extends BaseDialogComponent implements OnInit {
|
||||
isDocumentAdmin: boolean;
|
||||
typeOptions: ImageCategory[] = ['signature', 'logo', 'formula', 'image'];
|
||||
translations = imageCategoriesTranslations;
|
||||
@ -19,27 +19,32 @@ export class RecategorizeImageDialogComponent implements OnInit {
|
||||
constructor(
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
public dialogRef: MatDialogRef<RecategorizeImageDialogComponent>,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<RecategorizeImageDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: { annotations: AnnotationWrapper[]; dossier: Dossier },
|
||||
) {}
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
}
|
||||
|
||||
get changed(): boolean {
|
||||
return this.recategorizeImageForm.get('type').value !== this.data.annotations[0].type;
|
||||
return this.form.get('type').value !== this.data.annotations[0].type;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
this.isDocumentAdmin = this._permissionsService.isApprover(this.data.dossier);
|
||||
|
||||
this.recategorizeImageForm = this._formBuilder.group({
|
||||
this.form = this._formBuilder.group({
|
||||
type: [this.data.annotations[0].type, Validators.required],
|
||||
comment: this.isDocumentAdmin ? [null] : [null, Validators.required],
|
||||
});
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
}
|
||||
|
||||
save() {
|
||||
this.dialogRef.close({
|
||||
type: this.recategorizeImageForm.get('type').value,
|
||||
comment: this.recategorizeImageForm.get('comment').value,
|
||||
this._dialogRef.close({
|
||||
type: this.form.get('type').value,
|
||||
comment: this.form.get('comment').value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
) | translate: { hint: data.hint }
|
||||
}}
|
||||
</div>
|
||||
<form (submit)="confirm()" [formGroup]="redactionForm">
|
||||
<form (submit)="save()" [formGroup]="form">
|
||||
<div class="dialog-content">
|
||||
{{
|
||||
(data.removeFromDictionary
|
||||
@ -46,12 +46,12 @@
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button [disabled]="!redactionForm.valid" color="primary" mat-flat-button type="submit">
|
||||
<button [disabled]="disabled" color="primary" mat-flat-button type="submit">
|
||||
{{ 'remove-annotations-dialog.confirm' | translate }}
|
||||
</button>
|
||||
<div class="all-caps-label cancel" mat-dialog-close translate="remove-annotations-dialog.cancel"></div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
|
||||
</section>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { Component, Inject, Injector } from '@angular/core';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { humanize } from '@iqser/common-ui';
|
||||
import { FormBuilder, Validators } from '@angular/forms';
|
||||
import { BaseDialogComponent, humanize } from '@iqser/common-ui';
|
||||
import { Dossier } from '@red/domain';
|
||||
|
||||
export interface RemoveAnnotationsDialogInput {
|
||||
@ -18,23 +18,24 @@ export interface RemoveAnnotationsDialogInput {
|
||||
templateUrl: './remove-annotations-dialog.component.html',
|
||||
styleUrls: ['./remove-annotations-dialog.component.scss'],
|
||||
})
|
||||
export class RemoveAnnotationsDialogComponent {
|
||||
redactionForm: FormGroup;
|
||||
|
||||
export class RemoveAnnotationsDialogComponent extends BaseDialogComponent {
|
||||
constructor(
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
readonly permissionsService: PermissionsService,
|
||||
public dialogRef: MatDialogRef<RemoveAnnotationsDialogComponent>,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<RemoveAnnotationsDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: RemoveAnnotationsDialogInput,
|
||||
) {
|
||||
this.redactionForm = this._formBuilder.group({
|
||||
super(_injector, _dialogRef);
|
||||
this.form = this._formBuilder.group({
|
||||
comment: this.permissionsService.isApprover(this.data.dossier) ? [null] : [null, Validators.required],
|
||||
});
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
}
|
||||
|
||||
confirm() {
|
||||
this.dialogRef.close({ comment: this.redactionForm.getRawValue().comment });
|
||||
save() {
|
||||
this._dialogRef.close({ comment: this.form.getRawValue().comment });
|
||||
}
|
||||
|
||||
printable(annotation: AnnotationWrapper) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<section class="dialog">
|
||||
<form (submit)="save()" [formGroup]="resizeForm">
|
||||
<form (submit)="save()" [formGroup]="form">
|
||||
<div class="dialog-header heading-l" translate="resize-annotation-dialog.header"></div>
|
||||
|
||||
<div class="dialog-content">
|
||||
@ -10,12 +10,12 @@
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button [disabled]="!resizeForm.valid" color="primary" mat-flat-button type="submit">
|
||||
<button [disabled]="disabled" color="primary" mat-flat-button type="submit">
|
||||
{{ 'resize-annotation-dialog.actions.save' | translate }}
|
||||
</button>
|
||||
<div class="all-caps-label cancel" mat-dialog-close translate="resize-annotation-dialog.actions.cancel"></div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
|
||||
</section>
|
||||
|
||||
@ -1,34 +1,40 @@
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { Component, Inject, Injector, OnInit } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { FormBuilder, Validators } from '@angular/forms';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { Dossier } from '@red/domain';
|
||||
import { BaseDialogComponent } from '@iqser/common-ui';
|
||||
|
||||
@Component({
|
||||
templateUrl: './resize-annotation-dialog.component.html',
|
||||
})
|
||||
export class ResizeAnnotationDialogComponent implements OnInit {
|
||||
resizeForm: FormGroup;
|
||||
export class ResizeAnnotationDialogComponent extends BaseDialogComponent implements OnInit {
|
||||
isDocumentAdmin: boolean;
|
||||
|
||||
constructor(
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
public dialogRef: MatDialogRef<ResizeAnnotationDialogComponent>,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<ResizeAnnotationDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) private readonly _data: { dossier: Dossier },
|
||||
) {}
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
|
||||
this.isDocumentAdmin = this._permissionsService.isApprover(this._data.dossier);
|
||||
|
||||
this.resizeForm = this._formBuilder.group({
|
||||
this.form = this._formBuilder.group({
|
||||
comment: this.isDocumentAdmin ? [null] : [null, Validators.required],
|
||||
});
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
}
|
||||
|
||||
save() {
|
||||
this.dialogRef.close({
|
||||
comment: this.resizeForm.get('comment').value,
|
||||
this._dialogRef.close({
|
||||
comment: this.form.get('comment').value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,10 +10,7 @@ import { SharedModule } from '@shared/shared.module';
|
||||
import { DossiersRoutingModule } from './dossiers-routing.module';
|
||||
import { FileUploadDownloadModule } from '@upload-download/file-upload-download.module';
|
||||
import { DossiersDialogService } from './services/dossiers-dialog.service';
|
||||
import { AnnotationActionsService } from './services/annotation-actions.service';
|
||||
import { PdfViewerDataService } from './services/pdf-viewer-data.service';
|
||||
import { ManualAnnotationService } from './services/manual-annotation.service';
|
||||
import { AnnotationDrawService } from './services/annotation-draw.service';
|
||||
import { AnnotationProcessingService } from './services/annotation-processing.service';
|
||||
import { EditDossierDialogComponent } from './dialogs/edit-dossier-dialog/edit-dossier-dialog.component';
|
||||
import { EditDossierGeneralInfoComponent } from './dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component';
|
||||
@ -56,14 +53,7 @@ const components = [
|
||||
...dialogs,
|
||||
];
|
||||
|
||||
const services = [
|
||||
DossiersDialogService,
|
||||
AnnotationActionsService,
|
||||
ManualAnnotationService,
|
||||
PdfViewerDataService,
|
||||
AnnotationDrawService,
|
||||
AnnotationProcessingService,
|
||||
];
|
||||
const services = [DossiersDialogService, ManualAnnotationService, AnnotationProcessingService];
|
||||
|
||||
@NgModule({
|
||||
declarations: [...components],
|
||||
|
||||
@ -5,10 +5,15 @@
|
||||
[showCloseButton]="true"
|
||||
[viewModeSelection]="viewModeSelection"
|
||||
>
|
||||
<redaction-file-download-btn [files]="entitiesService.all$ | async" tooltipPosition="below"></redaction-file-download-btn>
|
||||
<redaction-file-download-btn
|
||||
[disabled]="listingService.areSomeSelected$ | async"
|
||||
[files]="entitiesService.all$ | async"
|
||||
tooltipPosition="below"
|
||||
></redaction-file-download-btn>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="exportFilesAsCSV()"
|
||||
[disabled]="listingService.areSomeSelected$ | async"
|
||||
[tooltip]="'dossier-overview.header-actions.download-csv' | translate"
|
||||
icon="iqser:csv"
|
||||
tooltipPosition="below"
|
||||
@ -17,7 +22,8 @@
|
||||
<iqser-circle-button
|
||||
(action)="reanalyseDossier()"
|
||||
*ngIf="permissionsService.displayReanalyseBtn(dossier) && analysisForced"
|
||||
[tooltipClass]="'small ' + ((listingService.areSomeSelected$ | async) ? '' : 'warn')"
|
||||
[disabled]="listingService.areSomeSelected$ | async"
|
||||
[tooltipClass]="'small warn'"
|
||||
[tooltip]="'dossier-overview.new-rule.toast.actions.reanalyse-all' | translate"
|
||||
[type]="circleButtonTypes.warn"
|
||||
icon="iqser:refresh"
|
||||
|
||||
@ -47,7 +47,7 @@ export class ScreenHeaderComponent implements OnInit {
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.actionConfigs = this.configService.actionConfig(this.dossier.dossierId);
|
||||
this.actionConfigs = this.configService.actionConfig(this.dossier.dossierId, this.listingService.areSomeSelected$);
|
||||
}
|
||||
|
||||
async reanalyseDossier() {
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
<div class="needs-work">
|
||||
<redaction-annotation-icon *ngIf="file.analysisRequired" [color]="analysisColor" label="A" type="square"></redaction-annotation-icon>
|
||||
<redaction-annotation-icon *ngIf="file.hasUpdates" [color]="updatedColor" label="U" type="square"></redaction-annotation-icon>
|
||||
<redaction-annotation-icon
|
||||
*ngIf="file.hasUpdates && file.assignee === userService.currentUser.id"
|
||||
[color]="updatedColor"
|
||||
label="U"
|
||||
type="square"
|
||||
></redaction-annotation-icon>
|
||||
<redaction-annotation-icon *ngIf="file.hasRedactions" [color]="redactionColor" label="R" type="square"></redaction-annotation-icon>
|
||||
<redaction-annotation-icon *ngIf="file.hasImages" [color]="imageColor" label="I" type="square"></redaction-annotation-icon>
|
||||
<redaction-annotation-icon *ngIf="file.hintsOnly" [color]="hintColor" label="H" type="circle"></redaction-annotation-icon>
|
||||
|
||||
@ -2,6 +2,7 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { File } from '@red/domain';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { UserService } from '../../../../../../../services/user.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-file-workload',
|
||||
@ -12,7 +13,11 @@ import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
export class FileWorkloadComponent {
|
||||
@Input() file: File;
|
||||
|
||||
constructor(private readonly _appStateService: AppStateService, private readonly _dossiersService: DossiersService) {}
|
||||
constructor(
|
||||
public readonly userService: UserService,
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _dossiersService: DossiersService,
|
||||
) {}
|
||||
|
||||
get suggestionColor() {
|
||||
return this._getDictionaryColor('suggestion');
|
||||
|
||||
@ -102,7 +102,7 @@ export class ConfigService {
|
||||
};
|
||||
}
|
||||
|
||||
actionConfig(dossierId: string): List<ActionConfig> {
|
||||
actionConfig(dossierId: string, disabled$: Observable<boolean>): List<ActionConfig> {
|
||||
return [
|
||||
{
|
||||
label: this._translateService.instant('dossier-overview.header-actions.edit'),
|
||||
@ -110,6 +110,7 @@ export class ConfigService {
|
||||
icon: 'iqser:edit',
|
||||
hide: !this._userService.currentUser.isManager,
|
||||
helpModeKey: 'edit-dossier-attributes',
|
||||
disabled$,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@ -15,19 +15,19 @@ import { FileDropOverlayService } from '@upload-download/services/file-drop-over
|
||||
import { FileUploadModel } from '@upload-download/model/file-upload.model';
|
||||
import { FileUploadService } from '@upload-download/services/file-upload.service';
|
||||
import { StatusOverlayService } from '@upload-download/services/status-overlay.service';
|
||||
import * as moment from 'moment';
|
||||
import { Observable, timer } from 'rxjs';
|
||||
import { Observable } from 'rxjs';
|
||||
import { filter, skip, switchMap, tap } from 'rxjs/operators';
|
||||
import { convertFiles, Files, handleFileDrop } from '@utils/index';
|
||||
import {
|
||||
CircleButtonTypes,
|
||||
CustomError,
|
||||
DefaultListingServices,
|
||||
ErrorService,
|
||||
ListingComponent,
|
||||
ListingModes,
|
||||
LoadingService,
|
||||
NestedFilter,
|
||||
OnAttach,
|
||||
OnDetach,
|
||||
TableColumnConfig,
|
||||
TableComponent,
|
||||
WorkflowConfig,
|
||||
@ -37,7 +37,6 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||
import { ConfigService as AppConfigService } from '@services/config.service';
|
||||
import { ConfigService } from '../config.service';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
@ -45,7 +44,6 @@ import { LongPressEvent } from '@shared/directives/long-press.directive';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { FilesMapService } from '@services/entity-services/files-map.service';
|
||||
import { FilesService } from '@services/entity-services/files.service';
|
||||
import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
|
||||
|
||||
@Component({
|
||||
templateUrl: './dossier-overview-screen.component.html',
|
||||
@ -53,7 +51,7 @@ import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
|
||||
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DossierOverviewScreenComponent) }],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class DossierOverviewScreenComponent extends ListingComponent<File> implements OnInit, OnDestroy, OnDetach, OnAttach {
|
||||
export class DossierOverviewScreenComponent extends ListingComponent<File> implements OnInit, OnDestroy, OnAttach {
|
||||
readonly listingModes = ListingModes;
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly tableHeaderLabel = _('dossier-overview.table-header.title');
|
||||
@ -78,21 +76,21 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
|
||||
constructor(
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _router: Router,
|
||||
readonly permissionsService: PermissionsService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _dossiersService: DossiersService,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _appConfigService: AppConfigService,
|
||||
private readonly _fileUploadService: FileUploadService,
|
||||
private readonly _filesService: FilesService,
|
||||
private readonly _statusOverlayService: StatusOverlayService,
|
||||
private readonly _fileDropOverlayService: FileDropOverlayService,
|
||||
private readonly _dossierAttributesService: DossierAttributesService,
|
||||
private readonly _fileAttributesService: FileAttributesService,
|
||||
readonly configService: ConfigService,
|
||||
private readonly _userPreferenceService: UserPreferenceService,
|
||||
private readonly _fileMapService: FilesMapService,
|
||||
activatedRoute: ActivatedRoute,
|
||||
private readonly _errorService: ErrorService,
|
||||
readonly permissionsService: PermissionsService,
|
||||
readonly configService: ConfigService,
|
||||
readonly activatedRoute: ActivatedRoute,
|
||||
) {
|
||||
super(_injector);
|
||||
this.dossierId = activatedRoute.snapshot.paramMap.get('dossierId');
|
||||
@ -118,6 +116,8 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
|
||||
async ngOnInit(): Promise<void> {
|
||||
this._loadEntitiesFromState();
|
||||
|
||||
this._setRemovableSubscriptions();
|
||||
|
||||
this.addSubscription = this._fileMapService
|
||||
.get$(this.dossierId)
|
||||
.pipe(
|
||||
@ -128,18 +128,17 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
|
||||
|
||||
this._fileDropOverlayService.initFileDropHandling(this.dossierId);
|
||||
|
||||
this.addSubscription = timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL)
|
||||
.pipe(
|
||||
switchMap(() => this._filesService.hasChanges$(this.dossierId)),
|
||||
filter(changed => changed),
|
||||
switchMap(() => this._reloadFiles()),
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
this.addSubscription = this.configService.listingMode$.subscribe(() => {
|
||||
this._computeAllFilters();
|
||||
});
|
||||
|
||||
this.addSubscription = this._dossiersService.dossierFileChanges$
|
||||
.pipe(
|
||||
filter(dossierId => dossierId === this.dossierId),
|
||||
switchMap(dossierId => this._filesService.loadAll(dossierId)),
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
this.addSubscription = this._dossierTemplatesService
|
||||
.getEntityChanged$(this.currentDossier.dossierTemplateId)
|
||||
.pipe(
|
||||
@ -166,11 +165,10 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
|
||||
|
||||
ngOnAttach() {
|
||||
this._fileDropOverlayService.initFileDropHandling(this.dossierId);
|
||||
this._setRemovableSubscriptions();
|
||||
this._tableComponent?.scrollToLastIndex();
|
||||
}
|
||||
|
||||
ngOnDetach() {}
|
||||
|
||||
forceReanalysisAction($event: LongPressEvent) {
|
||||
this.analysisForced = !$event.touchEnd && this._userPreferenceService.areDevFeaturesEnabled;
|
||||
}
|
||||
@ -192,8 +190,18 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
|
||||
(this._fileInput as any).nativeElement.value = null;
|
||||
}
|
||||
|
||||
recentlyModifiedChecker = (file: File) =>
|
||||
moment(file.lastUpdated).add(this._appConfigService.values.RECENT_PERIOD_IN_HOURS, 'hours').isAfter(moment());
|
||||
private _setRemovableSubscriptions(): void {
|
||||
this.addActiveScreenSubscription = this._dossiersService
|
||||
.getEntityDeleted$(this.dossierId)
|
||||
.pipe(tap(() => this._handleDeletedDossier()))
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
private _handleDeletedDossier(): void {
|
||||
this._errorService.set(
|
||||
new CustomError(_('error.deleted-entity.dossier.label'), _('error.deleted-entity.dossier.action'), 'iqser:expand'),
|
||||
);
|
||||
}
|
||||
|
||||
private _updateFileAttributes(): void {
|
||||
this._fileAttributeConfigs =
|
||||
@ -204,11 +212,6 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
|
||||
this._computeAllFilters();
|
||||
}
|
||||
|
||||
private async _reloadFiles() {
|
||||
await this._filesService.loadAll(this.dossierId).toPromise();
|
||||
this._computeAllFilters();
|
||||
}
|
||||
|
||||
private _loadEntitiesFromState() {
|
||||
this.currentDossier = this._dossiersService.find(this.dossierId);
|
||||
this._computeAllFilters();
|
||||
|
||||
@ -38,10 +38,10 @@ export class DossiersListingDetailsComponent {
|
||||
|
||||
private async _toDossierChartData(dossiers: Dossier[]): Promise<DoughnutChartConfig[]> {
|
||||
// TODO: deleted dossiers count should come with stats
|
||||
const deletedDossiers = await this.dossiersService.getDeleted();
|
||||
// const deletedDossiers = await this.dossiersService.getDeleted();
|
||||
return [
|
||||
{ value: dossiers.length, color: 'ACTIVE', label: _('active') },
|
||||
{ value: deletedDossiers.length, color: 'DELETED', label: _('archived') },
|
||||
// { value: deletedDossiers.length, color: 'DELETED', label: _('archived') },
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -1,19 +1,17 @@
|
||||
import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { Dossier } from '@red/domain';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { TranslateChartService } from '@services/translate-chart.service';
|
||||
import { timer } from 'rxjs';
|
||||
import { Router } from '@angular/router';
|
||||
import { DossiersDialogService } from '../../../services/dossiers-dialog.service';
|
||||
import { DefaultListingServicesTmp, EntitiesService, ListingComponent, OnAttach, OnDetach, TableComponent } from '@iqser/common-ui';
|
||||
import { DefaultListingServicesTmp, EntitiesService, ListingComponent, OnAttach, TableComponent } from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { ConfigService } from '../config.service';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { FilesService } from '@services/entity-services/files.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { switchMap, tap } from 'rxjs/operators';
|
||||
import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
|
||||
import { tap } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
templateUrl: './dossiers-listing-screen.component.html',
|
||||
@ -25,7 +23,7 @@ import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class DossiersListingScreenComponent extends ListingComponent<Dossier> implements OnInit, OnDestroy, OnAttach, OnDetach {
|
||||
export class DossiersListingScreenComponent extends ListingComponent<Dossier> implements OnInit, OnAttach {
|
||||
readonly currentUser = this._userService.currentUser;
|
||||
readonly tableColumnConfigs = this._configService.tableConfig;
|
||||
readonly tableHeaderLabel = _('dossier-listing.table-header.title');
|
||||
@ -57,22 +55,13 @@ export class DossiersListingScreenComponent extends ListingComponent<Dossier> im
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.addSubscription = timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL)
|
||||
.pipe(switchMap(() => this._dossiersService.loadAllIfChanged()))
|
||||
.subscribe();
|
||||
|
||||
this.addSubscription = this._dossiersService.all$.pipe(tap(() => this._computeAllFilters())).subscribe();
|
||||
}
|
||||
|
||||
ngOnAttach(): void {
|
||||
this.ngOnInit();
|
||||
this._tableComponent?.scrollToLastIndex();
|
||||
}
|
||||
|
||||
ngOnDetach(): void {
|
||||
this.ngOnDestroy();
|
||||
}
|
||||
|
||||
openAddDossierDialog(): void {
|
||||
this._dialogService.openDialog('addDossier', null, null, async (addResponse: { dossier: Dossier; addMembers: boolean }) => {
|
||||
await this._router.navigate([addResponse.dossier.routerLink]);
|
||||
|
||||
@ -2,7 +2,7 @@ import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { AnnotationPermissions } from '@models/file/annotation.permissions';
|
||||
import { AnnotationActionsService } from '../../../../services/annotation-actions.service';
|
||||
import { AnnotationActionsService } from '../../services/annotation-actions.service';
|
||||
import { WebViewerInstance } from '@pdftron/webviewer';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { Dossier, File } from '@red/domain';
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
<div class="comment-actions">
|
||||
<iqser-circle-button
|
||||
(action)="deleteComment(comment)"
|
||||
(action)="deleteComment($event, comment)"
|
||||
*ngIf="permissionsService.canDeleteComment(comment, file)"
|
||||
[iconSize]="10"
|
||||
[size]="20"
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostBinding, Input, ViewChild } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostBinding, Input, OnChanges, ViewChild } from '@angular/core';
|
||||
import { File, IComment } from '@red/domain';
|
||||
import { ManualAnnotationService } from '../../../../services/manual-annotation.service';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { InputWithActionComponent, trackBy } from '@iqser/common-ui';
|
||||
import { AutoUnsubscribe, InputWithActionComponent, LoadingService, trackBy } from '@iqser/common-ui';
|
||||
import { FilesMapService } from '@services/entity-services/files-map.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { CommentingService } from '../../services/commenting.service';
|
||||
import { tap } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-comments',
|
||||
@ -15,11 +17,11 @@ import { Observable } from 'rxjs';
|
||||
styleUrls: ['./comments.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CommentsComponent {
|
||||
export class CommentsComponent extends AutoUnsubscribe implements OnChanges {
|
||||
@Input() annotation: AnnotationWrapper;
|
||||
readonly trackBy = trackBy();
|
||||
readonly file$: Observable<File>;
|
||||
@HostBinding('class.hidden') private _hidden = true;
|
||||
@HostBinding('class.hidden') _hidden = true;
|
||||
@ViewChild(InputWithActionComponent) private readonly _input: InputWithActionComponent;
|
||||
private readonly _fileId: string;
|
||||
private readonly _dossierId: string;
|
||||
@ -28,49 +30,59 @@ export class CommentsComponent {
|
||||
readonly permissionsService: PermissionsService,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _manualAnnotationService: ManualAnnotationService,
|
||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
||||
private readonly _commentingService: CommentingService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _changeRef: ChangeDetectorRef,
|
||||
readonly filesMapService: FilesMapService,
|
||||
activatedRoute: ActivatedRoute,
|
||||
) {
|
||||
super();
|
||||
this._fileId = activatedRoute.snapshot.paramMap.get('fileId');
|
||||
this._dossierId = activatedRoute.snapshot.paramMap.get('dossierId');
|
||||
this.file$ = filesMapService.watch$(this._dossierId, this._fileId);
|
||||
}
|
||||
|
||||
addComment(value: string): void {
|
||||
ngOnChanges() {
|
||||
this.addSubscription = this._commentingService
|
||||
.isActive$(this.annotation.id)
|
||||
.pipe(
|
||||
tap(active => {
|
||||
this._hidden = !active;
|
||||
}),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
async addComment(value: string): Promise<void> {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
this._manualAnnotationService
|
||||
this._loadingService.start();
|
||||
const commentId = await this._manualAnnotationService
|
||||
.addComment(value, this.annotation.id, this._dossierId, this._fileId)
|
||||
.toPromise()
|
||||
.then(commentId => {
|
||||
this.annotation.comments.push({
|
||||
text: value,
|
||||
id: commentId,
|
||||
annotationId: this.annotation.id,
|
||||
user: this._userService.currentUser.id,
|
||||
});
|
||||
this._input.reset();
|
||||
this._changeDetectorRef.markForCheck();
|
||||
});
|
||||
.toPromise();
|
||||
this.annotation.comments.push({
|
||||
text: value,
|
||||
id: commentId,
|
||||
annotationId: this.annotation.id,
|
||||
user: this._userService.currentUser.id,
|
||||
});
|
||||
this._input.reset();
|
||||
this._changeRef.markForCheck();
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
toggleExpandComments($event?: MouseEvent): void {
|
||||
$event?.stopPropagation();
|
||||
this._hidden = !this._hidden;
|
||||
this._commentingService.toggle(this.annotation.id);
|
||||
}
|
||||
|
||||
deleteComment(comment: IComment): void {
|
||||
this._manualAnnotationService
|
||||
.deleteComment(comment.id, this.annotation.id, this._dossierId, this._fileId)
|
||||
.toPromise()
|
||||
.then(() => {
|
||||
this.annotation.comments.splice(this.annotation.comments.indexOf(comment), 1);
|
||||
if (!this.annotation.comments.length) {
|
||||
this._hidden = true;
|
||||
}
|
||||
this._changeDetectorRef.markForCheck();
|
||||
});
|
||||
async deleteComment($event: MouseEvent, comment: IComment): Promise<void> {
|
||||
$event.stopPropagation();
|
||||
this._loadingService.start();
|
||||
await this._manualAnnotationService.deleteComment(comment.id, this.annotation.id, this._dossierId, this._fileId).toPromise();
|
||||
this.annotation.comments.splice(this.annotation.comments.indexOf(comment), 1);
|
||||
this._changeRef.markForCheck();
|
||||
this._loadingService.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,13 +38,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!isReadOnly && file.isProcessing" class="justify-center read-only d-flex">
|
||||
<div class="flex-align-items-center">
|
||||
<span [translate]="'file-status.processing'" class="read-only-text"></span>
|
||||
<mat-progress-bar [mode]="'indeterminate'" class="w-100"></mat-progress-bar>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="multiSelectActive$ | async" class="multi-select">
|
||||
<div class="selected-wrapper">
|
||||
<iqser-round-checkbox
|
||||
@ -103,7 +96,6 @@
|
||||
[file]="file"
|
||||
[number]="pageNumber"
|
||||
[showDottedIcon]="hasOnlyManualRedactionsAndIsExcluded(pageNumber)"
|
||||
[viewedPages]="viewedPages"
|
||||
></redaction-page-indicator>
|
||||
</div>
|
||||
|
||||
@ -224,9 +216,9 @@
|
||||
|
||||
<ng-template #annotationFilterActionTemplate let-filter="filter">
|
||||
<iqser-circle-button
|
||||
(action)="toggleSkipped.emit($event)"
|
||||
(action)="skippedService.toggleSkipped($event)"
|
||||
*ngIf="filter.id === 'skipped'"
|
||||
[icon]="hideSkipped ? 'red:visibility-off' : 'red:visibility'"
|
||||
[icon]="(skippedService.hideSkipped$ | async) ? 'red:visibility-off' : 'red:visibility'"
|
||||
[type]="circleButtonTypes.dark"
|
||||
></iqser-circle-button>
|
||||
</ng-template>
|
||||
|
||||
@ -28,10 +28,11 @@ import { PermissionsService } from '@services/permissions.service';
|
||||
import { WebViewerInstance } from '@pdftron/webviewer';
|
||||
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
import { File, IViewedPage } from '@red/domain';
|
||||
import { File } from '@red/domain';
|
||||
import { ExcludedPagesService } from '../../services/excluded-pages.service';
|
||||
import { MultiSelectService } from '../../services/multi-select.service';
|
||||
import { DocumentInfoService } from '../../services/document-info.service';
|
||||
import { SkippedService } from '../../services/skipped.service';
|
||||
|
||||
const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape'];
|
||||
const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
|
||||
@ -51,16 +52,13 @@ export class FileWorkloadComponent {
|
||||
@Input() activeViewerPage: number;
|
||||
@Input() shouldDeselectAnnotationsOnPageChange: boolean;
|
||||
@Input() dialogRef: MatDialogRef<unknown>;
|
||||
@Input() viewedPages: IViewedPage[];
|
||||
@Input() file!: File;
|
||||
@Input() hideSkipped: boolean;
|
||||
@Input() annotationActionsTemplate: TemplateRef<unknown>;
|
||||
@Input() viewer: WebViewerInstance;
|
||||
@Output() readonly shouldDeselectAnnotationsOnPageChangeChange = new EventEmitter<boolean>();
|
||||
@Output() readonly selectAnnotations = new EventEmitter<AnnotationWrapper[]>();
|
||||
@Output() readonly deselectAnnotations = new EventEmitter<AnnotationWrapper[]>();
|
||||
@Output() readonly selectPage = new EventEmitter<number>();
|
||||
@Output() readonly toggleSkipped = new EventEmitter<MouseEvent>();
|
||||
@Output() readonly annotationsChanged = new EventEmitter<AnnotationWrapper>();
|
||||
displayedPages: number[] = [];
|
||||
pagesPanelActive = true;
|
||||
@ -76,6 +74,7 @@ export class FileWorkloadComponent {
|
||||
readonly excludedPagesService: ExcludedPagesService,
|
||||
readonly multiSelectService: MultiSelectService,
|
||||
readonly documentInfoService: DocumentInfoService,
|
||||
readonly skippedService: SkippedService,
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
||||
private readonly _filterService: FilterService,
|
||||
@ -225,7 +224,7 @@ export class FileWorkloadComponent {
|
||||
|
||||
scrollAnnotationsToPage(page: number, mode: 'always' | 'if-needed' = 'if-needed'): void {
|
||||
if (this._annotationsElement) {
|
||||
const elements = this._annotationsElement.nativeElement.querySelectorAll(`div[anotation-page-header="${page}"]`);
|
||||
const elements: HTMLElement[] = this._annotationsElement.nativeElement.querySelectorAll(`div[anotation-page-header="${page}"]`);
|
||||
FileWorkloadComponent._scrollToFirstElement(elements, mode);
|
||||
}
|
||||
}
|
||||
@ -235,7 +234,7 @@ export class FileWorkloadComponent {
|
||||
if (!this.selectedAnnotations || this.selectedAnnotations.length === 0 || !this._annotationsElement) {
|
||||
return;
|
||||
}
|
||||
const elements = this._annotationsElement.nativeElement.querySelectorAll(
|
||||
const elements: HTMLElement[] = this._annotationsElement.nativeElement.querySelectorAll(
|
||||
`div[annotation-id="${this._firstSelectedAnnotation?.id}"].active`,
|
||||
);
|
||||
FileWorkloadComponent._scrollToFirstElement(elements);
|
||||
@ -412,7 +411,7 @@ export class FileWorkloadComponent {
|
||||
|
||||
private _scrollQuickNavigationToPage(page: number) {
|
||||
if (this._quickNavigationElement) {
|
||||
const elements = this._quickNavigationElement.nativeElement.querySelectorAll(`#quick-nav-page-${page}`);
|
||||
const elements: HTMLElement[] = this._quickNavigationElement.nativeElement.querySelectorAll(`#quick-nav-page-${page}`);
|
||||
FileWorkloadComponent._scrollToFirstElement(elements);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import { ViewedPagesService } from '@services/entity-services/viewed-pages.servi
|
||||
import { File, IViewedPage } from '@red/domain';
|
||||
import { AutoUnsubscribe } from '@iqser/common-ui';
|
||||
import { FilesMapService } from '@services/entity-services/files-map.service';
|
||||
import { FilePreviewStateService } from '../../services/file-preview-state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-page-indicator',
|
||||
@ -18,7 +19,6 @@ export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy
|
||||
@Input() active = false;
|
||||
@Input() showDottedIcon = false;
|
||||
@Input() number: number;
|
||||
@Input() viewedPages: IViewedPage[];
|
||||
@Input() activeSelection = false;
|
||||
|
||||
@Output() readonly pageSelected = new EventEmitter<number>();
|
||||
@ -33,25 +33,17 @@ export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy
|
||||
private readonly _configService: ConfigService,
|
||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
||||
private readonly _permissionService: PermissionsService,
|
||||
private readonly _stateService: FilePreviewStateService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
get activePage() {
|
||||
return this.viewedPages?.find(p => p.page === this.number);
|
||||
return this._viewedPages.find(p => p.page === this.number);
|
||||
}
|
||||
|
||||
private _setReadState() {
|
||||
const readBefore = this.read;
|
||||
const activePage = this.activePage;
|
||||
if (!activePage) {
|
||||
this.read = false;
|
||||
} else {
|
||||
// console.log('setting read to',activePage.showAsUnseen, !activePage.showAsUnseen);
|
||||
this.read = !activePage.showAsUnseen;
|
||||
}
|
||||
// console.log(this.number, readBefore, activePage, this.read);
|
||||
this._changeDetectorRef.detectChanges();
|
||||
private get _viewedPages(): IViewedPage[] {
|
||||
return this._stateService.fileData?.viewedPages || [];
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
@ -87,20 +79,33 @@ export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy
|
||||
}
|
||||
}
|
||||
|
||||
private _setReadState() {
|
||||
const readBefore = this.read;
|
||||
const activePage = this.activePage;
|
||||
if (!activePage) {
|
||||
this.read = false;
|
||||
} else {
|
||||
// console.log('setting read to',activePage.showAsUnseen, !activePage.showAsUnseen);
|
||||
this.read = !activePage.showAsUnseen;
|
||||
}
|
||||
// console.log(this.number, readBefore, activePage, this.read);
|
||||
this._changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
private async _markPageRead() {
|
||||
await this._viewedPagesService.addPage({ page: this.number }, this.file.dossierId, this.file.fileId).toPromise();
|
||||
if (this.activePage) {
|
||||
this.activePage.showAsUnseen = false;
|
||||
} else {
|
||||
this.viewedPages?.push({ page: this.number, fileId: this.file.fileId });
|
||||
this._viewedPages.push({ page: this.number, fileId: this.file.fileId });
|
||||
}
|
||||
this._setReadState();
|
||||
}
|
||||
|
||||
private async _markPageUnread() {
|
||||
await this._viewedPagesService.removePage(this.file.dossierId, this.file.fileId, this.number).toPromise();
|
||||
this.viewedPages?.splice(
|
||||
this.viewedPages?.findIndex(p => p.page === this.number),
|
||||
this._viewedPages.splice(
|
||||
this._viewedPages.findIndex(p => p.page === this.number),
|
||||
1,
|
||||
);
|
||||
this._setReadState();
|
||||
|
||||
@ -22,12 +22,12 @@ import {
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { ManualAnnotationService } from '../../../../services/manual-annotation.service';
|
||||
import { environment } from '@environments/environment';
|
||||
import { AnnotationDrawService } from '../../../../services/annotation-draw.service';
|
||||
import { AnnotationActionsService } from '../../../../services/annotation-actions.service';
|
||||
import { AnnotationDrawService } from '../../services/annotation-draw.service';
|
||||
import { AnnotationActionsService } from '../../services/annotation-actions.service';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { BASE_HREF } from '../../../../../../tokens';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { ConfirmationDialogInput, LoadingService } from '@iqser/common-ui';
|
||||
import { AutoUnsubscribe, ConfirmationDialogInput, LoadingService, shareDistinctLast } from '@iqser/common-ui';
|
||||
import { DossiersDialogService } from '../../../../services/dossiers-dialog.service';
|
||||
import { loadCompareDocumentWrapper } from '../../../../utils/compare-mode.utils';
|
||||
import { PdfViewerUtils } from '../../../../utils/pdf-viewer.utils';
|
||||
@ -36,11 +36,13 @@ import { ActivatedRoute } from '@angular/router';
|
||||
import { toPosition } from '../../../../utils/pdf-calculation.utils';
|
||||
import { ViewModeService } from '../../services/view-mode.service';
|
||||
import { MultiSelectService } from '../../services/multi-select.service';
|
||||
import { FilePreviewStateService } from '../../services/file-preview-state.service';
|
||||
import { filter, switchMap, tap } from 'rxjs/operators';
|
||||
import Tools = Core.Tools;
|
||||
import TextTool = Tools.TextTool;
|
||||
import Annotation = Core.Annotations.Annotation;
|
||||
|
||||
const ALLOWED_KEYBOARD_SHORTCUTS = ['+', '-', 'p', 'r', 'Escape'] as const;
|
||||
const ALLOWED_KEYBOARD_SHORTCUTS: readonly string[] = ['+', '-', 'p', 'r', 'Escape'] as const;
|
||||
const dataElements = {
|
||||
ADD_REDACTION: 'add-redaction',
|
||||
ADD_DICTIONARY: 'add-dictionary',
|
||||
@ -61,8 +63,7 @@ const dataElements = {
|
||||
templateUrl: './pdf-viewer.component.html',
|
||||
styleUrls: ['./pdf-viewer.component.scss'],
|
||||
})
|
||||
export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
@Input() fileData: Blob;
|
||||
export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnChanges {
|
||||
@Input() file: File;
|
||||
@Input() dossier: Dossier;
|
||||
@Input() canPerformActions = false;
|
||||
@ -95,9 +96,12 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
private readonly _annotationActionsService: AnnotationActionsService,
|
||||
private readonly _configService: ConfigService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _stateService: FilePreviewStateService,
|
||||
readonly viewModeService: ViewModeService,
|
||||
readonly multiSelectService: MultiSelectService,
|
||||
) {}
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
private get _toggleTooltipsBtnTitle(): string {
|
||||
return this._translateService.instant(_('pdf-viewer.toggle-tooltips'), {
|
||||
@ -105,9 +109,27 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
});
|
||||
}
|
||||
|
||||
private get _toggleTooltipsIcon(): string {
|
||||
return this._convertPath(
|
||||
this._userPreferenceService.getFilePreviewTooltipsPreference()
|
||||
? '/assets/icons/general/pdftron-action-enable-tooltips.svg'
|
||||
: '/assets/icons/general/pdftron-action-disable-tooltips.svg',
|
||||
);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this._setReadyAndInitialState = this._setReadyAndInitialState.bind(this);
|
||||
await this.loadViewer();
|
||||
await this._loadViewer();
|
||||
|
||||
this.addActiveScreenSubscription = this._stateService.fileData$
|
||||
.pipe(
|
||||
filter(fileData => !!fileData),
|
||||
switchMap(fileData => fileData.blob$),
|
||||
// Skip document reload if file content hasn't changed
|
||||
shareDistinctLast(),
|
||||
tap(() => this._loadDocument()),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
@ -115,16 +137,12 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
return;
|
||||
}
|
||||
|
||||
if (changes.fileData) {
|
||||
this._loadDocument();
|
||||
}
|
||||
|
||||
if (changes.canPerformActions) {
|
||||
this._handleCustomActions();
|
||||
}
|
||||
}
|
||||
|
||||
uploadFile(files: any) {
|
||||
uploadFile(files: FileList) {
|
||||
const fileToCompare = files[0];
|
||||
this.compareFileInput.nativeElement.value = null;
|
||||
|
||||
@ -140,7 +158,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
await pdfNet.initialize(environment.licenseKey ? atob(environment.licenseKey) : null);
|
||||
|
||||
const compareDocument = await pdfNet.PDFDoc.createFromBuffer(fileReader.result as ArrayBuffer);
|
||||
const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await this.fileData.arrayBuffer());
|
||||
const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await this._stateService.fileData.blob$.value.arrayBuffer());
|
||||
|
||||
const loadCompareDocument = async () => {
|
||||
this._loadingService.start();
|
||||
@ -153,7 +171,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
this.instance,
|
||||
this.file,
|
||||
() => {
|
||||
this.viewModeService.set('COMPARE');
|
||||
this.viewModeService.compareMode = true;
|
||||
},
|
||||
() => {
|
||||
this.utils.navigateToPage(1);
|
||||
@ -190,10 +208,10 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
}
|
||||
|
||||
async closeCompareMode() {
|
||||
this.viewModeService.set('STANDARD');
|
||||
this.viewModeService.compareMode = false;
|
||||
const pdfNet = this.instance.Core.PDFNet;
|
||||
await pdfNet.initialize(environment.licenseKey ? atob(environment.licenseKey) : null);
|
||||
const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await this.fileData.arrayBuffer());
|
||||
const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await this._stateService.fileData.blob$.value.arrayBuffer());
|
||||
this.instance.UI.loadDocument(currentDocument, {
|
||||
filename: this.file ? this.file.filename : 'document.pdf',
|
||||
});
|
||||
@ -202,7 +220,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
this.utils.navigateToPage(1);
|
||||
}
|
||||
|
||||
async loadViewer() {
|
||||
private async _loadViewer() {
|
||||
this.instance = await WebViewer(
|
||||
{
|
||||
licenseKey: environment.licenseKey ? atob(environment.licenseKey) : null,
|
||||
@ -211,7 +229,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
css: this._convertPath('/assets/pdftron/stylesheet.css'),
|
||||
backendType: 'ems',
|
||||
},
|
||||
this.viewer.nativeElement,
|
||||
this.viewer.nativeElement as HTMLElement,
|
||||
);
|
||||
|
||||
this.documentViewer = this.instance.Core.documentViewer;
|
||||
@ -223,7 +241,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
this.utils.disableHotkeys();
|
||||
this._configureTextPopup();
|
||||
|
||||
this.annotationManager.addEventListener('annotationSelected', (annotations, action) => {
|
||||
this.annotationManager.addEventListener('annotationSelected', (annotations: Annotation[], action) => {
|
||||
this.annotationSelected.emit(this.annotationManager.getSelectedAnnotations().map(ann => ann.Id));
|
||||
if (action === 'deselected') {
|
||||
this._toggleRectangleAnnotationAction(true);
|
||||
@ -233,7 +251,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
}
|
||||
});
|
||||
|
||||
this.annotationManager.addEventListener('annotationChanged', annotations => {
|
||||
this.annotationManager.addEventListener('annotationChanged', (annotations: Annotation[]) => {
|
||||
// when a rectangle is drawn,
|
||||
// it returns one annotation with tool name 'AnnotationCreateRectangle;
|
||||
// this will auto select rectangle after drawing
|
||||
@ -243,7 +261,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
}
|
||||
});
|
||||
|
||||
this.documentViewer.addEventListener('pageNumberUpdated', pageNumber => {
|
||||
this.documentViewer.addEventListener('pageNumberUpdated', (pageNumber: number) => {
|
||||
if (this.shouldDeselectAnnotationsOnPageChange) {
|
||||
this.utils.deselectAllAnnotations();
|
||||
}
|
||||
@ -253,9 +271,9 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
|
||||
this.documentViewer.addEventListener('documentLoaded', this._setReadyAndInitialState);
|
||||
|
||||
this.documentViewer.addEventListener('keyUp', $event => {
|
||||
this.documentViewer.addEventListener('keyUp', ($event: KeyboardEvent) => {
|
||||
// arrows and full-screen
|
||||
if ($event.target?.tagName?.toLowerCase() !== 'input') {
|
||||
if (($event.target as HTMLElement)?.tagName?.toLowerCase() !== 'input') {
|
||||
if ($event.key.startsWith('Arrow') || $event.key === 'f') {
|
||||
this._ngZone.run(() => {
|
||||
this.keyUp.emit($event);
|
||||
@ -265,7 +283,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
}
|
||||
}
|
||||
|
||||
if (ALLOWED_KEYBOARD_SHORTCUTS.indexOf($event.key) < 0) {
|
||||
if (!ALLOWED_KEYBOARD_SHORTCUTS.includes($event.key)) {
|
||||
$event.preventDefault();
|
||||
$event.stopPropagation();
|
||||
}
|
||||
@ -293,8 +311,6 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._loadDocument();
|
||||
}
|
||||
|
||||
private _setInitialDisplayMode() {
|
||||
@ -354,13 +370,14 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
type: 'actionButton',
|
||||
element: 'tooltips',
|
||||
dataElement: dataElements.TOGGLE_TOOLTIPS,
|
||||
img: this._convertPath('/assets/icons/general/pdftron-action-toggle-tooltips.svg'),
|
||||
img: this._toggleTooltipsIcon,
|
||||
title: this._toggleTooltipsBtnTitle,
|
||||
onClick: async () => {
|
||||
await this._userPreferenceService.toggleFilePreviewTooltipsPreference();
|
||||
this._updateTooltipsVisibility();
|
||||
this.instance.UI.updateElement(dataElements.TOGGLE_TOOLTIPS, {
|
||||
title: this._toggleTooltipsBtnTitle,
|
||||
img: this._toggleTooltipsIcon,
|
||||
});
|
||||
},
|
||||
},
|
||||
@ -381,36 +398,34 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
const originalHeaderItems = header.getItems();
|
||||
originalHeaderItems.splice(8, 0, ...headerItems);
|
||||
|
||||
if (this._userPreferenceService.areDevFeaturesEnabled) {
|
||||
const devHeaderItems = [
|
||||
{
|
||||
type: 'actionButton',
|
||||
element: 'compare',
|
||||
dataElement: dataElements.COMPARE_BUTTON,
|
||||
img: this._convertPath('/assets/icons/general/pdftron-action-compare.svg'),
|
||||
title: 'Compare',
|
||||
onClick: () => {
|
||||
this.compareFileInput.nativeElement.click();
|
||||
},
|
||||
const compareHeaderItems = [
|
||||
{
|
||||
type: 'actionButton',
|
||||
element: 'compare',
|
||||
dataElement: dataElements.COMPARE_BUTTON,
|
||||
img: this._convertPath('/assets/icons/general/pdftron-action-compare.svg'),
|
||||
title: 'Compare',
|
||||
onClick: () => {
|
||||
this.compareFileInput.nativeElement.click();
|
||||
},
|
||||
{
|
||||
type: 'actionButton',
|
||||
element: 'closeCompare',
|
||||
dataElement: dataElements.CLOSE_COMPARE_BUTTON,
|
||||
img: this._convertPath('/assets/icons/general/pdftron-action-close-compare.svg'),
|
||||
title: 'Leave Compare Mode',
|
||||
onClick: async () => {
|
||||
await this.closeCompareMode();
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'actionButton',
|
||||
element: 'closeCompare',
|
||||
dataElement: dataElements.CLOSE_COMPARE_BUTTON,
|
||||
img: this._convertPath('/assets/icons/general/pdftron-action-close-compare.svg'),
|
||||
title: 'Leave Compare Mode',
|
||||
onClick: async () => {
|
||||
await this.closeCompareMode();
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
dataElement: dataElements.COMPARE_TOOL_DIVIDER,
|
||||
},
|
||||
];
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
dataElement: dataElements.COMPARE_TOOL_DIVIDER,
|
||||
},
|
||||
];
|
||||
|
||||
originalHeaderItems.splice(9, 0, ...devHeaderItems);
|
||||
}
|
||||
originalHeaderItems.splice(9, 0, ...compareHeaderItems);
|
||||
});
|
||||
|
||||
this.instance.UI.disableElements([dataElements.CLOSE_COMPARE_BUTTON]);
|
||||
@ -561,7 +576,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
}
|
||||
|
||||
private _addManualRedactionOfType(type: ManualRedactionEntryType) {
|
||||
const selectedQuads = this.documentViewer.getSelectedTextQuads();
|
||||
const selectedQuads: Readonly<Record<string, Core.Math.Quad[]>> = this.documentViewer.getSelectedTextQuads();
|
||||
const text = this.documentViewer.getSelectedText();
|
||||
const manualRedaction = this._getManualRedaction(selectedQuads, text, true);
|
||||
this.manualAnnotationRequested.emit(new ManualRedactionEntryWrapper(selectedQuads, manualRedaction, type));
|
||||
@ -617,7 +632,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
for (const quad of quads[key]) {
|
||||
const page = parseInt(key, 10);
|
||||
const pageHeight = this.documentViewer.getPageHeight(page);
|
||||
entry.positions.push(toPosition(page, pageHeight, convertQuads ? this.utils.translateQuads(page, quad) : quad));
|
||||
entry.positions.push(toPosition(page, pageHeight, convertQuads ? this.utils.translateQuad(page, quad) : quad));
|
||||
}
|
||||
}
|
||||
|
||||
@ -627,11 +642,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
}
|
||||
|
||||
private _loadDocument() {
|
||||
if (!this.fileData) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.instance.UI.loadDocument(this.fileData, {
|
||||
this.instance.UI.loadDocument(this._stateService.fileData.blob$.value, {
|
||||
filename: this.file ? this.file.filename : 'document.pdf',
|
||||
});
|
||||
}
|
||||
@ -640,7 +651,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
this._ngZone.run(() => {
|
||||
this.utils.ready = true;
|
||||
this.viewerReady.emit(this.instance);
|
||||
const routePageNumber = this._activatedRoute.snapshot.queryParams.page;
|
||||
const routePageNumber: number = this._activatedRoute.snapshot.queryParams.page;
|
||||
this.pageChanged.emit(routePageNumber || 1);
|
||||
this._setInitialDisplayMode();
|
||||
this._updateTooltipsVisibility();
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<ng-container *ngIf="viewModeService.viewMode$ | async as viewMode">
|
||||
<div
|
||||
<button
|
||||
(click)="switchView.emit('STANDARD')"
|
||||
[class.active]="viewModeService.isStandard"
|
||||
[matTooltip]="'file-preview.standard-tooltip' | translate"
|
||||
@ -7,27 +7,27 @@
|
||||
iqserHelpMode="standard-view"
|
||||
>
|
||||
{{ 'file-preview.standard' | translate }}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div
|
||||
(click)="canSwitchToDeltaView && switchView.emit('DELTA')"
|
||||
<button
|
||||
(click)="switchView.emit('DELTA')"
|
||||
[class.active]="viewModeService.isDelta"
|
||||
[class.disabled]="!canSwitchToDeltaView"
|
||||
[disabled]="(canSwitchToDeltaView$ | async) === false"
|
||||
[matTooltip]="'file-preview.delta-tooltip' | translate"
|
||||
class="red-tab"
|
||||
iqserHelpMode="delta-view"
|
||||
>
|
||||
{{ 'file-preview.delta' | translate }}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div
|
||||
<button
|
||||
(click)="canSwitchToRedactedView && switchView.emit('REDACTED')"
|
||||
[class.active]="viewModeService.isRedacted"
|
||||
[class.disabled]="!canSwitchToRedactedView"
|
||||
[disabled]="!canSwitchToRedactedView"
|
||||
[matTooltip]="'file-preview.redacted-tooltip' | translate"
|
||||
class="red-tab"
|
||||
iqserHelpMode="preview-view"
|
||||
>
|
||||
{{ 'file-preview.redacted' | translate }}
|
||||
</div>
|
||||
</button>
|
||||
</ng-container>
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
||||
import { File, ViewMode } from '@red/domain';
|
||||
import { ViewModeService } from '../../services/view-mode.service';
|
||||
import { FileDataModel } from '@models/file/file-data.model';
|
||||
import { FilePreviewStateService } from '../../services/file-preview-state.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { filter, switchMap } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-view-switch [file] [fileData]',
|
||||
selector: 'redaction-view-switch [file]',
|
||||
templateUrl: './view-switch.component.html',
|
||||
styleUrls: ['./view-switch.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
@ -12,19 +14,18 @@ import { FileDataModel } from '@models/file/file-data.model';
|
||||
export class ViewSwitchComponent implements OnChanges {
|
||||
@Output() readonly switchView = new EventEmitter<ViewMode>();
|
||||
@Input() file: File;
|
||||
@Input() fileData: FileDataModel;
|
||||
|
||||
canSwitchToDeltaView = false;
|
||||
readonly canSwitchToDeltaView$: Observable<boolean>;
|
||||
canSwitchToRedactedView = false;
|
||||
|
||||
constructor(readonly viewModeService: ViewModeService) {}
|
||||
constructor(readonly viewModeService: ViewModeService, private readonly _stateService: FilePreviewStateService) {
|
||||
this.canSwitchToDeltaView$ = this._stateService.fileData$.pipe(
|
||||
filter(fileData => !!fileData),
|
||||
switchMap(fileData => fileData?.hasChangeLog$),
|
||||
);
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes.fileData) {
|
||||
const fileData = changes.fileData.currentValue as FileDataModel;
|
||||
this.canSwitchToDeltaView = fileData?.hasChangeLog;
|
||||
}
|
||||
|
||||
if (changes.file) {
|
||||
const file = changes?.file.currentValue as File;
|
||||
this.canSwitchToRedactedView = !file.analysisRequired && !file.excluded;
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
<ng-container *ngIf="dossier$ | async as dossier">
|
||||
<ng-container *ngIf="file$ | async as file">
|
||||
<ng-container *ngIf="fileData?.file$ | async as file">
|
||||
<section [class.fullscreen]="fullScreen">
|
||||
<div class="page-header">
|
||||
<div class="flex flex-1">
|
||||
<redaction-view-switch (switchView)="switchView($event)" [fileData]="fileData" [file]="file"></redaction-view-switch>
|
||||
<redaction-view-switch (switchView)="switchView($event)" [file]="file"></redaction-view-switch>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 actions-container">
|
||||
@ -76,7 +76,6 @@
|
||||
[canPerformActions]="canPerformAnnotationActions$ | async"
|
||||
[class.hidden]="!ready"
|
||||
[dossier]="dossier"
|
||||
[fileData]="fileData?.fileData"
|
||||
[file]="file"
|
||||
[shouldDeselectAnnotationsOnPageChange]="shouldDeselectAnnotationsOnPageChange"
|
||||
></redaction-pdf-viewer>
|
||||
@ -102,7 +101,6 @@
|
||||
(deselectAnnotations)="deselectAnnotations($event)"
|
||||
(selectAnnotations)="selectAnnotations($event)"
|
||||
(selectPage)="selectPage($event)"
|
||||
(toggleSkipped)="toggleSkipped($event)"
|
||||
*ngIf="!file.excluded"
|
||||
[(shouldDeselectAnnotationsOnPageChange)]="shouldDeselectAnnotationsOnPageChange"
|
||||
[activeViewerPage]="activeViewerPage"
|
||||
@ -110,9 +108,7 @@
|
||||
[annotations]="visibleAnnotations"
|
||||
[dialogRef]="dialogRef"
|
||||
[file]="file"
|
||||
[hideSkipped]="hideSkipped"
|
||||
[selectedAnnotations]="selectedAnnotations"
|
||||
[viewedPages]="fileData?.viewedPages"
|
||||
[viewer]="activeViewer"
|
||||
></redaction-file-workload>
|
||||
</div>
|
||||
|
||||
@ -5,21 +5,21 @@ import { PdfViewerComponent } from './components/pdf-viewer/pdf-viewer.component
|
||||
import {
|
||||
AutoUnsubscribe,
|
||||
CircleButtonTypes,
|
||||
CustomError,
|
||||
Debounce,
|
||||
ErrorService,
|
||||
FilterService,
|
||||
LoadingService,
|
||||
OnAttach,
|
||||
OnDetach,
|
||||
processFilters,
|
||||
shareDistinctLast,
|
||||
shareLast,
|
||||
} from '@iqser/common-ui';
|
||||
import { MatDialogRef, MatDialogState } from '@angular/material/dialog';
|
||||
import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { ManualAnnotationResponse } from '@models/file/manual-annotation-response';
|
||||
import { FileDataModel } from '@models/file/file-data.model';
|
||||
import { AnnotationDrawService } from '../../services/annotation-draw.service';
|
||||
import { AnnotationDrawService } from './services/annotation-draw.service';
|
||||
import { AnnotationProcessingService } from '../../services/annotation-processing.service';
|
||||
import { Dossier, File, ViewMode } from '@red/domain';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
@ -35,7 +35,7 @@ import { handleFilterDelta } from '@utils/filter-utils';
|
||||
import { FilesService } from '@services/entity-services/files.service';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { FileManagementService } from '@services/entity-services/file-management.service';
|
||||
import { filter, map, switchMap, tap } from 'rxjs/operators';
|
||||
import { map, switchMap, tap } from 'rxjs/operators';
|
||||
import { FilesMapService } from '@services/entity-services/files-map.service';
|
||||
import { WatermarkService } from '@shared/services/watermark.service';
|
||||
import { ExcludedPagesService } from './services/excluded-pages.service';
|
||||
@ -43,23 +43,33 @@ import { ViewModeService } from './services/view-mode.service';
|
||||
import { MultiSelectService } from './services/multi-select.service';
|
||||
import { DocumentInfoService } from './services/document-info.service';
|
||||
import { ReanalysisService } from '../../../../services/reanalysis.service';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { CommentingService } from './services/commenting.service';
|
||||
import { SkippedService } from './services/skipped.service';
|
||||
import { AnnotationActionsService } from './services/annotation-actions.service';
|
||||
import { FilePreviewStateService } from './services/file-preview-state.service';
|
||||
import { FileDataModel } from '../../../../models/file/file-data.model';
|
||||
import Annotation = Core.Annotations.Annotation;
|
||||
import PDFNet = Core.PDFNet;
|
||||
|
||||
const ALL_HOTKEY_ARRAY = ['Escape', 'F', 'f', 'ArrowUp', 'ArrowDown'];
|
||||
|
||||
function diff<T>(first: readonly T[], second: readonly T[]): T[] {
|
||||
// symmetrical difference between two arrays
|
||||
const a = new Set(first);
|
||||
const b = new Set(second);
|
||||
|
||||
return [...first.filter(x => !b.has(x)), ...second.filter(x => !a.has(x))];
|
||||
}
|
||||
|
||||
@Component({
|
||||
templateUrl: './file-preview-screen.component.html',
|
||||
styleUrls: ['./file-preview-screen.component.scss'],
|
||||
providers: [FilterService, ExcludedPagesService, ViewModeService, MultiSelectService, DocumentInfoService],
|
||||
providers: [
|
||||
FilterService,
|
||||
ExcludedPagesService,
|
||||
ViewModeService,
|
||||
MultiSelectService,
|
||||
DocumentInfoService,
|
||||
CommentingService,
|
||||
SkippedService,
|
||||
AnnotationDrawService,
|
||||
AnnotationActionsService,
|
||||
FilePreviewStateService,
|
||||
PdfViewerDataService,
|
||||
],
|
||||
})
|
||||
export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnInit, OnDestroy, OnAttach, OnDetach {
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
@ -67,16 +77,13 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
dialogRef: MatDialogRef<unknown>;
|
||||
fullScreen = false;
|
||||
shouldDeselectAnnotationsOnPageChange = true;
|
||||
fileData: FileDataModel;
|
||||
selectedAnnotations: AnnotationWrapper[] = [];
|
||||
hideSkipped = false;
|
||||
displayPdfViewer = false;
|
||||
activeViewerPage: number = null;
|
||||
@ViewChild(PdfViewerComponent) readonly viewerComponent: PdfViewerComponent;
|
||||
readonly dossierId: string;
|
||||
readonly canPerformAnnotationActions$: Observable<boolean>;
|
||||
readonly dossier$: Observable<Dossier>;
|
||||
readonly file$: Observable<File>;
|
||||
readonly fileId: string;
|
||||
ready = false;
|
||||
private _instance: WebViewerInstance;
|
||||
@ -91,6 +98,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
constructor(
|
||||
readonly permissionsService: PermissionsService,
|
||||
readonly userPreferenceService: UserPreferenceService,
|
||||
private readonly _stateService: FilePreviewStateService,
|
||||
private readonly _watermarkService: WatermarkService,
|
||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
||||
private readonly _activatedRoute: ActivatedRoute,
|
||||
@ -98,7 +106,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
private readonly _router: Router,
|
||||
private readonly _annotationProcessingService: AnnotationProcessingService,
|
||||
private readonly _annotationDrawService: AnnotationDrawService,
|
||||
private readonly _fileDownloadService: PdfViewerDataService,
|
||||
private readonly _pdfViewerDataService: PdfViewerDataService,
|
||||
private readonly _filesService: FilesService,
|
||||
private readonly _ngZone: NgZone,
|
||||
private readonly _fileManagementService: FileManagementService,
|
||||
@ -108,6 +116,8 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
private readonly _filesMapService: FilesMapService,
|
||||
private readonly _dossiersService: DossiersService,
|
||||
private readonly _reanalysisService: ReanalysisService,
|
||||
private readonly _errorService: ErrorService,
|
||||
private readonly _skippedService: SkippedService,
|
||||
readonly excludedPagesService: ExcludedPagesService,
|
||||
readonly viewModeService: ViewModeService,
|
||||
readonly multiSelectService: MultiSelectService,
|
||||
@ -117,12 +127,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
this.dossierId = _activatedRoute.snapshot.paramMap.get('dossierId');
|
||||
this.dossier$ = _dossiersService.getEntityChanged$(this.dossierId);
|
||||
this.fileId = _activatedRoute.snapshot.paramMap.get('fileId');
|
||||
this.file$ = _filesMapService.watch$(this.dossierId, this.fileId).pipe(
|
||||
tap(async file => {
|
||||
await this._reloadFile(file);
|
||||
}),
|
||||
shareLast(),
|
||||
);
|
||||
this.canPerformAnnotationActions$ = this._canPerformAnnotationActions$;
|
||||
|
||||
document.documentElement.addEventListener('fullscreenchange', () => {
|
||||
@ -144,8 +148,16 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
return this._instance;
|
||||
}
|
||||
|
||||
get fileData(): FileDataModel {
|
||||
return this._stateService.fileData;
|
||||
}
|
||||
|
||||
private get _canPerformAnnotationActions$() {
|
||||
return combineLatest([this.file$, this.viewModeService.viewMode$]).pipe(
|
||||
return combineLatest([
|
||||
this._stateService.fileData$.pipe(switchMap(fileData => fileData.file$)),
|
||||
this.viewModeService.viewMode$,
|
||||
this.viewModeService.compareMode$,
|
||||
]).pipe(
|
||||
map(([file, viewMode]) => this.permissionsService.canPerformAnnotationActions(file) && viewMode === 'STANDARD'),
|
||||
shareDistinctLast(),
|
||||
);
|
||||
@ -190,7 +202,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
|
||||
ngOnDetach(): void {
|
||||
this.displayPdfViewer = false;
|
||||
super.ngOnDestroy();
|
||||
super.ngOnDetach();
|
||||
this._changeDetectorRef.markForCheck();
|
||||
}
|
||||
|
||||
@ -199,6 +211,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
if (!file.canBeOpened) {
|
||||
return this._router.navigate([this._dossiersService.find(this.dossierId)?.routerLink]);
|
||||
}
|
||||
this.viewModeService.viewMode = 'STANDARD';
|
||||
|
||||
await this.ngOnInit();
|
||||
this._lastPage = previousRoute.queryParams.page;
|
||||
@ -250,11 +263,9 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
});
|
||||
console.log(`[REDACTION] Process time: ${new Date().getTime() - processStartTime} ms`);
|
||||
console.log(`[REDACTION] Filter rebuild time: ${new Date().getTime() - startTime}`);
|
||||
console.log();
|
||||
}
|
||||
|
||||
handleAnnotationSelected(annotationIds: string[]) {
|
||||
// TODO: use includes() here
|
||||
this.selectedAnnotations = annotationIds
|
||||
.map(id => this.visibleAnnotations.find(annotationWrapper => annotationWrapper.id === id))
|
||||
.filter(ann => ann !== undefined);
|
||||
@ -268,7 +279,8 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
@Debounce(10)
|
||||
selectAnnotations(annotations?: AnnotationWrapper[]) {
|
||||
if (annotations) {
|
||||
this.viewerComponent?.utils?.selectAnnotations(annotations, this.multiSelectService.isActive);
|
||||
const annotationsToSelect = this.multiSelectService.isActive ? [...this.selectedAnnotations, ...annotations] : annotations;
|
||||
this.viewerComponent?.utils?.selectAnnotations(annotationsToSelect, this.multiSelectService.isActive);
|
||||
} else {
|
||||
this.viewerComponent?.utils?.deselectAllAnnotations();
|
||||
}
|
||||
@ -387,7 +399,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
});
|
||||
|
||||
// Go to initial page from query params
|
||||
const pageNumber = this._lastPage || this._activatedRoute.snapshot.queryParams.page;
|
||||
const pageNumber: string = this._lastPage || this._activatedRoute.snapshot.queryParams.page;
|
||||
if (pageNumber) {
|
||||
setTimeout(() => {
|
||||
this.selectPage(parseInt(pageNumber, 10));
|
||||
@ -403,6 +415,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
}
|
||||
|
||||
async annotationsChangedByReviewAction(annotation: AnnotationWrapper) {
|
||||
this.multiSelectService.deactivate();
|
||||
await this._reloadAnnotationsForPage(annotation?.pageNumber || this.activeViewerPage);
|
||||
}
|
||||
|
||||
@ -413,7 +426,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
}
|
||||
|
||||
async switchView(viewMode: ViewMode) {
|
||||
this.viewModeService.set(viewMode);
|
||||
this.viewModeService.viewMode = viewMode;
|
||||
await this.updateViewMode();
|
||||
this._scrollViews();
|
||||
}
|
||||
@ -425,16 +438,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
download(data, file.filename);
|
||||
}
|
||||
|
||||
toggleSkipped($event) {
|
||||
$event.stopPropagation();
|
||||
$event.preventDefault();
|
||||
this.hideSkipped = !this.hideSkipped;
|
||||
|
||||
this._handleIgnoreAnnotationsDrawing();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private _setActiveViewerPage() {
|
||||
const currentPage = this._instance?.Core.documentViewer?.getCurrentPage();
|
||||
if (!currentPage) {
|
||||
@ -464,17 +467,20 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
}
|
||||
|
||||
private async _reloadFile(file: File): Promise<void> {
|
||||
const previousFile = this.fileData?.file;
|
||||
await this._loadFileData(file);
|
||||
await this._stampPDF();
|
||||
|
||||
// file already loaded at least once
|
||||
if (previousFile) {
|
||||
// If it has been OCRd, we need to wait for it to load into the viewer
|
||||
if (previousFile.lastOCRTime !== this.fileData?.file?.lastOCRTime) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _stampPDF() {
|
||||
if (!this._instance) {
|
||||
return;
|
||||
}
|
||||
|
||||
const document = this._instance.Core.documentViewer.getDocument();
|
||||
if (!document) {
|
||||
if (!this._instance?.Core.documentViewer.getDocument()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -517,7 +523,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
await stampPDFPage(
|
||||
document,
|
||||
this._instance.Core.PDFNet,
|
||||
this._translateService.instant('file-preview.excluded-from-redaction'),
|
||||
this._translateService.instant('file-preview.excluded-from-redaction') as string,
|
||||
17,
|
||||
'courier',
|
||||
'TOP_LEFT',
|
||||
@ -528,19 +534,54 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
}
|
||||
}
|
||||
|
||||
private async _fileUpdated(file: File): Promise<File> {
|
||||
if (!this.fileData || file.lastProcessed === this.fileData.file.lastProcessed) {
|
||||
await this._reloadFile(file);
|
||||
} else {
|
||||
// File reanalysed
|
||||
const previousAnnotations = this.visibleAnnotations;
|
||||
await this._loadFileData(file);
|
||||
await this._reloadAnnotations(previousAnnotations);
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
private _subscribeToFileUpdates(): void {
|
||||
this.addSubscription = timer(0, 5000)
|
||||
this.addActiveScreenSubscription = this._filesMapService
|
||||
.watch$(this.dossierId, this.fileId)
|
||||
.pipe(switchMap(file => this._fileUpdated(file)))
|
||||
.subscribe();
|
||||
|
||||
this.addActiveScreenSubscription = timer(0, 5000)
|
||||
.pipe(switchMap(() => this._filesService.reload(this.dossierId, this.fileId)))
|
||||
.subscribe();
|
||||
this.addSubscription = this._filesMapService.fileReanalysed$
|
||||
.pipe(filter(file => file.fileId === this.fileId))
|
||||
.subscribe(async file => {
|
||||
if (file.lastProcessed !== this.fileData?.file.lastProcessed) {
|
||||
await this._loadFileData(file);
|
||||
await this._reloadAnnotations();
|
||||
}
|
||||
this._loadingService.stop();
|
||||
});
|
||||
|
||||
this.addActiveScreenSubscription = this._dossiersService
|
||||
.getEntityDeleted$(this.dossierId)
|
||||
.pipe(tap(() => this._handleDeletedDossier()))
|
||||
.subscribe();
|
||||
|
||||
this.addActiveScreenSubscription = this._filesMapService
|
||||
.watchDeleted$(this.fileId)
|
||||
.pipe(tap(() => this._handleDeletedFile()))
|
||||
.subscribe();
|
||||
|
||||
this.addActiveScreenSubscription = this._skippedService.hideSkipped$
|
||||
.pipe(tap(hideSkipped => this._handleIgnoreAnnotationsDrawing(hideSkipped)))
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
private _handleDeletedDossier(): void {
|
||||
this._errorService.set(
|
||||
new CustomError(_('error.deleted-entity.file-dossier.label'), _('error.deleted-entity.file-dossier.action'), 'iqser:expand'),
|
||||
);
|
||||
}
|
||||
|
||||
private _handleDeletedFile(): void {
|
||||
this._errorService.set(
|
||||
new CustomError(_('error.deleted-entity.file.label'), _('error.deleted-entity.file.action'), 'iqser:expand'),
|
||||
);
|
||||
}
|
||||
|
||||
private async _loadFileData(file: File): Promise<void | boolean> {
|
||||
@ -548,13 +589,13 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
return this._router.navigate([this._dossiersService.find(this.dossierId).routerLink]);
|
||||
}
|
||||
|
||||
const fileData = await this._fileDownloadService.loadDataFor(file, this.fileData).toPromise();
|
||||
const fileData = await this._pdfViewerDataService.loadDataFor(file).toPromise();
|
||||
|
||||
if (file.isPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.fileData = fileData;
|
||||
this._stateService.fileData = fileData;
|
||||
}
|
||||
|
||||
@Debounce(0)
|
||||
@ -563,13 +604,9 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
this._workloadComponent?.scrollAnnotations();
|
||||
}
|
||||
|
||||
private async _reloadAnnotations() {
|
||||
this.fileData.redactionLog = await this._fileDownloadService.loadRedactionLogFor(this.dossierId, this.fileId).toPromise();
|
||||
this._instance.Core.annotationManager.deleteAnnotations(this._instance.Core.annotationManager.getAnnotationsList(), {
|
||||
imported: true,
|
||||
force: true,
|
||||
});
|
||||
await this._cleanupAndRedrawAnnotations();
|
||||
private async _reloadAnnotations(previousAnnotations?: AnnotationWrapper[]) {
|
||||
this._deleteAnnotations();
|
||||
await this._cleanupAndRedrawAnnotations(previousAnnotations);
|
||||
}
|
||||
|
||||
private async _reloadAnnotationsForPage(page: number) {
|
||||
@ -582,24 +619,42 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
}
|
||||
|
||||
const currentPageAnnotations = this.visibleAnnotations.filter(a => a.pageNumber === page);
|
||||
this.fileData.redactionLog = await this._fileDownloadService.loadRedactionLogFor(this.dossierId, this.fileId).toPromise();
|
||||
this.fileData.redactionLog = await this._pdfViewerDataService.loadRedactionLogFor(this.dossierId, this.fileId).toPromise();
|
||||
|
||||
this._deleteAnnotations(currentPageAnnotations);
|
||||
await this._cleanupAndRedrawAnnotations(currentPageAnnotations, annotation => annotation.pageNumber === page);
|
||||
}
|
||||
|
||||
private _deleteAnnotations(annotationsToDelete?: AnnotationWrapper[]) {
|
||||
if (!this._instance?.Core.documentViewer.getDocument()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!annotationsToDelete) {
|
||||
this._instance.Core.annotationManager.deleteAnnotations(this._instance.Core.annotationManager.getAnnotationsList(), {
|
||||
imported: true,
|
||||
force: true,
|
||||
});
|
||||
}
|
||||
annotationsToDelete?.forEach(annotation => {
|
||||
this._findAndDeleteAnnotation(annotation.id);
|
||||
});
|
||||
}
|
||||
|
||||
private async _cleanupAndRedrawAnnotations(
|
||||
annotationsToDelete?: AnnotationWrapper[],
|
||||
currentAnnotations?: AnnotationWrapper[],
|
||||
newAnnotationsFilter?: (annotation: AnnotationWrapper) => boolean,
|
||||
) {
|
||||
if (!this._instance?.Core.documentViewer.getDocument()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.rebuildFilters();
|
||||
|
||||
if (this.viewModeService.viewMode === 'STANDARD') {
|
||||
const startTime = new Date().getTime();
|
||||
annotationsToDelete?.forEach(annotation => {
|
||||
this._findAndDeleteAnnotation(annotation.id);
|
||||
});
|
||||
const newAnnotations = newAnnotationsFilter ? this.allAnnotations.filter(newAnnotationsFilter) : this.allAnnotations;
|
||||
this._handleDeltaAnnotationFilters(annotationsToDelete ?? [], newAnnotations);
|
||||
const newAnnotations = newAnnotationsFilter ? this.visibleAnnotations.filter(newAnnotationsFilter) : this.visibleAnnotations;
|
||||
this._handleDeltaAnnotationFilters(currentAnnotations ?? [], newAnnotations);
|
||||
await this._redrawAnnotations(newAnnotations);
|
||||
console.log(
|
||||
`[REDACTION] Annotations redraw time: ${new Date().getTime() - startTime} ms for ${newAnnotations.length} annotations`,
|
||||
@ -613,7 +668,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
annotations,
|
||||
this.fileId,
|
||||
this.dossierId,
|
||||
this.hideSkipped,
|
||||
!!this.viewModeService.isCompare,
|
||||
);
|
||||
}
|
||||
@ -630,6 +684,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
|
||||
const oldPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter(currentPageAnnotations);
|
||||
const newPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter(newPageAnnotations);
|
||||
|
||||
handleFilterDelta(oldPageSpecificFilters, newPageSpecificFilters, primaryFilters);
|
||||
this._filterService.addFilterGroup({
|
||||
...primaryFilterGroup,
|
||||
@ -655,9 +710,13 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
}
|
||||
}
|
||||
|
||||
private _handleIgnoreAnnotationsDrawing() {
|
||||
private _handleIgnoreAnnotationsDrawing(hideSkipped: boolean): void {
|
||||
const ignored = this._getAnnotations(a => a.getCustomData('skipped'));
|
||||
return this.hideSkipped ? this._hide(ignored) : this._show(ignored);
|
||||
if (hideSkipped) {
|
||||
this._hide(ignored);
|
||||
} else {
|
||||
this._show(ignored);
|
||||
}
|
||||
}
|
||||
|
||||
private _getAnnotations(predicate: (value) => unknown) {
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
import { EventEmitter, Inject, Injectable, NgZone } from '@angular/core';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { ManualAnnotationService } from './manual-annotation.service';
|
||||
import { ManualAnnotationService } from '../../../services/manual-annotation.service';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { Observable } from 'rxjs';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { getFirstRelevantTextPart } from '@utils/functions';
|
||||
import { AnnotationPermissions } from '@models/file/annotation.permissions';
|
||||
import { DossiersDialogService } from './dossiers-dialog.service';
|
||||
import { BASE_HREF } from '../../../tokens';
|
||||
import { DossiersDialogService } from '../../../services/dossiers-dialog.service';
|
||||
import { BASE_HREF } from '../../../../../tokens';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { Core, WebViewerInstance } from '@pdftron/webviewer';
|
||||
import { Dossier, File, IAddRedactionRequest, ILegalBasisChangeRequest, IRectangle, IResizeRequest } from '@red/domain';
|
||||
import { toPosition } from '../utils/pdf-calculation.utils';
|
||||
import { toPosition } from '../../../utils/pdf-calculation.utils';
|
||||
import { AnnotationDrawService } from './annotation-draw.service';
|
||||
import { translateQuads } from '../../../utils';
|
||||
import { translateQuads } from '@utils/pdf-coordinates';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import Annotation = Core.Annotations.Annotation;
|
||||
|
||||
@ -5,10 +5,11 @@ import { AppStateService } from '@state/app-state.service';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { RedactionLogService } from './redaction-log.service';
|
||||
import { RedactionLogService } from '../../../services/redaction-log.service';
|
||||
import { environment } from '@environments/environment';
|
||||
|
||||
import { IRectangle, ISectionGrid, ISectionRectangle } from '@red/domain';
|
||||
import { SkippedService } from './skipped.service';
|
||||
import Annotation = Core.Annotations.Annotation;
|
||||
|
||||
@Injectable()
|
||||
@ -18,6 +19,7 @@ export class AnnotationDrawService {
|
||||
private readonly _dossiersService: DossiersService,
|
||||
private readonly _redactionLogService: RedactionLogService,
|
||||
private readonly _userPreferenceService: UserPreferenceService,
|
||||
private readonly _skippedService: SkippedService,
|
||||
) {}
|
||||
|
||||
async drawAnnotations(
|
||||
@ -25,7 +27,6 @@ export class AnnotationDrawService {
|
||||
annotationWrappers: AnnotationWrapper[],
|
||||
fileId: string,
|
||||
dossierId: string,
|
||||
hideSkipped = false,
|
||||
compareMode = false,
|
||||
) {
|
||||
if (!activeViewer) {
|
||||
@ -36,7 +37,7 @@ export class AnnotationDrawService {
|
||||
|
||||
await pdfNet.runWithCleanup(
|
||||
async () => {
|
||||
await this._drawAnnotations(activeViewer, annotationWrappers, fileId, dossierId, hideSkipped, compareMode);
|
||||
await this._drawAnnotations(activeViewer, annotationWrappers, fileId, dossierId, compareMode);
|
||||
},
|
||||
environment.licenseKey ? atob(environment.licenseKey) : null,
|
||||
);
|
||||
@ -82,12 +83,9 @@ export class AnnotationDrawService {
|
||||
annotationWrappers: AnnotationWrapper[],
|
||||
fileId: string,
|
||||
dossierId: string,
|
||||
hideSkipped: boolean,
|
||||
compareMode: boolean,
|
||||
) {
|
||||
const annotations = annotationWrappers.map(annotation =>
|
||||
this._computeAnnotation(activeViewer, annotation, dossierId, hideSkipped, compareMode),
|
||||
);
|
||||
const annotations = annotationWrappers.map(annotation => this._computeAnnotation(activeViewer, annotation, dossierId, compareMode));
|
||||
const annotationManager = activeViewer.Core.annotationManager;
|
||||
annotationManager.addAnnotations(annotations, { imported: true });
|
||||
await annotationManager.drawAnnotationsFromList(annotations);
|
||||
@ -143,7 +141,6 @@ export class AnnotationDrawService {
|
||||
activeViewer: WebViewerInstance,
|
||||
annotationWrapper: AnnotationWrapper,
|
||||
dossierId: string,
|
||||
hideSkipped: boolean,
|
||||
compareMode: boolean,
|
||||
) {
|
||||
const pageNumber = compareMode ? annotationWrapper.pageNumber * 2 - 1 : annotationWrapper.pageNumber;
|
||||
@ -177,7 +174,7 @@ export class AnnotationDrawService {
|
||||
|
||||
annotation.Hidden =
|
||||
annotationWrapper.isChangeLogRemoved ||
|
||||
(hideSkipped && annotationWrapper.isSkipped) ||
|
||||
(this._skippedService.hideSkipped && annotationWrapper.isSkipped) ||
|
||||
annotationWrapper.isOCR ||
|
||||
annotationWrapper.hidden;
|
||||
annotation.setCustomData('redact-manager', 'true');
|
||||
@ -0,0 +1,38 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { map, startWith } from 'rxjs/operators';
|
||||
import { shareDistinctLast } from '@iqser/common-ui';
|
||||
|
||||
@Injectable()
|
||||
export class CommentingService {
|
||||
private _activeAnnotations = new BehaviorSubject<Set<string>>(new Set<string>());
|
||||
|
||||
isActive$(annotationId: string): Observable<boolean> {
|
||||
return this._activeAnnotations.pipe(
|
||||
map(annotations => annotations.has(annotationId)),
|
||||
startWith(false),
|
||||
shareDistinctLast(),
|
||||
);
|
||||
}
|
||||
|
||||
toggle(annotationId: string): void {
|
||||
if (this._activeAnnotations.value.has(annotationId)) {
|
||||
this._deactivate(annotationId);
|
||||
} else {
|
||||
this._activate(annotationId);
|
||||
}
|
||||
}
|
||||
|
||||
private _activate(annotationId: string): void {
|
||||
const currentValue = this._activeAnnotations.value;
|
||||
const newSet = new Set<string>(currentValue).add(annotationId);
|
||||
this._activeAnnotations.next(newSet);
|
||||
}
|
||||
|
||||
private _deactivate(annotationId: string): void {
|
||||
const currentValue = this._activeAnnotations.value;
|
||||
const newSet = new Set<string>(currentValue);
|
||||
newSet.delete(annotationId);
|
||||
this._activeAnnotations.next(newSet);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { FileDataModel } from '@models/file/file-data.model';
|
||||
|
||||
@Injectable()
|
||||
export class FilePreviewStateService {
|
||||
readonly fileData$: Observable<FileDataModel>;
|
||||
private readonly _fileData$ = new BehaviorSubject<FileDataModel>(undefined);
|
||||
|
||||
constructor() {
|
||||
this.fileData$ = this._fileData$.asObservable();
|
||||
}
|
||||
|
||||
get fileData(): FileDataModel {
|
||||
return this._fileData$.value;
|
||||
}
|
||||
|
||||
set fileData(fileDataModel: FileDataModel) {
|
||||
this._fileData$.next(fileDataModel);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { skip } from 'rxjs/operators';
|
||||
import { shareDistinctLast } from '@iqser/common-ui';
|
||||
|
||||
@Injectable()
|
||||
export class SkippedService {
|
||||
readonly hideSkipped$: Observable<boolean>;
|
||||
private readonly _hideSkipped$ = new BehaviorSubject(false);
|
||||
|
||||
constructor() {
|
||||
this.hideSkipped$ = this._hideSkipped$.pipe(shareDistinctLast(), skip(1));
|
||||
}
|
||||
|
||||
get hideSkipped(): boolean {
|
||||
return this._hideSkipped$.value;
|
||||
}
|
||||
|
||||
toggleSkipped($event): void {
|
||||
$event.stopPropagation();
|
||||
$event.preventDefault();
|
||||
this._hideSkipped$.next(!this.hideSkipped);
|
||||
}
|
||||
}
|
||||
@ -5,10 +5,14 @@ import { ViewMode } from '@red/domain';
|
||||
@Injectable()
|
||||
export class ViewModeService {
|
||||
readonly viewMode$: Observable<ViewMode>;
|
||||
readonly compareMode$: Observable<Boolean>;
|
||||
private readonly _viewMode$ = new BehaviorSubject<ViewMode>('STANDARD');
|
||||
|
||||
private readonly _compareMode$ = new BehaviorSubject<Boolean>(false);
|
||||
|
||||
constructor() {
|
||||
this.viewMode$ = this._viewMode$.asObservable();
|
||||
this.compareMode$ = this._compareMode$.asObservable();
|
||||
}
|
||||
|
||||
get viewMode() {
|
||||
@ -23,15 +27,19 @@ export class ViewModeService {
|
||||
return this._viewMode$.value === 'DELTA';
|
||||
}
|
||||
|
||||
get isCompare() {
|
||||
return this._viewMode$.value === 'COMPARE';
|
||||
}
|
||||
|
||||
get isRedacted() {
|
||||
return this._viewMode$.value === 'REDACTED';
|
||||
}
|
||||
|
||||
set(mode: ViewMode) {
|
||||
set viewMode(mode: ViewMode) {
|
||||
this._viewMode$.next(mode);
|
||||
}
|
||||
|
||||
get isCompare() {
|
||||
return this._compareMode$.value;
|
||||
}
|
||||
|
||||
set compareMode(compareMode: boolean) {
|
||||
this._compareMode$.next(compareMode);
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,6 +30,7 @@ export class DossiersDialogService extends DialogService<DialogType> {
|
||||
protected readonly _config: DialogConfig<DialogType> = {
|
||||
confirm: {
|
||||
component: ConfirmationDialogComponent,
|
||||
dialogConfig: { disableClose: false },
|
||||
},
|
||||
documentInfo: {
|
||||
component: DocumentInfoDialogComponent,
|
||||
@ -45,6 +46,7 @@ export class DossiersDialogService extends DialogService<DialogType> {
|
||||
},
|
||||
assignFile: {
|
||||
component: AssignReviewerApproverDialogComponent,
|
||||
dialogConfig: { disableClose: false },
|
||||
},
|
||||
recategorizeImage: {
|
||||
component: RecategorizeImageDialogComponent,
|
||||
|
||||
@ -3,13 +3,14 @@ import { forkJoin, Observable, of } from 'rxjs';
|
||||
import { catchError, map, tap } from 'rxjs/operators';
|
||||
import { FileDataModel } from '@models/file/file-data.model';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { File } from '@red/domain';
|
||||
import { File, IRedactionLog, IViewedPage } from '@red/domain';
|
||||
import { FileManagementService } from '@services/entity-services/file-management.service';
|
||||
import { RedactionLogService } from './redaction-log.service';
|
||||
import { ViewedPagesService } from '@services/entity-services/viewed-pages.service';
|
||||
import { AppStateService } from '../../../state/app-state.service';
|
||||
import { DossiersService } from '../../../services/entity-services/dossiers.service';
|
||||
import { UserPreferenceService } from '../../../services/user-preference.service';
|
||||
import { FilePreviewStateService } from '../screens/file-preview-screen/services/file-preview-state.service';
|
||||
|
||||
@Injectable()
|
||||
export class PdfViewerDataService {
|
||||
@ -21,6 +22,7 @@ export class PdfViewerDataService {
|
||||
private readonly _viewedPagesService: ViewedPagesService,
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _userPreferenceService: UserPreferenceService,
|
||||
private readonly _stateService: FilePreviewStateService,
|
||||
) {}
|
||||
|
||||
loadRedactionLogFor(dossierId: string, fileId: string) {
|
||||
@ -30,16 +32,17 @@ export class PdfViewerDataService {
|
||||
);
|
||||
}
|
||||
|
||||
loadDataFor(file: File, fileData?: FileDataModel): Observable<FileDataModel> {
|
||||
const file$ = fileData?.file.cacheIdentifier === file.cacheIdentifier ? of(fileData.fileData) : this.downloadOriginalFile(file);
|
||||
const reactionLog$ = this.loadRedactionLogFor(file.dossierId, file.fileId);
|
||||
loadDataFor(file: File): Observable<FileDataModel> {
|
||||
const fileData = this._stateService.fileData;
|
||||
const blob$ = fileData?.file.cacheIdentifier === file.cacheIdentifier ? of(fileData.blob$.value) : this.downloadOriginalFile(file);
|
||||
const redactionLog$ = this.loadRedactionLogFor(file.dossierId, file.fileId);
|
||||
const viewedPages$ = this.getViewedPagesFor(file);
|
||||
|
||||
const dossier = this._dossiersService.find(file.dossierId);
|
||||
|
||||
return forkJoin([file$, reactionLog$, viewedPages$]).pipe(
|
||||
return forkJoin([blob$, redactionLog$, viewedPages$]).pipe(
|
||||
map(
|
||||
data =>
|
||||
(data: [blob: Blob, redactionLog: IRedactionLog, viewedPages: IViewedPage[]]) =>
|
||||
new FileDataModel(
|
||||
file,
|
||||
...data,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
EventEmitter,
|
||||
HostBinding,
|
||||
@ -96,6 +97,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
|
||||
private readonly _userPreferenceService: UserPreferenceService,
|
||||
private readonly _reanalysisService: ReanalysisService,
|
||||
private readonly _router: Router,
|
||||
private readonly _changeRef: ChangeDetectorRef,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@ -157,7 +159,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
|
||||
ariaExpanded: this._excludedPagesService?.shown$,
|
||||
showDot: !!this.file.excludedPages?.length,
|
||||
icon: 'red:exclude-pages',
|
||||
show: !!this._excludedPagesService,
|
||||
show: !!this._excludedPagesService && !this.file.excluded,
|
||||
},
|
||||
{
|
||||
type: ActionTypes.circleBtn,
|
||||
@ -224,7 +226,10 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this._dossiersService.getEntityChanged$(this.file.dossierId).pipe(tap(() => this._setup()));
|
||||
this.addSubscription = this._dossiersService
|
||||
.getEntityChanged$(this.file.dossierId)
|
||||
.pipe(tap(() => this._setup()))
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
@ -353,6 +358,8 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
|
||||
this.showReanalyseDossierOverview = this.canReanalyse && this.isDossierOverview && this.analysisForced;
|
||||
|
||||
this.buttons = this._buttons;
|
||||
|
||||
this._changeRef.markForCheck();
|
||||
}
|
||||
|
||||
private async _setFileApproved() {
|
||||
|
||||
@ -19,5 +19,6 @@ export const processingFileStatusTranslations: { [key in ProcessingFileStatus]:
|
||||
OCR_PROCESSING: _('file-status.ocr-processing'),
|
||||
PROCESSING: _('file-status.processing'),
|
||||
REPROCESS: _('file-status.reprocess'),
|
||||
SURROUNDING_TEXT_PROCESSING: _('file-status.processing'),
|
||||
UNPROCESSED: _('file-status.unprocessed'),
|
||||
};
|
||||
|
||||
@ -111,9 +111,9 @@ export class PdfViewerUtils {
|
||||
}
|
||||
}
|
||||
|
||||
translateQuads(page: number, quads: any) {
|
||||
translateQuad(page: number, quad: Core.Math.Quad) {
|
||||
const rotation = this._documentViewer.getCompleteRotation(page);
|
||||
return translateQuads(page, rotation, quads);
|
||||
return translateQuads(page, rotation, quad);
|
||||
}
|
||||
|
||||
deselectAllAnnotations() {
|
||||
|
||||
@ -7,9 +7,9 @@ import {
|
||||
IPrepareDownloadRequest,
|
||||
IRemoveDownloadRequest,
|
||||
} from '@red/domain';
|
||||
import { interval, Observable } from 'rxjs';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { filter, map, switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
||||
import { map, switchMap, tap } from 'rxjs/operators';
|
||||
import { KeycloakService } from 'keycloak-angular';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { EntitiesService, List, mapEach, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
@ -44,11 +44,12 @@ export class FileDownloadService extends EntitiesService<DownloadStatus, IDownlo
|
||||
}
|
||||
|
||||
async performDownload(status: DownloadStatus) {
|
||||
const token = await this._keycloakService.getToken();
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = `${this._configService.values.API_URL}/async/download?access_token=${encodeURIComponent(
|
||||
token,
|
||||
)}&storageId=${encodeURIComponent(status.storageId)}`;
|
||||
anchor.href = `${this._configService.values.API_URL}/async/download?storageId=${encodeURIComponent(status.storageId)}`;
|
||||
if (!this._configService.values.USE_SESSION_FOR_DOWNLOAD) {
|
||||
const token = await this._keycloakService.getToken();
|
||||
anchor.href = anchor.href + `&access_token=${encodeURIComponent(token)}`;
|
||||
}
|
||||
anchor.download = status.filename;
|
||||
anchor.target = '_blank';
|
||||
|
||||
|
||||
@ -41,7 +41,7 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
|
||||
) {
|
||||
super(_injector, 'upload');
|
||||
const fileFetch$ = this._fetchFiles$.pipe(
|
||||
throttleTime(1500),
|
||||
throttleTime(250),
|
||||
switchMap(dossierId => this._filesService.loadAll(dossierId)),
|
||||
);
|
||||
this._subscriptions.add(fileFetch$.subscribe());
|
||||
|
||||
@ -2,16 +2,27 @@ import { Injectable, Injector } from '@angular/core';
|
||||
import { EntitiesService, List, mapEach, QueryParam, RequiredParam, shareLast, Toaster, Validate } from '@iqser/common-ui';
|
||||
import { Dossier, IDossier, IDossierRequest } from '@red/domain';
|
||||
import { catchError, filter, map, mapTo, switchMap, tap } from 'rxjs/operators';
|
||||
import { combineLatest, iif, Observable, of, throwError } from 'rxjs';
|
||||
import { combineLatest, Observable, of, Subject, throwError, timer } from 'rxjs';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
|
||||
import { DossierStatsService } from '@services/entity-services/dossier-stats.service';
|
||||
import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
|
||||
|
||||
export interface IDossiersStats {
|
||||
totalPeople: number;
|
||||
totalAnalyzedPages: number;
|
||||
}
|
||||
|
||||
interface ChangesDetails {
|
||||
dossierChanges: [
|
||||
{
|
||||
dossierChanges: boolean;
|
||||
dossierId: string;
|
||||
fileChanges: boolean;
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const DOSSIER_EXISTS_MSG = _('add-dossier-dialog.errors.dossier-already-exists');
|
||||
const GENERIC_MGS = _('add-dossier-dialog.errors.generic');
|
||||
|
||||
@ -20,6 +31,7 @@ const GENERIC_MGS = _('add-dossier-dialog.errors.generic');
|
||||
})
|
||||
export class DossiersService extends EntitiesService<Dossier, IDossier> {
|
||||
readonly generalStats$ = this.all$.pipe(switchMap(entities => this._generalStats$(entities)));
|
||||
readonly dossierFileChanges$ = new Subject<string>();
|
||||
|
||||
constructor(
|
||||
private readonly _toaster: Toaster,
|
||||
@ -27,6 +39,13 @@ export class DossiersService extends EntitiesService<Dossier, IDossier> {
|
||||
private readonly _dossierStatsService: DossierStatsService,
|
||||
) {
|
||||
super(_injector, Dossier, 'dossier');
|
||||
|
||||
timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL)
|
||||
.pipe(
|
||||
switchMap(() => this.loadAllIfChanged()),
|
||||
tap(changes => this._emitFileChanges(changes)),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
loadAll(): Observable<Dossier[]> {
|
||||
@ -39,8 +58,15 @@ export class DossiersService extends EntitiesService<Dossier, IDossier> {
|
||||
);
|
||||
}
|
||||
|
||||
loadAllIfChanged(): Observable<boolean> {
|
||||
return this.hasChanges$().pipe(switchMap(changed => iif(() => changed, this.loadAll()).pipe(mapTo(changed))));
|
||||
loadAllIfChanged(): Observable<ChangesDetails> {
|
||||
return this.hasChangesDetails$().pipe(switchMap(changes => this.loadAll().pipe(mapTo(changes))));
|
||||
}
|
||||
|
||||
hasChangesDetails$(): Observable<ChangesDetails> {
|
||||
const body = { value: this._lastCheckedForChanges.get('root') ?? '0' };
|
||||
return this._post<ChangesDetails>(body, `${this._defaultModelPath}/changes/details`).pipe(
|
||||
filter(changes => changes.dossierChanges.length > 0),
|
||||
);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
@ -82,6 +108,10 @@ export class DossiersService extends EntitiesService<Dossier, IDossier> {
|
||||
return super.delete(body, 'deleted-dossiers/hard-delete', body).toPromise();
|
||||
}
|
||||
|
||||
private _emitFileChanges(changes: ChangesDetails): void {
|
||||
changes.dossierChanges.filter(change => change.fileChanges).forEach(change => this.dossierFileChanges$.next(change.dossierId));
|
||||
}
|
||||
|
||||
private _computeStats(entities: List<Dossier>): IDossiersStats {
|
||||
let totalAnalyzedPages = 0;
|
||||
const totalPeople = new Set<string>();
|
||||
|
||||
@ -5,8 +5,8 @@ import { filter, startWith } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class FilesMapService {
|
||||
readonly fileReanalysed$ = new Subject<File>();
|
||||
private readonly _entityChanged$ = new Subject<File>();
|
||||
private readonly _entityDeleted$ = new Subject<File>();
|
||||
private readonly _map = new Map<string, BehaviorSubject<File[]>>();
|
||||
|
||||
get$(dossierId: string) {
|
||||
@ -37,17 +37,13 @@ export class FilesMapService {
|
||||
return entities.forEach(entity => this._entityChanged$.next(entity));
|
||||
}
|
||||
|
||||
const reanalysedEntities = [];
|
||||
const changedEntities = [];
|
||||
const deletedEntities = this.get(key).filter(oldEntity => !entities.find(newEntity => newEntity.id === oldEntity.id));
|
||||
|
||||
// Keep old object references for unchanged entities
|
||||
const newEntities = entities.map(newEntity => {
|
||||
const oldEntity = this.get(key, newEntity.id);
|
||||
|
||||
if (oldEntity?.lastProcessed !== newEntity.lastProcessed) {
|
||||
reanalysedEntities.push(newEntity);
|
||||
}
|
||||
|
||||
if (newEntity.isEqual(oldEntity)) {
|
||||
return oldEntity;
|
||||
}
|
||||
@ -60,13 +56,13 @@ export class FilesMapService {
|
||||
|
||||
// Emit observables only after entities have been updated
|
||||
|
||||
for (const file of reanalysedEntities) {
|
||||
this.fileReanalysed$.next(file);
|
||||
}
|
||||
|
||||
for (const file of changedEntities) {
|
||||
this._entityChanged$.next(file);
|
||||
}
|
||||
|
||||
for (const file of deletedEntities) {
|
||||
this._entityDeleted$.next(file);
|
||||
}
|
||||
}
|
||||
|
||||
replace(entity: File) {
|
||||
@ -83,4 +79,8 @@ export class FilesMapService {
|
||||
startWith(this.get(key, entityId)),
|
||||
);
|
||||
}
|
||||
|
||||
watchDeleted$(entityId: string): Observable<File> {
|
||||
return this._entityDeleted$.pipe(filter(entity => entity.id === entityId));
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import { Injectable, Injector } from '@angular/core';
|
||||
import { GenericService, List, mapEach, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import * as moment from 'moment';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { iif, Observable } from 'rxjs';
|
||||
import { EMPTY, iif, Observable } from 'rxjs';
|
||||
import { INotification, Notification, NotificationTypes } from '@red/domain';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
import { notificationsTranslations } from '../translations/notifications-translations';
|
||||
@ -40,7 +40,7 @@ export class NotificationsService extends GenericService<Notification> {
|
||||
|
||||
@Validate()
|
||||
getNotificationsIfChanged(@RequiredParam() includeSeen: boolean): Observable<Notification[]> {
|
||||
return this.hasChanges$().pipe(switchMap(changed => iif(() => changed, this.getNotifications(includeSeen))));
|
||||
return this.hasChanges$().pipe(switchMap(changed => iif(() => changed, this.getNotifications(includeSeen), EMPTY)));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
@ -54,7 +54,7 @@ export class NotificationsService extends GenericService<Notification> {
|
||||
}
|
||||
|
||||
private _new(notification: INotification) {
|
||||
const message = this._translate(notification, notificationsTranslations[notification.notificationType]);
|
||||
const message = this._translate(notification, notificationsTranslations[notification.notificationType] as string);
|
||||
const time = this._getTime(notification.creationDate);
|
||||
return new Notification(notification, message, time);
|
||||
}
|
||||
@ -64,7 +64,7 @@ export class NotificationsService extends GenericService<Notification> {
|
||||
return moment(date).format('hh:mm A');
|
||||
}
|
||||
|
||||
private _translate(notification: INotification, translation: string) {
|
||||
private _translate(notification: INotification, translation: string): string {
|
||||
const fileId = notification.target.fileId;
|
||||
const dossierId = notification.target.dossierId;
|
||||
const dossier = this._dossiersService.find(dossierId);
|
||||
|
||||
@ -97,7 +97,7 @@ export class PermissionsService {
|
||||
|
||||
// TODO: Remove '?', after we make sure file is loaded before page
|
||||
canPerformAnnotationActions(file: File): boolean {
|
||||
return (file?.isUnderReview || file?.isUnderApproval) && this.isFileAssignee(file);
|
||||
return !file.excluded && (file?.isUnderReview || file?.isUnderApproval) && this.isFileAssignee(file);
|
||||
}
|
||||
|
||||
canUndoApproval(file: File | File[]): boolean {
|
||||
|
||||
@ -160,6 +160,16 @@ export class AppStateService {
|
||||
}
|
||||
}
|
||||
|
||||
dictionaryData['dossier_redaction'] = new Dictionary(
|
||||
{
|
||||
hexColor: colors.manualRedactionColor || FALLBACK_COLOR,
|
||||
type: 'dossier_redaction',
|
||||
hint: false,
|
||||
recommendation: false,
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
dictionaryData['declined-suggestion'] = new Dictionary(
|
||||
{
|
||||
hexColor: colors.notRedacted || FALLBACK_COLOR,
|
||||
|
||||
@ -1 +1 @@
|
||||
export const CHANGED_CHECK_INTERVAL = 3000;
|
||||
export const CHANGED_CHECK_INTERVAL = 5000;
|
||||
|
||||
@ -98,9 +98,9 @@ export function removeBraces(str: any): string {
|
||||
return str.replace(/[{}]/g, '');
|
||||
}
|
||||
|
||||
export function toKebabCase(str: string): string {
|
||||
export function toSnakeCase(str: string): string {
|
||||
return str
|
||||
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
||||
.replace(/[\s_]+/g, '-')
|
||||
.replace(/[\s_]+/g, '_')
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"ADMIN_CONTACT_NAME": null,
|
||||
"ADMIN_CONTACT_URL": null,
|
||||
"API_URL": "https://dev-08.iqser.cloud/redaction-gateway-v1",
|
||||
"API_URL": "https://aks-staging.iqser.cloud/redaction-gateway-v1",
|
||||
"APP_NAME": "RedactManager",
|
||||
"AUTO_READ_TIME": 1.5,
|
||||
"AUTO_READ_TIME": 3,
|
||||
"BACKEND_APP_VERSION": "4.4.40",
|
||||
"DELETE_RETENTION_HOURS": 96,
|
||||
"EULA_URL": "EULA_URL",
|
||||
@ -17,7 +17,8 @@
|
||||
"MAX_RETRIES_ON_SERVER_ERROR": 3,
|
||||
"OAUTH_CLIENT_ID": "redaction",
|
||||
"OAUTH_IDP_HINT": null,
|
||||
"OAUTH_URL": "https://dev-08.iqser.cloud/auth/realms/redaction",
|
||||
"OAUTH_URL": "https://aks-staging.iqser.cloud/auth/realms/redaction",
|
||||
"RECENT_PERIOD_IN_HOURS": 24,
|
||||
"SELECTION_MODE": "structural"
|
||||
"SELECTION_MODE": "structural",
|
||||
"USE_SESSION_FOR_DOWNLOAD": false
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -448,12 +448,25 @@
|
||||
"question": "Are you sure you want to delete {filesCount, plural, one{this document} other{these documents}}?",
|
||||
"title": "Delete {filesCount, plural, one{{fileName}} other{Selected Documents}}"
|
||||
},
|
||||
"unsaved-changes": {
|
||||
"confirmation-text": "Save and Leave",
|
||||
"details": "If you leave the tab without saving, all the unsaved changes will be lost.",
|
||||
"discard-changes-text": "DISCARD CHANGES",
|
||||
"question": "Are you sure you want to leave the tab? You have unsaved changes.",
|
||||
"title": "You have unsaved changes"
|
||||
},
|
||||
"upload-report-template": {
|
||||
"alternate-confirmation-text": "Upload as multi-file report",
|
||||
"confirmation-text": "Upload as single-file report",
|
||||
"deny-text": "Cancel Upload",
|
||||
"question": "Please choose if <b>{fileName}</b> is a single or multi-file report template",
|
||||
"title": "Report Template Upload"
|
||||
},
|
||||
"report-template-same-name": {
|
||||
"confirmation-text": "Yes. Continue upload",
|
||||
"deny-text": "No. Cancel Upload",
|
||||
"question": "There is already a Report Template with the name: <b>{fileName}</b>. Do you wish to continue?",
|
||||
"title": "Report Template Upload"
|
||||
}
|
||||
},
|
||||
"content": "Reason",
|
||||
@ -943,10 +956,23 @@
|
||||
"members": "Members",
|
||||
"team-members": "Team Members"
|
||||
},
|
||||
"side-nav-title": "Configurations",
|
||||
"unsaved-changes": "You have unsaved changes. Save or revert before changing the tab."
|
||||
"side-nav-title": "Configurations"
|
||||
},
|
||||
"error": {
|
||||
"deleted-entity": {
|
||||
"dossier": {
|
||||
"action": "Back to overview",
|
||||
"label": "This dossier has been deleted!"
|
||||
},
|
||||
"file-dossier": {
|
||||
"action": "Back to overview",
|
||||
"label": "The dossier of this file has been deleted!"
|
||||
},
|
||||
"file": {
|
||||
"action": "Back to dossier",
|
||||
"label": "This file has been deleted!"
|
||||
}
|
||||
},
|
||||
"http": {
|
||||
"generic": "Action failed with code {status}"
|
||||
},
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="90px" version="1.1" viewBox="-10 -10 110 110" width="90px"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd" id="Symbols" stroke="none" stroke-width="1">
|
||||
<g id="disable-tooltip">
|
||||
<rect height="100" id="Rectangle" width="100" x="0" y="0"></rect>
|
||||
<path
|
||||
d="M50,0 C75,0 95,20 95,45 C95,45.3345638 94.9964181,45.6682321 94.9892904,46.0009689 L84.9863576,46.0005557 C84.9954332,45.6682544 85,45.3347165 85,45 C85,25.5 69.5,10 50,10 C30.5,10 15,25.5 15,45 C15,53.3152174 17.870983,61.1519376 23.1448539,67.5739706 L23.5,68 L25,69.5 L25,86 L43,79.5 L43.2,79.5 L43.2,89.79 L15,100 L15,73 C8.5,65 5,55.5 5,45 C5,20 25,0 50,0 Z M87.5553391,55 L94.6264069,62.0710678 L80.484,76.213 L94.6264069,90.3553391 L87.5553391,97.4264069 L73.413,83.284 L59.2710678,97.4264069 L52.2,90.3553391 L66.342,76.213 L52.2,62.0710678 L59.2710678,55 L73.413,69.142 L87.5553391,55 Z"
|
||||
fill="#868E96" fill-rule="nonzero" id="Combined-Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 630 B After Width: | Height: | Size: 630 B |
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user