RED-3800 rss editing

This commit is contained in:
Timo Bejan 2022-12-13 13:50:58 +02:00
commit 4555f596bb
38 changed files with 482 additions and 150 deletions

View File

@ -149,9 +149,8 @@ export class ReportsScreenComponent implements OnInit {
} }
private async _loadReportTemplates() { private async _loadReportTemplates() {
this.availableTemplates$.next( const reportTemplates = await this._reportTemplateService.getAvailableReportTemplates(this.#dossierTemplateId);
await firstValueFrom(this._reportTemplateService.getAvailableReportTemplates(this.#dossierTemplateId)), this.availableTemplates$.next(reportTemplates);
);
} }
private async _loadPlaceholders() { private async _loadPlaceholders() {

View File

@ -214,18 +214,19 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
return; return;
} }
if ($event.key === 'ArrowLeft') { if (!$event.metaKey && !$event.ctrlKey && $event.key === 'ArrowLeft') {
this.pagesPanelActive = true; this.pagesPanelActive = true;
this._changeDetectorRef.markForCheck(); this._changeDetectorRef.markForCheck();
return; return;
} }
if ($event.key === 'ArrowRight') { if (!$event.metaKey && !$event.ctrlKey && $event.key === 'ArrowRight') {
this.pagesPanelActive = false; this.pagesPanelActive = false;
// if we activated annotationsPanel - // if we activated annotationsPanel -
// select first annotation from this page in case there is no // select first annotation from this page in case there is no
// selected annotation on this page and not in multi select mode // selected annotation on this page and not in multi select mode
if (!this.pagesPanelActive && !this.multiSelectService.isActive) { if (!this.pagesPanelActive && !this.multiSelectService.isActive) {
this._documentViewer.clearSelection();
this._selectFirstAnnotationOnCurrentPageIfNecessary(); this._selectFirstAnnotationOnCurrentPageIfNecessary();
} }
this._changeDetectorRef.markForCheck(); this._changeDetectorRef.markForCheck();

View File

@ -1,57 +1,72 @@
<section class="dialog"> <section class="dialog">
<div translate="rss-dialog.title" class="dialog-header heading-l"></div> <div class="dialog-header heading-l" translate="rss-dialog.title"></div>
<hr /> <hr />
<div class="dialog-content"> <div class="dialog-content">
<div class="table output-data" *ngIf="rssData$ | async as rssEntry"> <div *ngIf="rssData$ | async as rssEntry" class="table output-data">
<div class="table-header">Component</div> <div class="table-header">Component</div>
<div class="table-header">Value</div> <div class="table-header">Value</div>
<div class="table-header">Transformation</div> <div class="table-header">Transformation</div>
<div class="table-header">Annotations</div> <div class="table-header">
Annotations
<hr />
<div class="annotation-grid">
<div>Type</div>
<div>Rule</div>
<div>Pages</div>
<div>Reason</div>
</div>
</div>
<ng-container *ngFor="let entry of rssEntry.result | keyvalue: originalOrder"> <ng-container *ngFor="let entry of rssEntry.result | keyvalue: originalOrder">
<div class="bold">{{ entry.key }}</div> <div class="bold">{{ entry.key }}</div>
<div> <div>
<div class="value-content"> <iqser-editable-input
<iqser-editable-input (save)="saveEdit($event, entry)"
(save)="saveEdit($event, entry.value.originalKey)" [buttonsType]="iconButtonTypes.dark"
[buttonsType]="iconButtonTypes.dark" [cancelTooltip]="'rss-dialog.actions.cancel-edit' | translate"
[cancelTooltip]="'rss-dialog.actions.cancel-edit-name' | translate" [editTooltip]="'rss-dialog.actions.edit' | translate"
[class]="'w-200'" [saveTooltip]="'rss-dialog.actions.save' | translate"
[editTooltip]="'rss-dialog.actions.edit-name' | translate" [value]="entry.value.value ?? entry.value.originalValue"
[saveTooltip]="'rss-dialog.actions.save-name' | translate" >
[value]="entry.value.value ?? entry.value.originalValue" <ng-container slot="editing">
></iqser-editable-input>
<div class="actions">
<iqser-circle-button <iqser-circle-button
(action)="undo()" (action)="undo(entry)"
icon="red:undo" *ngIf="entry.value.value"
[showDot]="false" [showDot]="true"
[tooltip]="'rss-dialog.actions.undo' | translate" [tooltip]="'rss-dialog.actions.undo' | translate"
[type]="iconButtonTypes.dark" [type]="iconButtonTypes.dark"
class="ml-2"
icon="red:undo"
></iqser-circle-button> ></iqser-circle-button>
</div> </ng-container>
</div> </iqser-editable-input>
</div> </div>
<div>{{ entry.value.transformation }}</div> <div>{{ entry.value.transformation }}</div>
<div>{{ entry.value.scmAnnotations | json }}</div> <div class="annotation-grid">
<ng-container *ngFor="let annotation of entry.value.scmAnnotations">
<div>{{ annotation.type }}</div>
<div>{{ annotation.ruleNumber }}</div>
<div>{{ annotation.pages.join(',') }}</div>
<div>{{ annotation.reason }}</div>
</ng-container>
</div>
</ng-container> </ng-container>
</div> </div>
</div> </div>
<div class="dialog-actions"> <div class="dialog-actions">
<button color="primary" mat-flat-button type="submit" (click)="exportJSON()"> <button (click)="exportJSON()" color="primary" mat-flat-button type="submit">
{{ 'rss-dialog.actions.export-json' | translate }} {{ 'rss-dialog.actions.export-json' | translate }}
</button> </button>
<button color="primary" mat-flat-button type="button" (click)="exportXML()"> <button (click)="exportXML()" color="primary" mat-flat-button type="button">
{{ 'rss-dialog.actions.export-xml' | translate }} {{ 'rss-dialog.actions.export-xml' | translate }}
</button> </button>
<button color="primary" mat-flat-button type="button" (click)="exportAllInDossier()" *ngIf="userPreferences.areDevFeaturesEnabled"> <button (click)="exportAllInDossier()" *ngIf="userPreferences.areDevFeaturesEnabled" color="primary" mat-flat-button type="button">
{{ 'Export All' }} {{ 'Export All' }}
</button> </button>
<div class="all-caps-label cancel" mat-dialog-close translate="rss-dialog.actions.close"></div> <div class="all-caps-label cancel" mat-dialog-close translate="rss-dialog.actions.close"></div>
</div> </div>
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button> <iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
</section> </section>

View File

@ -47,3 +47,8 @@
font-weight: 600; font-weight: 600;
} }
} }
.annotation-grid {
display: grid;
grid-template-columns: 3fr 1fr 1fr 5fr;
}

View File

@ -1,9 +1,9 @@
import { Component, Inject } from '@angular/core'; import { Component, Inject, OnInit } from '@angular/core';
import { BaseDialogComponent, IconButtonTypes } from '@iqser/common-ui'; import { BaseDialogComponent, IconButtonTypes } from '@iqser/common-ui';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { RssService } from '@services/files/rss.service'; import { RssService } from '@services/files/rss.service';
import { IFile, IRssEntry } from '@red/domain'; import { IFile, IRssEntry } from '@red/domain';
import { firstValueFrom, Observable } from 'rxjs'; import { BehaviorSubject, firstValueFrom } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { FilesMapService } from '@services/files/files-map.service'; import { FilesMapService } from '@services/files/files-map.service';
import { UserPreferenceService } from '@users/user-preference.service'; import { UserPreferenceService } from '@users/user-preference.service';
@ -17,12 +17,10 @@ interface RssData {
templateUrl: './rss-dialog.component.html', templateUrl: './rss-dialog.component.html',
styleUrls: ['./rss-dialog.component.scss'], styleUrls: ['./rss-dialog.component.scss'],
}) })
export class RssDialogComponent extends BaseDialogComponent { export class RssDialogComponent extends BaseDialogComponent implements OnInit {
readonly iconButtonTypes = IconButtonTypes; readonly iconButtonTypes = IconButtonTypes;
rssData$: Observable<IRssEntry>; rssData$ = new BehaviorSubject<IRssEntry>(null);
originalOrder = (a: KeyValue<string, any>, b: KeyValue<string, any>): number => 0;
constructor( constructor(
protected readonly _dialogRef: MatDialogRef<RssDialogComponent>, protected readonly _dialogRef: MatDialogRef<RssDialogComponent>,
@ -32,22 +30,14 @@ export class RssDialogComponent extends BaseDialogComponent {
@Inject(MAT_DIALOG_DATA) readonly data: RssData, @Inject(MAT_DIALOG_DATA) readonly data: RssData,
) { ) {
super(_dialogRef); super(_dialogRef);
this.rssData$ = this._rssService.getRSSData(this.data.file.dossierId, this.data.file.fileId).pipe(
map(entry => {
const mapped = {};
for (const key of Object.keys(entry.result)) {
const newKey = key.replace(new RegExp('_', 'g'), ' ');
(<any>entry.result[key]).originalKey = key;
mapped[newKey] = entry.result[key];
}
return {
filaName: entry.filaName,
result: mapped,
};
}),
);
} }
async ngOnInit(): Promise<void> {
await this.#loadData();
}
originalOrder = (a: KeyValue<string, any>, b: KeyValue<string, any>): number => 0;
exportJSON() { exportJSON() {
this._rssService.exportJSON(this.data.file.dossierId, this.data.file.fileId, this.data.file.filename).subscribe(); this._rssService.exportJSON(this.data.file.dossierId, this.data.file.fileId, this.data.file.filename).subscribe();
} }
@ -68,14 +58,39 @@ export class RssDialogComponent extends BaseDialogComponent {
this.exportJSON(); this.exportJSON();
} }
undo() { async undo(entry: KeyValue<string, any>) {
console.log('Undo'); this._loadingService.start();
await firstValueFrom(this._rssService.revertOverride(this.data.file.dossierId, this.data.file.fileId, [entry.value.originalKey]));
await this.#loadData();
} }
saveEdit(event: string, originalKey: string) { async saveEdit(event: string, entry: KeyValue<string, any>) {
console.log(event, originalKey); this._loadingService.start();
/** await firstValueFrom(
* https://qa2.iqser.cloud/redaction-gateway-v1/swagger-ui/index.html#/rss-controller/revertOverrides this._rssService.override(this.data.file.dossierId, this.data.file.fileId, { [entry.value.originalKey]: event }),
*/ );
await this.#loadData();
}
async #loadData(): Promise<void> {
this._loadingService.start();
const rssData = await firstValueFrom(
this._rssService.getRSSData(this.data.file.dossierId, this.data.file.fileId).pipe(
map(entry => {
const mapped = {};
for (const key of Object.keys(entry.result)) {
const newKey = key.replace(new RegExp('_', 'g'), ' ');
(<any>entry.result[key]).originalKey = key;
mapped[newKey] = entry.result[key];
}
return {
filaName: entry.filaName,
result: mapped,
};
}),
),
);
this.rssData$.next(rssData);
this._loadingService.stop();
} }
} }

View File

@ -12,7 +12,6 @@ import {
ViewChild, ViewChild,
} from '@angular/core'; } from '@angular/core';
import { ActivatedRoute, ActivatedRouteSnapshot, NavigationExtras, Router } from '@angular/router'; import { ActivatedRoute, ActivatedRouteSnapshot, NavigationExtras, Router } from '@angular/router';
import { Core } from '@pdftron/webviewer';
import { import {
AutoUnsubscribe, AutoUnsubscribe,
bool, bool,
@ -26,7 +25,6 @@ import {
HelpModeService, HelpModeService,
List, List,
LoadingService, LoadingService,
log,
NestedFilter, NestedFilter,
OnAttach, OnAttach,
OnDetach, OnDetach,
@ -73,7 +71,6 @@ import { ConfigService } from '@services/config.service';
import { ReadableRedactionsService } from '../pdf-viewer/services/readable-redactions.service'; import { ReadableRedactionsService } from '../pdf-viewer/services/readable-redactions.service';
import { ROLES } from '@users/roles'; import { ROLES } from '@users/roles';
import { SuggestionsService } from './services/suggestions.service'; import { SuggestionsService } from './services/suggestions.service';
import Annotation = Core.Annotations.Annotation;
const textActions = [TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE]; const textActions = [TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE];
@ -92,10 +89,7 @@ export class FilePreviewScreenComponent
fullScreen = false; fullScreen = false;
readonly fileId = this.state.fileId; readonly fileId = this.state.fileId;
readonly dossierId = this.state.dossierId; readonly dossierId = this.state.dossierId;
readonly file$ = this.state.file$.pipe( readonly file$ = this.state.file$.pipe(tap(file => this._fileDataService.loadAnnotations(file)));
tap(file => this._fileDataService.loadAnnotations(file)),
log('file'),
);
width: number; width: number;
@ViewChild('annotationFilterTemplate', { @ViewChild('annotationFilterTemplate', {
read: TemplateRef, read: TemplateRef,
@ -180,6 +174,7 @@ export class FilePreviewScreenComponent
const earmarks$ = isEarmarksViewMode$.pipe( const earmarks$ = isEarmarksViewMode$.pipe(
tap(() => this._loadingService.start()), tap(() => this._loadingService.start()),
switchMap(() => this._fileDataService.loadEarmarks()), switchMap(() => this._fileDataService.loadEarmarks()),
switchMap(() => this._fileDataService.earmarks$),
tap(() => this.updateViewMode().then(() => this._loadingService.stop())), tap(() => this.updateViewMode().then(() => this._loadingService.stop())),
); );
@ -223,7 +218,7 @@ export class FilePreviewScreenComponent
switch (this._viewModeService.viewMode) { switch (this._viewModeService.viewMode) {
case ViewModes.STANDARD: { case ViewModes.STANDARD: {
this._setAnnotationsColor(redactions, 'annotationColor'); this._readableRedactionsService.setAnnotationsColor(redactions, 'annotationColor');
const wrappers = await this._fileDataService.annotations; const wrappers = await this._fileDataService.annotations;
const ocrAnnotationIds = wrappers.filter(a => a.isOCR).map(a => a.id); const ocrAnnotationIds = wrappers.filter(a => a.isOCR).map(a => a.id);
const standardEntries = annotations const standardEntries = annotations
@ -232,7 +227,7 @@ export class FilePreviewScreenComponent
const nonStandardEntries = annotations.filter( const nonStandardEntries = annotations.filter(
a => bool(a.getCustomData('changeLogRemoved')) || this._annotationManager.isHidden(a.Id), a => bool(a.getCustomData('changeLogRemoved')) || this._annotationManager.isHidden(a.Id),
); );
this._setAnnotationsOpacity(standardEntries, true); this._readableRedactionsService.setAnnotationsOpacity(standardEntries, true);
this._annotationManager.show(standardEntries); this._annotationManager.show(standardEntries);
this._annotationManager.hide(nonStandardEntries); this._annotationManager.hide(nonStandardEntries);
break; break;
@ -240,8 +235,8 @@ export class FilePreviewScreenComponent
case ViewModes.DELTA: { case ViewModes.DELTA: {
const changeLogEntries = annotations.filter(a => bool(a.getCustomData('changeLog'))); const changeLogEntries = annotations.filter(a => bool(a.getCustomData('changeLog')));
const nonChangeLogEntries = annotations.filter(a => !bool(a.getCustomData('changeLog'))); const nonChangeLogEntries = annotations.filter(a => !bool(a.getCustomData('changeLog')));
this._setAnnotationsColor(redactions, 'annotationColor'); this._readableRedactionsService.setAnnotationsColor(redactions, 'annotationColor');
this._setAnnotationsOpacity(changeLogEntries, true); this._readableRedactionsService.setAnnotationsOpacity(changeLogEntries, true);
this._annotationManager.show(changeLogEntries); this._annotationManager.show(changeLogEntries);
this._annotationManager.hide(nonChangeLogEntries); this._annotationManager.hide(nonChangeLogEntries);
break; break;
@ -250,14 +245,8 @@ export class FilePreviewScreenComponent
const nonRedactionEntries = annotations.filter( const nonRedactionEntries = annotations.filter(
a => !bool(a.getCustomData('redaction')) || bool(a.getCustomData('changeLogRemoved')), a => !bool(a.getCustomData('redaction')) || bool(a.getCustomData('changeLogRemoved')),
); );
if (this._readableRedactionsService.active) { this._readableRedactionsService.setPreviewAnnotationsOpacity(redactions);
this._setAnnotationsOpacity(redactions, true); this._readableRedactionsService.setPreviewAnnotationsColor(redactions);
this._setAnnotationsColor(redactions, 'annotationColor');
} else {
this._setAnnotationsOpacity(redactions);
this._setAnnotationsColor(redactions, 'redactionColor');
}
this._annotationManager.show(redactions); this._annotationManager.show(redactions);
this._annotationManager.hide(nonRedactionEntries); this._annotationManager.hide(nonRedactionEntries);
this._suggestionsService.hideSuggestionsInPreview(redactions); this._suggestionsService.hideSuggestionsInPreview(redactions);
@ -762,20 +751,6 @@ export class FilePreviewScreenComponent
} }
} }
private _setAnnotationsOpacity(annotations: Annotation[], restoreToOriginal = false) {
annotations.forEach(annotation => {
annotation['Opacity'] = restoreToOriginal ? parseFloat(annotation.getCustomData('opacity')) : 1;
});
}
private _setAnnotationsColor(annotations: Annotation[], customData: string) {
annotations.forEach(annotation => {
const color = this._annotationDrawService.convertColor(annotation.getCustomData(customData));
annotation['StrokeColor'] = color;
annotation['FillColor'] = color;
});
}
private _navigateToDossier() { private _navigateToDossier() {
this._logger.info('Navigating to ', this.state.dossier.dossierName); this._logger.info('Navigating to ', this.state.dossier.dossierName);
return this._router.navigate([this.state.dossier.routerLink]); return this._router.navigate([this.state.dossier.routerLink]);

View File

@ -181,9 +181,10 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
async #buildRemovedRedactions(redactionLog: IRedactionLog, file: File): Promise<void> { async #buildRemovedRedactions(redactionLog: IRedactionLog, file: File): Promise<void> {
const redactionLogCopy = JSON.parse(JSON.stringify(redactionLog)); const redactionLogCopy = JSON.parse(JSON.stringify(redactionLog));
redactionLogCopy.redactionLogEntry = redactionLogCopy.redactionLogEntry.reduce((filtered, entry) => { redactionLogCopy.redactionLogEntry = redactionLogCopy.redactionLogEntry.reduce((filtered, entry) => {
const isRemoveChange = entry.manualChanges.find(c => this.#isRemoveChange(c.manualRedactionType)); const lastChange = entry.manualChanges.at(-1);
const isRemoveChange = this.#isRemoveChange(lastChange?.manualRedactionType);
if (isRemoveChange) { if (isRemoveChange) {
entry.manualChanges = entry.manualChanges.filter(c => !this.#isRemoveChange(c.manualRedactionType)); entry.manualChanges.pop();
filtered.push(entry); filtered.push(entry);
} }
return filtered; return filtered;

View File

@ -6,6 +6,7 @@ import Annotation = Core.Annotations.Annotation;
import { REDAnnotationManager } from '../../pdf-viewer/services/annotation-manager.service'; import { REDAnnotationManager } from '../../pdf-viewer/services/annotation-manager.service';
import { UserPreferenceService } from '@users/user-preference.service'; import { UserPreferenceService } from '@users/user-preference.service';
import { AnnotationDrawService } from '../../pdf-viewer/services/annotation-draw.service'; import { AnnotationDrawService } from '../../pdf-viewer/services/annotation-draw.service';
import { ReadableRedactionsService } from '../../pdf-viewer/services/readable-redactions.service';
@Injectable() @Injectable()
export class SuggestionsService { export class SuggestionsService {
@ -15,6 +16,7 @@ export class SuggestionsService {
private readonly _annotationManager: REDAnnotationManager, private readonly _annotationManager: REDAnnotationManager,
private readonly _userPreferenceService: UserPreferenceService, private readonly _userPreferenceService: UserPreferenceService,
private readonly _annotationDrawService: AnnotationDrawService, private readonly _annotationDrawService: AnnotationDrawService,
private readonly _readableRedactionsService: ReadableRedactionsService,
) {} ) {}
set removedRedactions(removedRedactions: AnnotationWrapper[]) { set removedRedactions(removedRedactions: AnnotationWrapper[]) {
@ -39,14 +41,8 @@ export class SuggestionsService {
#convertRemoveSuggestionsToRedactions(suggestions: Annotation[]): void { #convertRemoveSuggestionsToRedactions(suggestions: Annotation[]): void {
const removeSuggestions = suggestions.filter(a => bool(a.getCustomData('suggestionRemove'))); const removeSuggestions = suggestions.filter(a => bool(a.getCustomData('suggestionRemove')));
this._readableRedactionsService.setPreviewAnnotationsOpacity(removeSuggestions);
removeSuggestions.forEach(suggestion => { this._readableRedactionsService.setPreviewAnnotationsColor(removeSuggestions);
const color = this._annotationDrawService.convertColor(suggestion.getCustomData('redactionColor'));
suggestion['Opacity'] = 1;
suggestion['StrokeColor'] = color;
suggestion['FillColor'] = color;
});
this._annotationManager.show(removeSuggestions); this._annotationManager.show(removeSuggestions);
} }
} }

View File

@ -19,6 +19,7 @@ import Quad = Core.Math.Quad;
const DEFAULT_TEXT_ANNOTATION_OPACITY = 1; const DEFAULT_TEXT_ANNOTATION_OPACITY = 1;
const DEFAULT_REMOVED_ANNOTATION_OPACITY = 0.2; const DEFAULT_REMOVED_ANNOTATION_OPACITY = 0.2;
const FINAL_REDACTION_COLOR = '#000000';
@Injectable() @Injectable()
export class AnnotationDrawService { export class AnnotationDrawService {
@ -164,6 +165,7 @@ export class AnnotationDrawService {
? this._defaultColorsService.getColor(dossierTemplateId, 'requestAddColor') ? this._defaultColorsService.getColor(dossierTemplateId, 'requestAddColor')
: this._defaultColorsService.getColor(dossierTemplateId, 'previewColor'); : this._defaultColorsService.getColor(dossierTemplateId, 'previewColor');
annotation.setCustomData('redactionColor', String(redactionColor)); annotation.setCustomData('redactionColor', String(redactionColor));
annotation.setCustomData('finalRedactionColor', FINAL_REDACTION_COLOR);
annotation.setCustomData('annotationColor', String(annotationWrapper.color)); annotation.setCustomData('annotationColor', String(annotationWrapper.color));
return annotation; return annotation;

View File

@ -80,6 +80,11 @@ export class REDDocumentViewer {
return merge(this.#documentUnloaded$, this.#documentLoaded$).pipe(shareLast()); return merge(this.#documentUnloaded$, this.#documentLoaded$).pipe(shareLast());
} }
clearSelection() {
this.#document.clearSelection();
this.#pdf.disable('textPopup');
}
close() { close() {
this.#logger.info('[PDF] Closing document'); this.#logger.info('[PDF] Closing document');
this.#document.closeDocument(); this.#document.closeDocument();

View File

@ -7,13 +7,15 @@ import { PdfViewer } from './pdf-viewer.service';
import { REDAnnotationManager } from './annotation-manager.service'; import { REDAnnotationManager } from './annotation-manager.service';
import { AnnotationDrawService } from './annotation-draw.service'; import { AnnotationDrawService } from './annotation-draw.service';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { Core } from '@pdftron/webviewer';
import Annotation = Core.Annotations.Annotation;
@Injectable() @Injectable()
export class ReadableRedactionsService { export class ReadableRedactionsService {
readonly active$: Observable<boolean>;
readonly #enableIcon = this._convertPath('/assets/icons/general/pdftron-action-enable-tooltips.svg'); readonly #enableIcon = this._convertPath('/assets/icons/general/pdftron-action-enable-tooltips.svg');
readonly #disableIcon = this._convertPath('/assets/icons/general/pdftron-action-disable-tooltips.svg'); readonly #disableIcon = this._convertPath('/assets/icons/general/pdftron-action-disable-tooltips.svg');
readonly #active$ = new BehaviorSubject<boolean>(false); readonly #active$ = new BehaviorSubject<boolean>(true);
readonly active$: Observable<boolean>;
constructor( constructor(
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn, @Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
@ -47,4 +49,27 @@ export class ReadableRedactionsService {
img: this.toggleReadableRedactionsBtnIcon, img: this.toggleReadableRedactionsBtnIcon,
}); });
} }
setAnnotationsOpacity(annotations: Annotation[], restoreToOriginal = false) {
annotations.forEach(annotation => {
annotation['Opacity'] = restoreToOriginal ? parseFloat(annotation.getCustomData('opacity')) : 0.5;
});
}
setAnnotationsColor(annotations: Annotation[], customData: string) {
annotations.forEach(annotation => {
const color = this._annotationDrawService.convertColor(annotation.getCustomData(customData));
annotation['StrokeColor'] = color;
annotation['FillColor'] = color;
});
}
setPreviewAnnotationsOpacity(annotations: Annotation[]) {
this.setAnnotationsOpacity(annotations, !this.active);
}
setPreviewAnnotationsColor(annotations: Annotation[]) {
const color = this.active ? 'redactionColor' : 'finalRedactionColor';
this.setAnnotationsColor(annotations, color);
}
} }

View File

@ -249,7 +249,7 @@ export class ViewerHeaderService {
header.getItems().splice(10, header.getItems().length - 14, ...enabledItems); header.getItems().splice(10, header.getItems().length - 14, ...enabledItems);
}); });
this._pdf.instance.UI.updateElement('selectToolButton', { this._pdf.instance?.UI.updateElement('selectToolButton', {
img: this._convertPath('/assets/icons/general/pdftron-cursor.svg'), img: this._convertPath('/assets/icons/general/pdftron-cursor.svg'),
}); });
} }

View File

@ -106,8 +106,7 @@ export class EditDossierDownloadPackageComponent
existsWatermarks: this.#existsWatermarks$, existsWatermarks: this.#existsWatermarks$,
}); });
this.availableReportTypes = this.availableReportTypes = (await this._reportTemplateController.getAvailableReportTemplates(dossierTemplateId)) || [];
(await firstValueFrom(this._reportTemplateController.getAvailableReportTemplates(dossierTemplateId))) || [];
this.form = this._getForm(); this.form = this._getForm();
if (!this.canEditDossier) { if (!this.canEditDossier) {

View File

@ -200,7 +200,7 @@ export class AddEditEntityComponent extends BaseFormComponent implements OnInit
private _toTechnicalName(value: string) { private _toTechnicalName(value: string) {
const existingTechnicalNames = this._dictionariesMapService.get(this.dossierTemplateId).map(dict => dict.type); const existingTechnicalNames = this._dictionariesMapService.get(this.dossierTemplateId).map(dict => dict.type);
const baseTechnicalName = toSnakeCase(value.trim()); const baseTechnicalName = toSnakeCase(value.trim());
let technicalName = baseTechnicalName; let technicalName = baseTechnicalName.replaceAll(/[^A-Za-z0-9_-]/g, '');
let suffix = 1; let suffix = 1;
while (existingTechnicalNames.includes(technicalName)) { while (existingTechnicalNames.includes(technicalName)) {
technicalName = [baseTechnicalName, suffix++].join('_'); technicalName = [baseTechnicalName, suffix++].join('_');

View File

@ -2,8 +2,14 @@ import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/c
import { PermissionsService } from '@services/permissions.service'; import { PermissionsService } from '@services/permissions.service';
import { Dossier, File } from '@red/domain'; import { Dossier, File } from '@red/domain';
import { FileDownloadService } from '@upload-download/services/file-download.service'; import { FileDownloadService } from '@upload-download/services/file-download.service';
import { CircleButtonType, CircleButtonTypes, Toaster } from '@iqser/common-ui'; import { CircleButtonType, CircleButtonTypes, defaultDialogConfig, Toaster } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { MatDialog } from '@angular/material/dialog';
import {
DownloadDialogComponent,
DownloadDialogData,
DownloadDialogResult,
} from '@shared/dialogs/download-dialog/download-dialog.component';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
@Component({ @Component({
@ -25,6 +31,7 @@ export class FileDownloadBtnComponent implements OnChanges {
constructor( constructor(
private readonly _permissionsService: PermissionsService, private readonly _permissionsService: PermissionsService,
private readonly _fileDownloadService: FileDownloadService, private readonly _fileDownloadService: FileDownloadService,
private readonly _dialog: MatDialog,
private readonly _toaster: Toaster, private readonly _toaster: Toaster,
) {} ) {}
@ -35,9 +42,20 @@ export class FileDownloadBtnComponent implements OnChanges {
async downloadFiles($event: MouseEvent) { async downloadFiles($event: MouseEvent) {
$event.stopPropagation(); $event.stopPropagation();
const dossierId = this.files[0].dossierId; const ref = this._dialog.open<DownloadDialogComponent, DownloadDialogData, DownloadDialogResult>(DownloadDialogComponent, {
const filesIds = this.files.map(f => f.id); ...defaultDialogConfig,
await firstValueFrom(this._fileDownloadService.downloadFiles(filesIds, dossierId)); data: { dossier: this.dossier, hasUnapprovedDocuments: this.files.some(file => !file.isApproved) },
});
const result = await firstValueFrom(ref.afterClosed());
if (!result) {
return;
}
await this._fileDownloadService.downloadFiles({
dossierId: this.dossier.id,
fileIds: this.files.map(f => f.id),
...result,
});
this._toaster.info(_('download-status.queued')); this._toaster.info(_('download-status.queued'));
} }
} }

View File

@ -1,7 +1,8 @@
import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges, ViewChild } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges, ViewChild } from '@angular/core';
import { Action, ActionTypes, File } from '@red/domain'; import { Action, ActionTypes, Dossier, File } from '@red/domain';
import { import {
CircleButtonType, CircleButtonType,
defaultDialogConfig,
IqserTooltipPosition, IqserTooltipPosition,
OverlappingElements, OverlappingElements,
ScrollableParentView, ScrollableParentView,
@ -13,6 +14,12 @@ import { FileDownloadService } from '@upload-download/services/file-download.ser
import { PermissionsService } from '@services/permissions.service'; import { PermissionsService } from '@services/permissions.service';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { MatMenuTrigger } from '@angular/material/menu'; import { MatMenuTrigger } from '@angular/material/menu';
import {
DownloadDialogComponent,
DownloadDialogData,
DownloadDialogResult,
} from '@shared/dialogs/download-dialog/download-dialog.component';
import { MatDialog } from '@angular/material/dialog';
@Component({ @Component({
selector: 'redaction-expandable-file-actions', selector: 'redaction-expandable-file-actions',
@ -39,8 +46,13 @@ export class ExpandableFileActionsComponent implements OnChanges {
private readonly _fileDownloadService: FileDownloadService, private readonly _fileDownloadService: FileDownloadService,
private readonly _toaster: Toaster, private readonly _toaster: Toaster,
private readonly _permissionsService: PermissionsService, private readonly _permissionsService: PermissionsService,
private readonly _dialog: MatDialog,
) {} ) {}
get overlappingElement() {
return this.helpModeKey === 'document_features_in_editor' ? OverlappingElements.USER_MENU : undefined;
}
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
if (changes.actions || changes.maxWidth || changes.minWidth) { if (changes.actions || changes.maxWidth || changes.minWidth) {
let count = 0; let count = 0;
@ -72,7 +84,7 @@ export class ExpandableFileActionsComponent implements OnChanges {
// Patch download button // Patch download button
const downloadBtn = this.actions.find(btn => btn.type === ActionTypes.downloadBtn); const downloadBtn = this.actions.find(btn => btn.type === ActionTypes.downloadBtn);
if (downloadBtn) { if (downloadBtn) {
downloadBtn.action = ($event: MouseEvent) => this._downloadFiles($event, downloadBtn.files); downloadBtn.action = ($event: MouseEvent) => this._downloadFiles($event, downloadBtn.files, downloadBtn.dossier);
downloadBtn.disabled = !this._permissionsService.canDownloadFiles(downloadBtn.files, downloadBtn.dossier); downloadBtn.disabled = !this._permissionsService.canDownloadFiles(downloadBtn.files, downloadBtn.dossier);
} }
} }
@ -83,20 +95,27 @@ export class ExpandableFileActionsComponent implements OnChanges {
} }
} }
private async _downloadFiles($event: MouseEvent, files: File[]) {
$event.stopPropagation();
const dossierId = files[0].dossierId;
const filesIds = files.map(f => f.id);
await firstValueFrom(this._fileDownloadService.downloadFiles(filesIds, dossierId));
this._toaster.info(_('download-status.queued'));
}
onButtonClick(button: Action, $event: MouseEvent) { onButtonClick(button: Action, $event: MouseEvent) {
button.action($event); button.action($event);
this.matMenu.closeMenu(); this.matMenu.closeMenu();
} }
get overlappingElement() { private async _downloadFiles($event: MouseEvent, files: File[], dossier: Dossier) {
return this.helpModeKey === 'document_features_in_editor' ? OverlappingElements.USER_MENU : undefined; $event.stopPropagation();
const ref = this._dialog.open<DownloadDialogComponent, DownloadDialogData, DownloadDialogResult>(DownloadDialogComponent, {
...defaultDialogConfig,
data: { dossier, hasUnapprovedDocuments: files.some(file => !file.isApproved) },
});
const result = await firstValueFrom(ref.afterClosed());
if (!result) {
return;
}
await this._fileDownloadService.downloadFiles({
dossierId: dossier.id,
fileIds: files.map(f => f.id),
...result,
});
this._toaster.info(_('download-status.queued'));
} }
} }

View File

@ -25,6 +25,7 @@
flex-direction: column; flex-direction: column;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
flex-wrap: nowrap;
@include common-mixins.scroll-bar(); @include common-mixins.scroll-bar();
} }
} }

View File

@ -102,7 +102,7 @@ export class AddDossierDialogComponent extends BaseDialogComponent implements On
if (dossierTemplate) { if (dossierTemplate) {
this.availableReportTypes = this.availableReportTypes =
(await firstValueFrom(this._reportTemplateController.getAvailableReportTemplates(dossierTemplate.dossierTemplateId))) || []; (await this._reportTemplateController.getAvailableReportTemplates(dossierTemplate.dossierTemplateId)) || [];
// update dropdown values // update dropdown values
this.form.patchValue( this.form.patchValue(
{ {

View File

@ -0,0 +1,61 @@
<section class="dialog">
<form *ngIf="form" [formGroup]="form">
<div [translate]="'download-dialog.header'" class="dialog-header heading-l"></div>
<div *ngIf="data.hasUnapprovedDocuments" class="inline-dialog-toast toast-warning">
<div [translate]="'download-dialog.unapproved-files-warning'"></div>
</div>
<div class="dialog-content">
<redaction-select
*ngIf="availableReportTypes | async as reportTypes"
[label]="'report-type.label' | translate: { length: reportTypesLength }"
[optionTemplate]="reportTemplateOptionTemplate"
[options]="reportTypes"
[valueMapper]="reportTemplateValueMapper"
class="mb-16"
formControlName="reportTemplateIds"
></redaction-select>
<redaction-select
[label]="'download-type.label' | translate: { length: downloadFileTypesLength }"
[options]="downloadTypes"
formControlName="downloadFileTypes"
></redaction-select>
<div class="iqser-input-group required">
<label translate="download-dialog.form.redaction-preview-color"></label>
<input
[placeholder]="'download-dialog.form.redaction-preview-color-placeholder' | translate"
class="hex-color-input"
formControlName="redactionPreviewColor"
name="color"
type="text"
/>
<div
(colorPickerChange)="form.controls.redactionPreviewColor.setValue($event)"
[colorPicker]="form.controls.redactionPreviewColor.value"
[cpOutputFormat]="'hex'"
[style.background]="form.controls.redactionPreviewColor.value"
class="input-icon"
>
<mat-icon
*ngIf="!form.controls.redactionPreviewColor.value || form.controls.redactionPreviewColor.value?.length === 0"
svgIcon="red:color-picker"
></mat-icon>
</div>
</div>
</div>
<div class="dialog-actions">
<button (click)="save()" [disabled]="form.invalid" color="primary" mat-flat-button type="submit">
{{ 'download-dialog.actions.save' | translate }}
</button>
</div>
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
</form>
</section>
<ng-template #reportTemplateOptionTemplate let-option="option">
{{ option.fileName }} {{ option.multiFileReport ? ('reports-screen.multi-file-report' | translate) : '' }}
</ng-template>

View File

@ -0,0 +1,100 @@
import { Component, Inject } from '@angular/core';
import { Dossier, DownloadFileType, IReportTemplate } from '@red/domain';
import { downloadTypesForDownloadTranslations } from '@translations/download-types-translations';
import { ReportTemplateService } from '@services/report-template.service';
import { AbstractControl, FormBuilder } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { DefaultColorsService } from '@services/entity-services/default-colors.service';
import { List } from '@iqser/common-ui';
export interface DownloadDialogData {
readonly dossier: Dossier;
readonly hasUnapprovedDocuments: boolean;
}
export interface DownloadDialogResult {
downloadFileTypes: List<DownloadFileType>;
reportTemplateIds: List;
redactionPreviewColor: string;
}
@Component({
selector: 'redaction-download-dialog',
templateUrl: './download-dialog.component.html',
styleUrls: ['./download-dialog.component.scss'],
})
export class DownloadDialogComponent {
readonly downloadTypes: { key: DownloadFileType; label: string }[] = ['ORIGINAL', 'PREVIEW', 'DELTA_PREVIEW', 'REDACTED'].map(
(type: DownloadFileType) => ({
key: type,
label: downloadTypesForDownloadTranslations[type],
}),
);
readonly availableReportTypes = this._availableReportTypes;
readonly form = this._getForm();
constructor(
private readonly _defaultColorsService: DefaultColorsService,
private readonly _reportTemplateController: ReportTemplateService,
private readonly _formBuilder: FormBuilder,
private readonly _dialogRef: MatDialogRef<DownloadDialogComponent, DownloadDialogResult>,
@Inject(MAT_DIALOG_DATA) readonly data: DownloadDialogData,
) {}
get reportTypesLength() {
return this.form.controls.reportTemplateIds?.value?.length || 0;
}
get downloadFileTypesLength() {
return this.form.controls.downloadFileTypes?.value?.length || 0;
}
private get _availableReportTypes() {
const dossierTemplateId = this.data.dossier.dossierTemplateId;
const result = this._reportTemplateController.getAvailableReportTemplates(dossierTemplateId);
return result.then(values => values ?? []);
}
reportTemplateValueMapper = (reportTemplate: IReportTemplate) => reportTemplate.templateId;
async save() {
const result: DownloadDialogResult = {
reportTemplateIds: this.form.controls.reportTemplateIds.value,
downloadFileTypes: this.form.controls.downloadFileTypes.value,
redactionPreviewColor: this.form.controls.redactionPreviewColor.value,
};
this._dialogRef.close(result);
}
close() {
this._dialogRef.close();
}
private _hasReportTemplateOrDownloadType(control: AbstractControl) {
const atLeastAReportSelected = control.get('reportTemplateIds')?.value.length > 0;
const atLeastATypeSelected = control.get('downloadFileTypes')?.value.length > 0;
return atLeastATypeSelected || atLeastAReportSelected ? null : { reportTemplateIds: true, downloadFileTypes: true };
}
private _getForm() {
const previewColor = this._defaultColorsService.getColor(this.data.dossier.dossierTemplateId, 'previewColor');
return this._formBuilder.group(
{
reportTemplateIds: [this.data.dossier.reportTemplateIds],
downloadFileTypes: [this.data.dossier.downloadFileTypes],
redactionPreviewColor: [previewColor],
},
{
validators: [control => this._hasReportTemplateOrDownloadType(control), control => this._isHexColor(control)],
},
);
}
private _isHexColor(control: AbstractControl) {
const color = control.get('redactionPreviewColor')?.value;
const isHexColor = /^#[0-9A-F]{6}$/i.test(color);
return isHexColor ? null : { redactionPreviewColor: true };
}
}

View File

@ -42,6 +42,7 @@ import { AddEditEntityComponent } from './components/add-edit-entity/add-edit-en
import { ColorPickerModule } from 'ngx-color-picker'; import { ColorPickerModule } from 'ngx-color-picker';
import { WatermarkSelectorComponent } from './components/dossier-watermark-selector/watermark-selector.component'; import { WatermarkSelectorComponent } from './components/dossier-watermark-selector/watermark-selector.component';
import { OcrProgressBarComponent } from './components/ocr-progress-bar/ocr-progress-bar.component'; import { OcrProgressBarComponent } from './components/ocr-progress-bar/ocr-progress-bar.component';
import { DownloadDialogComponent } from './dialogs/download-dialog/download-dialog.component';
const buttons = [FileDownloadBtnComponent]; const buttons = [FileDownloadBtnComponent];
@ -76,7 +77,7 @@ const services = [SharedDialogService];
const modules = [MatConfigModule, ScrollingModule, IconsModule, FormsModule, ReactiveFormsModule, ColorPickerModule]; const modules = [MatConfigModule, ScrollingModule, IconsModule, FormsModule, ReactiveFormsModule, ColorPickerModule];
@NgModule({ @NgModule({
declarations: [...components, ...utils, EditorComponent], declarations: [...components, ...utils, EditorComponent, DownloadDialogComponent],
imports: [ imports: [
CommonModule, CommonModule,
...modules, ...modules,

View File

@ -29,11 +29,9 @@ export class FileDownloadService extends EntitiesService<IDownloadStatus, Downlo
super(); super();
} }
downloadFiles(fileIds: List, dossierId: string): Observable<DownloadStatus[]> { downloadFiles(request: IPrepareDownloadRequest): Promise<DownloadStatus[]> {
return this.prepareDownload({ const prepare = this.prepareDownload(request).pipe(switchMap(() => this.loadAll()));
fileIds, return firstValueFrom(prepare);
dossierId,
}).pipe(switchMap(() => this.loadAll()));
} }
loadAll(): Observable<DownloadStatus[]> { loadAll(): Observable<DownloadStatus[]> {
@ -61,7 +59,7 @@ export class FileDownloadService extends EntitiesService<IDownloadStatus, Downlo
@Validate() @Validate()
prepareDownload(@RequiredParam() body: IPrepareDownloadRequest): Observable<IDownloadResponse> { prepareDownload(@RequiredParam() body: IPrepareDownloadRequest): Observable<IDownloadResponse> {
return this._post(body, `${this._defaultModelPath}/prepare`); return this._post(body, `${this._defaultModelPath}/prepare-option`);
} }
@Validate() @Validate()

View File

@ -40,6 +40,20 @@ export class RssService extends GenericService<void> {
}); });
} }
@Validate()
override(
@RequiredParam() dossierId: string,
@RequiredParam() fileId: string,
@RequiredParam() componentOverrides: Record<string, string>,
) {
return this._post({ componentOverrides }, `rss/override/${dossierId}/${fileId}`);
}
@Validate()
revertOverride(@RequiredParam() dossierId: string, @RequiredParam() fileId: string, @RequiredParam() components: string[]) {
return this._post({ components }, `rss/override/revert/${dossierId}/${fileId}`);
}
exportJSON(dossierId: string, fileId: string, name: string) { exportJSON(dossierId: string, fileId: string, name: string) {
return this.getRSSData(dossierId, fileId).pipe( return this.getRSSData(dossierId, fileId).pipe(
tap(data => { tap(data => {

View File

@ -261,7 +261,7 @@ export class PermissionsService {
if (files.length === 0) { if (files.length === 0) {
return false; return false;
} }
return this.isApprover(dossier) && files.reduce((prev, file) => prev && file.isApproved, true); return this.isApprover(dossier) && files.reduce((prev, file) => prev && !file.isInitialProcessing, true);
} }
canSoftDeleteDossier(dossier: IDossier): boolean { canSoftDeleteDossier(dossier: IDossier): boolean {

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { GenericService, HeadersConfiguration, RequiredParam, Validate } from '@iqser/common-ui'; import { GenericService, HeadersConfiguration, RequiredParam, Validate } from '@iqser/common-ui';
import { IPlaceholdersResponse, IReportTemplate } from '@red/domain'; import { IPlaceholdersResponse, IReportTemplate } from '@red/domain';
import { Observable, of } from 'rxjs'; import { firstValueFrom, Observable, of } from 'rxjs';
import { HttpResponse } from '@angular/common/http'; import { HttpResponse } from '@angular/common/http';
import { catchError } from 'rxjs/operators'; import { catchError } from 'rxjs/operators';
@ -34,7 +34,8 @@ export class ReportTemplateService extends GenericService<unknown> {
@Validate() @Validate()
getAvailableReportTemplates(@RequiredParam() dossierTemplateId: string) { getAvailableReportTemplates(@RequiredParam() dossierTemplateId: string) {
return this.getAll<IReportTemplate[]>(`${this._defaultModelPath}/${dossierTemplateId}`); const request = this.getAll<IReportTemplate[]>(`${this._defaultModelPath}/${dossierTemplateId}`);
return firstValueFrom(request);
} }
@Validate() @Validate()

View File

@ -9,3 +9,12 @@ export const downloadTypesTranslations: { [key in DownloadFileType]: string } =
FLATTEN: _('download-type.flatten'), FLATTEN: _('download-type.flatten'),
DELTA_PREVIEW: _('download-type.delta-preview'), DELTA_PREVIEW: _('download-type.delta-preview'),
} as const; } as const;
export const downloadTypesForDownloadTranslations: { [key in DownloadFileType]: string } = {
ORIGINAL: _('download-type.original'),
PREVIEW: _('download-type.preview'),
REDACTED: _('download-type.redacted-only'),
ANNOTATED: _('download-type.annotated'),
FLATTEN: _('download-type.flatten'),
DELTA_PREVIEW: _('download-type.delta-preview'),
} as const;

View File

@ -13,7 +13,7 @@ export class RedRoleGuard extends IqserRoleGuard {
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> { async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
const currentUser = this._userService.currentUser; const currentUser = this._userService.currentUser;
if (!currentUser.hasAnyRole) { if (!currentUser?.hasAnyRole) {
await this._router.navigate(['/auth-error']); await this._router.navigate(['/auth-error']);
this._loadingService.stop(); this._loadingService.stop();
return false; return false;

View File

@ -1017,6 +1017,17 @@
"active": "", "active": "",
"archive": "" "archive": ""
}, },
"download-dialog": {
"actions": {
"save": ""
},
"form": {
"redaction-preview-color": "",
"redaction-preview-color-placeholder": ""
},
"header": "",
"unapproved-files-warning": ""
},
"download-includes": "Wählen Sie die Dokumente für Ihr Download-Paket aus", "download-includes": "Wählen Sie die Dokumente für Ihr Download-Paket aus",
"download-status": { "download-status": {
"queued": "Ihr Download wurde zur Warteschlange hinzugefügt. Hier finden Sie alle angeforderten Downloads: <a href='/main/downloads'>My Downloads<a/>." "queued": "Ihr Download wurde zur Warteschlange hinzugefügt. Hier finden Sie alle angeforderten Downloads: <a href='/main/downloads'>My Downloads<a/>."
@ -1028,7 +1039,8 @@
"label": "{length} Dokumenten{length, plural, one{version} other{versionen}}", "label": "{length} Dokumenten{length, plural, one{version} other{versionen}}",
"original": "Optimiertes PDF", "original": "Optimiertes PDF",
"preview": "PDF-Vorschau", "preview": "PDF-Vorschau",
"redacted": "geschwärztes PDF" "redacted": "geschwärztes PDF",
"redacted-only": ""
}, },
"downloads-list": { "downloads-list": {
"actions": { "actions": {
@ -1941,9 +1953,13 @@
}, },
"rss-dialog": { "rss-dialog": {
"actions": { "actions": {
"cancel-edit": "",
"close": "", "close": "",
"edit": "",
"export-json": "", "export-json": "",
"export-xml": "" "export-xml": "",
"save": "",
"undo": ""
}, },
"title": "" "title": ""
}, },

View File

@ -849,7 +849,7 @@
} }
}, },
"download-file": "Download", "download-file": "Download",
"download-file-disabled": "You need to be approver in the dossier and the {count, plural, one{file needs} other{files need}} to be approved in order to download.", "download-file-disabled": "You need to be approver in the dossier and the {count, plural, one{file needs} other{files need}} to be initially processed in order to download.",
"file-listing": { "file-listing": {
"file-entry": { "file-entry": {
"file-error": "Re-processing required", "file-error": "Re-processing required",
@ -1017,6 +1017,17 @@
"active": "Active", "active": "Active",
"archive": "Archived" "archive": "Archived"
}, },
"download-dialog": {
"actions": {
"save": "Download"
},
"form": {
"redaction-preview-color": "Redaction preview color",
"redaction-preview-color-placeholder": "#000000"
},
"header": "Download options",
"unapproved-files-warning": "This download contains unapproved file(s)"
},
"download-includes": "Choose what is included at download:", "download-includes": "Choose what is included at download:",
"download-status": { "download-status": {
"queued": "Your download has been queued, you can see all your requested downloads here: <a href='/ui/main/downloads'>My Downloads<a/>." "queued": "Your download has been queued, you can see all your requested downloads here: <a href='/ui/main/downloads'>My Downloads<a/>."
@ -1028,7 +1039,8 @@
"label": "{length} document {length, plural, one{version} other{versions}}", "label": "{length} document {length, plural, one{version} other{versions}}",
"original": "Optimized PDF", "original": "Optimized PDF",
"preview": "Preview PDF", "preview": "Preview PDF",
"redacted": "Redacted PDF" "redacted": "Redacted PDF",
"redacted-only": "Redacted PDF (approved documents only)"
}, },
"downloads-list": { "downloads-list": {
"actions": { "actions": {
@ -1941,9 +1953,13 @@
}, },
"rss-dialog": { "rss-dialog": {
"actions": { "actions": {
"cancel-edit": "Cancel",
"close": "Close", "close": "Close",
"edit": "Edit",
"export-json": "Export JSON", "export-json": "Export JSON",
"export-xml": "Export XML" "export-xml": "Export XML",
"save": "Save",
"undo": "Undo"
}, },
"title": "Structured Component Management" "title": "Structured Component Management"
}, },

View File

@ -1017,6 +1017,17 @@
"active": "", "active": "",
"archive": "" "archive": ""
}, },
"download-dialog": {
"actions": {
"save": ""
},
"form": {
"redaction-preview-color": "",
"redaction-preview-color-placeholder": ""
},
"header": "",
"unapproved-files-warning": ""
},
"download-includes": "Wählen Sie die Dokumente für Ihr Download-Paket aus", "download-includes": "Wählen Sie die Dokumente für Ihr Download-Paket aus",
"download-status": { "download-status": {
"queued": "Ihr Download wurde zur Warteschlange hinzugefügt. Hier finden Sie alle angeforderten Downloads: <a href='/main/downloads'>My Downloads<a/>." "queued": "Ihr Download wurde zur Warteschlange hinzugefügt. Hier finden Sie alle angeforderten Downloads: <a href='/main/downloads'>My Downloads<a/>."
@ -1028,7 +1039,8 @@
"label": "{length} Dokumenten{length, plural, one{version} other{versionen}}", "label": "{length} Dokumenten{length, plural, one{version} other{versionen}}",
"original": "Optimiertes PDF", "original": "Optimiertes PDF",
"preview": "PDF-Vorschau", "preview": "PDF-Vorschau",
"redacted": "geschwärztes PDF" "redacted": "geschwärztes PDF",
"redacted-only": ""
}, },
"downloads-list": { "downloads-list": {
"actions": { "actions": {
@ -1941,9 +1953,13 @@
}, },
"rss-dialog": { "rss-dialog": {
"actions": { "actions": {
"cancel-edit": "",
"close": "", "close": "",
"edit": "",
"export-json": "", "export-json": "",
"export-xml": "" "export-xml": "",
"save": "",
"undo": ""
}, },
"title": "" "title": ""
}, },

View File

@ -849,7 +849,7 @@
} }
}, },
"download-file": "Download", "download-file": "Download",
"download-file-disabled": "You need to be approver in the dossier and the {count, plural, one{file needs} other{files need}} to be approved in order to download.", "download-file-disabled": "You need to be approver in the dossier and the {count, plural, one{file needs} other{files need}} to be initially processed in order to download.",
"file-listing": { "file-listing": {
"file-entry": { "file-entry": {
"file-error": "Re-processing required", "file-error": "Re-processing required",
@ -1017,6 +1017,17 @@
"active": "Active", "active": "Active",
"archive": "Archived" "archive": "Archived"
}, },
"download-dialog": {
"actions": {
"save": "Download"
},
"form": {
"redaction-preview-color": "Redaction preview color",
"redaction-preview-color-placeholder": "#000000"
},
"header": "Download options",
"unapproved-files-warning": "This download contains unapproved file(s)"
},
"download-includes": "Choose what is included at download:", "download-includes": "Choose what is included at download:",
"download-status": { "download-status": {
"queued": "Your download has been queued, you can see all your requested downloads here: <a href='/ui/main/downloads'>My Downloads<a/>." "queued": "Your download has been queued, you can see all your requested downloads here: <a href='/ui/main/downloads'>My Downloads<a/>."
@ -1028,7 +1039,8 @@
"label": "{length} document {length, plural, one{version} other{versions}}", "label": "{length} document {length, plural, one{version} other{versions}}",
"original": "Optimized PDF", "original": "Optimized PDF",
"preview": "Preview PDF", "preview": "Preview PDF",
"redacted": "Redacted PDF" "redacted": "Redacted PDF",
"redacted-only": "Redacted PDF (redacted documents only)"
}, },
"downloads-list": { "downloads-list": {
"actions": { "actions": {
@ -1941,9 +1953,13 @@
}, },
"rss-dialog": { "rss-dialog": {
"actions": { "actions": {
"cancel-edit": "Cancel",
"close": "Close", "close": "Close",
"edit": "Edit",
"export-json": "Export JSON", "export-json": "Export JSON",
"export-xml": "Export XML" "export-xml": "Export XML",
"save": "Save",
"undo": "Undo"
}, },
"title": "Structured Component Management" "title": "Structured Component Management"
}, },

@ -1 +1 @@
Subproject commit 5f9c754abf0fb4fcc0606d811a66c5bbb88d164a Subproject commit b58f1ca2fed8e8ddf2834c0fd15e48d00b6a1329

View File

@ -2,8 +2,12 @@
* Object containing information on which file and report types should be included in the download. * Object containing information on which file and report types should be included in the download.
*/ */
import { List } from '@iqser/common-ui'; import { List } from '@iqser/common-ui';
import { DownloadFileType } from '../shared';
export interface IPrepareDownloadRequest { export interface IPrepareDownloadRequest {
readonly dossierId?: string; readonly dossierId: string;
readonly fileIds?: List; readonly fileIds: List;
readonly reportTemplateIds: List;
readonly downloadFileTypes: List<DownloadFileType>;
readonly redactionPreviewColor: string;
} }

View File

@ -146,7 +146,11 @@ export class File extends Entity<IFile> implements IFile {
this.isUnderApproval = this.workflowStatus === WorkflowFileStatuses.UNDER_APPROVAL; this.isUnderApproval = this.workflowStatus === WorkflowFileStatuses.UNDER_APPROVAL;
this.canBeApproved = !this.hasSuggestions && !this.isProcessing && !this.isError; this.canBeApproved = !this.hasSuggestions && !this.isProcessing && !this.isError;
this.canBeOpened = !this.isError && !this.isUnprocessed && this.numberOfAnalyses > 0; this.canBeOpened = !this.isError && !this.isUnprocessed && this.numberOfAnalyses > 0;
this.canBeOCRed = !this.excluded && !this.lastOCRTime && (this.isNew || this.isUnderReview || this.isUnderApproval); this.canBeOCRed =
!this.excluded &&
!this.lastOCRTime &&
this.numberOfAnalyses !== 0 &&
(this.isNew || this.isUnderReview || this.isUnderApproval);
this.fileAttributes = this.fileAttributes =
file.fileAttributes && file.fileAttributes.attributeIdToValue ? file.fileAttributes : { attributeIdToValue: {} }; file.fileAttributes && file.fileAttributes.attributeIdToValue ? file.fileAttributes : { attributeIdToValue: {} };

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "redaction", "name": "redaction",
"version": "3.813.0", "version": "3.828.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {

View File

@ -1,6 +1,6 @@
{ {
"name": "redaction", "name": "redaction",
"version": "3.813.0", "version": "3.828.0",
"private": true, "private": true,
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {

Binary file not shown.