Merge branch 'master' into VM/RED-2614

This commit is contained in:
Valentin 2022-01-12 10:11:48 +02:00
commit 1831d17270
81 changed files with 1669 additions and 1248 deletions

View File

@ -36,6 +36,9 @@ import { KeycloakService } from 'keycloak-angular';
import { GeneralSettingsService } from '@services/general-settings.service';
import { BreadcrumbsComponent } from '@components/breadcrumbs/breadcrumbs.component';
import { UserPreferenceService } from '@services/user-preference.service';
import * as german from '../assets/i18n/de.json';
import * as english from '../assets/i18n/en.json';
import { UserService } from '@services/user.service';
export function httpLoaderFactory(httpClient: HttpClient): PruningTranslationLoader {
return new PruningTranslationLoader(httpClient, '/assets/i18n/', '.json');
@ -114,7 +117,7 @@ const components = [AppComponent, AuthErrorComponent, NotificationsComponent, Sp
provide: APP_INITIALIZER,
multi: true,
useFactory: configurationInitializer,
deps: [KeycloakService, Title, ConfigService, GeneralSettingsService, LanguageService, UserPreferenceService],
deps: [KeycloakService, Title, ConfigService, GeneralSettingsService, LanguageService, UserService, UserPreferenceService],
},
{
provide: MissingTranslationHandler,
@ -136,6 +139,7 @@ const components = [AppComponent, AuthErrorComponent, NotificationsComponent, Sp
export class AppModule {
constructor(private readonly _router: Router, private readonly _route: ActivatedRoute) {
this._configureKeyCloakRouteHandling();
// this._test();
}
private _configureKeyCloakRouteHandling() {
@ -152,4 +156,60 @@ export class AppModule {
}
});
}
// private _test(){
//
// const flatGerman = flatten(german);
//
//
// const flatEnglish = flatten(english);
//
// const tmfc = new TranslateMessageFormatCompiler();
//
//
//
// for (const key of Object.keys(flatGerman)) {
// try {
// const result = tmfc.compile(flatGerman[key], 'de');
// //console.log(result);
// } catch (e) {
// console.error('ERROR AT: ', flatGerman[key]);
// }
// }
//
// for (const key of Object.keys(flatEnglish)) {
// try {
// const result = tmfc.compile(flatEnglish[key], 'de');
// //console.log(result);
// } catch (e) {
// console.error('ERROR AT: ', flatEnglish[key]);
// }
// }
// console.log('done');
// }
}
//
// function flatten(data: any) {
// const result: any = {};
//
// function recurse(cur: any, prop: any) {
// if (Object(cur) !== cur) {
// result[prop] = cur;
// } else if (Array.isArray(cur)) {
// let l = 0;
// for (let i = 0, l = cur.length; i < l; i++) recurse(cur[i], prop + '[' + i + ']');
// if (l === 0) result[prop] = [];
// } else {
// let isEmpty = true;
// for (const p in cur) {
// isEmpty = false;
// recurse(cur[p], prop ? prop + '.' + p : p);
// }
// if (isEmpty && prop) result[prop] = {};
// }
// }
//
// recurse(data, '');
// return result;
// }

View File

@ -1,9 +1,5 @@
@use 'variables';
.mt-2 {
margin-top: 2px;
}
::ng-deep .notifications-backdrop + .cdk-overlay-connected-position-bounding-box {
right: 0 !important;
@ -20,6 +16,7 @@
.view-all {
cursor: pointer;
&:hover {
color: var(--iqser-primary);
}

View File

@ -8,6 +8,7 @@ import { UserPreferenceService } from '@services/user-preference.service';
export class LanguageService {
constructor(private readonly _translateService: TranslateService, private readonly _userPreferenceService: UserPreferenceService) {
_translateService.addLangs(['en', 'de']);
_translateService.setDefaultLang('en');
}
get currentLanguage() {

View File

@ -14,6 +14,7 @@ export class AnnotationPermissions {
canChangeLegalBasis = true;
canResizeAnnotation = true;
canRecategorizeImage = true;
canForceHint = true;
static forUser(isApprover: boolean, user: User, annotations: AnnotationWrapper | AnnotationWrapper[]) {
if (!isArray(annotations)) {
@ -29,12 +30,13 @@ export class AnnotationPermissions {
permissions.canAcceptSuggestion = isApprover && (annotation.isSuggestion || annotation.isDeclinedSuggestion);
permissions.canRejectSuggestion = isApprover && annotation.isSuggestion;
permissions.canForceHint = annotation.isIgnoredHint;
permissions.canForceRedaction = annotation.isSkipped && !annotation.isFalsePositive;
permissions.canAcceptRecommendation = annotation.isRecommendation;
permissions.canMarkAsFalsePositive = annotation.canBeMarkedAsFalsePositive;
permissions.canRemoveOrSuggestToRemoveOnlyHere = annotation.isRedacted;
permissions.canRemoveOrSuggestToRemoveOnlyHere = annotation.isRedacted || annotation.isHint;
permissions.canRemoveOrSuggestToRemoveFromDictionary =
annotation.isModifyDictionary && (annotation.isRedacted || annotation.isSkipped || annotation.isHint);

View File

@ -12,6 +12,7 @@ export type AnnotationSuperType =
| 'suggestion-remove-dictionary'
| 'suggestion-add'
| 'suggestion-remove'
| 'ignored-hint'
| 'skipped'
| 'redaction'
| 'manual-redaction'
@ -43,6 +44,7 @@ export class AnnotationWrapper {
legalBasisChangeValue?: string;
resizing?: boolean;
rectangle?: boolean;
hintDictionary?: boolean;
section?: string;
manual?: boolean;
@ -87,7 +89,7 @@ export class AnnotationWrapper {
}
get isSuperTypeBasedColor() {
return this.isSkipped || this.isSuggestion || this.isDeclinedSuggestion;
return this.isSkipped || this.isSuggestion || this.isDeclinedSuggestion || this.isIgnoredHint;
}
get isSkipped() {
@ -106,6 +108,20 @@ export class AnnotationWrapper {
return this.recategorizationType || this.typeValue;
}
get topLevelFilter() {
return (
this.superType !== 'hint' &&
this.superType !== 'redaction' &&
this.superType !== 'recommendation' &&
this.superType !== 'ignored-hint' &&
this.superType !== 'skipped'
);
}
get filterKey() {
return this.topLevelFilter ? this.superType : this.superType + this.type;
}
get isManuallySkipped() {
return this.isSkipped && this.manual;
}
@ -113,7 +129,10 @@ export class AnnotationWrapper {
get isFalsePositive() {
return (
this.type?.toLowerCase() === 'false_positive' &&
(this.superType === 'skipped' || this.superType === 'hint' || this.superType === 'redaction')
(this.superType === 'skipped' ||
this.superType === 'hint' ||
this.superType === 'ignored-hint' ||
this.superType === 'redaction')
);
}
@ -133,6 +152,10 @@ export class AnnotationWrapper {
return this.superType === 'hint';
}
get isIgnoredHint() {
return this.superType === 'ignored-hint';
}
get isRedacted() {
return this.superType === 'redaction' || this.superType === 'manual-redaction';
}
@ -242,6 +265,7 @@ export class AnnotationWrapper {
annotationWrapper.hasLegalBasisChanged = redactionLogEntry.hasLegalBasisChanged;
annotationWrapper.hasBeenForced = redactionLogEntry.hasBeenForced;
annotationWrapper.hasBeenRemovedByManualOverride = redactionLogEntry.hasBeenRemovedByManualOverride;
annotationWrapper.hintDictionary = redactionLogEntry.hintDictionary;
this._createContent(annotationWrapper, redactionLogEntry);
this._setSuperType(annotationWrapper, redactionLogEntry);
@ -280,7 +304,7 @@ export class AnnotationWrapper {
if (redactionLogEntryWrapper.status === 'REQUESTED') {
annotationWrapper.superType = 'suggestion-force-redaction';
} else if (redactionLogEntryWrapper.status === 'APPROVED') {
annotationWrapper.superType = 'redaction';
annotationWrapper.superType = redactionLogEntryWrapper.hint ? 'hint' : 'redaction';
} else {
annotationWrapper.superType = 'skipped';
}
@ -408,6 +432,11 @@ export class AnnotationWrapper {
if (!annotationWrapper.superType) {
annotationWrapper.superType = annotationWrapper.redaction ? 'redaction' : annotationWrapper.hint ? 'hint' : 'skipped';
}
if (annotationWrapper.superType === 'skipped') {
if (redactionLogEntryWrapper.hintDictionary) {
annotationWrapper.superType = 'ignored-hint';
}
}
}
private static _createContent(annotationWrapper: AnnotationWrapper, entry: RedactionLogEntryWrapper) {

View File

@ -3,34 +3,34 @@ import { AnnotationWrapper } from './annotation.wrapper';
import { RedactionLogEntryWrapper } from './redaction-log-entry.wrapper';
import * as moment from 'moment';
export class AnnotationData {
visibleAnnotations: AnnotationWrapper[];
allAnnotations: AnnotationWrapper[];
}
export class FileDataModel {
static readonly DELTA_VIEW_TIME = 10 * 60 * 1000; // 10 minutes;
hasChangeLog: boolean;
allAnnotations: AnnotationWrapper[];
constructor(public file: File, public fileData: Blob, public redactionLog: IRedactionLog, public viewedPages?: IViewedPage[]) {}
constructor(
public file: File,
public fileData: Blob,
private _redactionLog: IRedactionLog,
public viewedPages?: IViewedPage[],
private _dictionaryData?: { [p: string]: Dictionary },
private _areDevFeaturesEnabled?: boolean,
) {
this._buildAllAnnotations();
}
getAnnotations(
dictionaryData: { [p: string]: Dictionary },
currentUser: User,
viewMode: ViewMode,
areDevFeaturesEnabled: boolean,
): AnnotationData {
const entries: RedactionLogEntryWrapper[] = this._convertData();
let allAnnotations = entries
.map(entry => AnnotationWrapper.fromData(entry))
.filter(ann => ann.manual || !this.file.excludedPages.includes(ann.pageNumber));
set redactionLog(redactionLog: IRedactionLog) {
this._redactionLog = redactionLog;
this._buildAllAnnotations();
}
if (!areDevFeaturesEnabled) {
allAnnotations = allAnnotations.filter(annotation => !annotation.isFalsePositive);
}
get redactionLog() {
return this._redactionLog;
}
const visibleAnnotations = allAnnotations.filter(annotation => {
getVisibleAnnotations(viewMode: ViewMode) {
return this.allAnnotations.filter(annotation => {
if (viewMode === 'STANDARD') {
return !annotation.isChangeLogRemoved;
} else if (viewMode === 'DELTA') {
@ -39,11 +39,30 @@ export class FileDataModel {
return annotation.isRedacted;
}
});
}
return {
visibleAnnotations: visibleAnnotations,
allAnnotations: allAnnotations,
};
private _buildAllAnnotations() {
const entries: RedactionLogEntryWrapper[] = this._convertData();
const previousAnnotations = this.allAnnotations || [];
this.allAnnotations = entries
.map(entry => AnnotationWrapper.fromData(entry))
.filter(ann => ann.manual || !this.file.excludedPages.includes(ann.pageNumber));
if (!this._areDevFeaturesEnabled) {
this.allAnnotations = this.allAnnotations.filter(annotation => !annotation.isFalsePositive);
}
this._setHiddenPropertyToNewAnnotations(this.allAnnotations, previousAnnotations);
}
private _setHiddenPropertyToNewAnnotations(newAnnotations: AnnotationWrapper[], oldAnnotations: AnnotationWrapper[]) {
newAnnotations.forEach(newAnnotation => {
const oldAnnotation = oldAnnotations.find(a => a.annotationId === newAnnotation.annotationId);
if (oldAnnotation) {
newAnnotation.hidden = oldAnnotation.hidden;
}
});
}
private _convertData(): RedactionLogEntryWrapper[] {
@ -55,6 +74,7 @@ export class FileDataModel {
const redactionLogEntryWrapper: RedactionLogEntryWrapper = {};
Object.assign(redactionLogEntryWrapper, redactionLogEntry);
redactionLogEntryWrapper.type = redactionLogEntry.type;
redactionLogEntryWrapper.hintDictionary = this._dictionaryData[redactionLogEntry.type].hint;
this._isChangeLogEntry(redactionLogEntry, redactionLogEntryWrapper);

View File

@ -11,6 +11,7 @@ export interface RedactionLogEntryWrapper {
startOffset?: number;
type?: string;
rectangle?: boolean;
hintDictionary?: boolean;
color?: Array<number>;
dictionaryEntry?: boolean;

View File

@ -13,7 +13,3 @@
left: 270px;
}
}
.mt-44 {
margin-top: 44px;
}

View File

@ -10,10 +10,6 @@
}
}
.mb-14 {
margin-bottom: 14px;
}
.technical-name {
font-weight: 600;
}

View File

@ -60,7 +60,6 @@
*ngIf="form.get('userId').value !== ALL_USERS"
[user]="form.get('userId').value"
[withName]="true"
size="small"
></redaction-initials-avatar>
<div *ngIf="form.get('userId').value === ALL_USERS" [translate]="ALL_USERS"></div>
</mat-select-trigger>
@ -69,7 +68,6 @@
*ngIf="userId !== ALL_USERS"
[user]="userId"
[withName]="true"
size="small"
></redaction-initials-avatar>
<div *ngIf="userId === ALL_USERS" [translate]="ALL_USERS"></div>
</mat-option>
@ -109,7 +107,7 @@
</div>
<div class="user-column cell">
<redaction-initials-avatar [user]="log.userId" [withName]="true" size="small"></redaction-initials-avatar>
<redaction-initials-avatar [user]="log.userId" [withName]="true"></redaction-initials-avatar>
</div>
<div [translate]="translations[log.category]" class="cell"></div>

View File

@ -17,11 +17,3 @@ form {
font-size: 16px;
opacity: 0.7;
}
.mr-0 {
margin-right: 0;
}
.mr-20 {
margin-right: 20px;
}

View File

@ -84,7 +84,3 @@
}
}
}
.mb-5 {
margin-bottom: 5px;
}

View File

@ -11,4 +11,5 @@ export const defaultColorsTranslations: { [key in DefaultColorType]: string } =
requestAdd: _('default-colors-screen.types.requestAdd'),
requestRemove: _('default-colors-screen.types.requestRemove'),
updatedColor: _('default-colors-screen.types.updatedColor'),
ignoredHintColor: _('default-colors-screen.types.ignoredHintColor'),
};

View File

@ -28,7 +28,7 @@
<iqser-circle-button
(action)="hardDelete()"
*ngIf="listingService.areSomeSelected$ | async"
*ngIf="canDeleteSelected$ | async"
[tooltip]="'edit-dossier-dialog.deleted-documents.bulk.delete' | translate"
[type]="circleButtonTypes.dark"
icon="iqser:trash"
@ -48,6 +48,24 @@
</div>
</div>
<div class="cell user-column">
<redaction-initials-avatar [user]="file.assignee" [withName]="true"></redaction-initials-avatar>
</div>
<div class="cell">
<iqser-status-bar
[configs]="[
{
color: file.workflowStatus,
label: fileStatusTranslations[file.workflowStatus] | translate,
length: 1,
cssClass: 'all-caps-label'
}
]"
[small]="true"
></iqser-status-bar>
</div>
<div class="cell">
<span class="small-label">{{ file.softDeleted | date: 'exactDate' }}</span>
</div>
@ -65,6 +83,7 @@
<iqser-circle-button
(action)="hardDelete([file])"
*ngIf="file.canHardDelete"
[tooltip]="'edit-dossier-dialog.deleted-documents.action.delete' | translate"
[type]="circleButtonTypes.dark"
icon="iqser:trash"

View File

@ -1,6 +1,6 @@
import { Component, forwardRef, Injector, Input, OnInit } from '@angular/core';
import { EditDossierSaveResult, EditDossierSectionInterface } from '../edit-dossier-section.interface';
import { Dossier, IFile } from '@red/domain';
import { Dossier, File, IFile } from '@red/domain';
import {
CircleButtonTypes,
ConfirmationDialogInput,
@ -21,8 +21,12 @@ import { distinctUntilChanged, map } from 'rxjs/operators';
import { DossiersDialogService } from '../../../services/dossiers-dialog.service';
import { FilesService } from '@services/entity-services/files.service';
import { FileManagementService } from '@services/entity-services/file-management.service';
import { workflowFileStatusTranslations } from '../../../translations/file-status-translations';
import { PermissionsService } from '@services/permissions.service';
import { UserService } from '@services/user.service';
interface FileListItem extends IFile, IListable {
readonly canHardDelete: boolean;
readonly canRestore: boolean;
readonly restoreDate: string;
}
@ -37,14 +41,18 @@ interface FileListItem extends IFile, IListable {
],
})
export class EditDossierDeletedDocumentsComponent extends ListingComponent<FileListItem> implements EditDossierSectionInterface, OnInit {
readonly fileStatusTranslations = workflowFileStatusTranslations;
@Input() dossier: Dossier;
readonly changed = false;
readonly valid = false;
readonly canRestoreSelected$ = this._canRestoreSelected$;
readonly canDeleteSelected$ = this._canDeleteSelected$;
disabled: boolean;
readonly tableColumnConfigs: TableColumnConfig<FileListItem>[] = [
{ label: _('edit-dossier-dialog.deleted-documents.table-col-names.name'), width: '3fr' },
{ label: _('edit-dossier-dialog.deleted-documents.table-col-names.pages') },
{ label: _('edit-dossier-dialog.deleted-documents.table-col-names.assignee'), class: 'user-column' },
{ label: _('edit-dossier-dialog.deleted-documents.table-col-names.status') },
{ label: _('edit-dossier-dialog.deleted-documents.table-col-names.deleted-on'), sortByKey: 'softDeleted', width: '2fr' },
{ label: _('edit-dossier-dialog.deleted-documents.table-col-names.time-to-restore'), sortByKey: 'softDeleted', width: '2fr' },
];
@ -59,6 +67,8 @@ export class EditDossierDeletedDocumentsComponent extends ListingComponent<FileL
private readonly _loadingService: LoadingService,
private readonly _configService: ConfigService,
private readonly _dialogService: DossiersDialogService,
private readonly _permissionsService: PermissionsService,
private readonly _userService: UserService,
) {
super(_injector);
}
@ -70,6 +80,13 @@ export class EditDossierDeletedDocumentsComponent extends ListingComponent<FileL
);
}
private get _canDeleteSelected$(): Observable<boolean> {
return this.listingService.selectedEntities$.pipe(
map(entities => entities.length && !entities.find(file => !file.canHardDelete)),
distinctUntilChanged(),
);
}
hardDelete(files = this.listingService.selected) {
const data = new ConfirmationDialogInput({
title: _('confirmation-dialog.permanently-delete-file.title'),
@ -133,21 +150,26 @@ export class EditDossierDeletedDocumentsComponent extends ListingComponent<FileL
return files.map(file => this._toListItem(file));
}
private _toListItem(file: IFile): FileListItem {
const restoreDate = this._getRestoreDate(file.softDeleted);
private _toListItem(_file: IFile): FileListItem {
const file = new File(_file, this._userService.getNameForId(_file.assignee));
const restoreDate = this._getRestoreDate(_file.softDeleted);
return {
id: file.fileId,
...file,
restoreDate,
searchKey: file.filename,
canRestore: this._canRestoreFile(restoreDate),
canRestore: this._canRestore(file, restoreDate),
canHardDelete: this._canPerformActions(file),
};
}
private _canRestoreFile(restoreDate: string): boolean {
const { daysLeft, hoursLeft, minutesLeft } = getLeftDateTime(restoreDate);
private _canPerformActions(file: File): boolean {
return this._userService.currentUser.isManager || this._permissionsService.canDeleteFile(file);
}
return daysLeft >= 0 && hoursLeft >= 0 && minutesLeft >= 0;
private _canRestore(file: File, restoreDate: string): boolean {
const { daysLeft, hoursLeft, minutesLeft } = getLeftDateTime(restoreDate);
return this._canPerformActions(file) && daysLeft + hoursLeft + minutesLeft > 0;
}
private _getRestoreDate(softDeletedTime: string): string {

View File

@ -5,6 +5,7 @@ import { EditDossierSaveResult, EditDossierSectionInterface } from '../edit-doss
import { downloadTypesTranslations } from '../../../../../translations/download-types-translations';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { ReportTemplateService } from '@services/report-template.service';
import { PermissionsService } from '@services/permissions.service';
@Component({
selector: 'redaction-edit-dossier-download-package',
@ -25,6 +26,7 @@ export class EditDossierDownloadPackageComponent implements OnInit, EditDossierS
private readonly _dossiersService: DossiersService,
private readonly _reportTemplateController: ReportTemplateService,
private readonly _formBuilder: FormBuilder,
private readonly _permissionsService: PermissionsService,
) {}
get reportTypesLength() {
@ -71,6 +73,9 @@ export class EditDossierDownloadPackageComponent implements OnInit, EditDossierS
(await this._reportTemplateController.getAvailableReportTemplates(this.dossier.dossierTemplateId).toPromise()) || [];
this.form = this._getForm();
if (!this._permissionsService.canEditDossier()) {
this.form.disable();
}
}
async save(): EditDossierSaveResult {

View File

@ -15,6 +15,7 @@ import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { EditDossierTeamComponent } from './edit-dossier-team/edit-dossier-team.component';
import { BaseDialogComponent } from '@shared/dialog/base-dialog.component';
import { PermissionsService } from '@services/permissions.service';
type Section = 'dossierInfo' | 'downloadPackage' | 'dossierDictionary' | 'members' | 'dossierAttributes' | 'deletedDocuments';
@ -41,6 +42,7 @@ export class EditDossierDialogComponent extends BaseDialogComponent {
private readonly _dossiersService: DossiersService,
private readonly _changeRef: ChangeDetectorRef,
private readonly _loadingService: LoadingService,
private readonly _permissionsService: PermissionsService,
protected readonly _injector: Injector,
protected readonly _dialogRef: MatDialogRef<EditDossierDialogComponent>,
@Inject(MAT_DIALOG_DATA)
@ -111,7 +113,7 @@ export class EditDossierDialogComponent extends BaseDialogComponent {
}
get showActionButtons(): boolean {
return !['deletedDocuments'].includes(this.activeNav);
return !['deletedDocuments'].includes(this.activeNav) && this._permissionsService.canEditDossier();
}
get changed(): boolean {

View File

@ -13,7 +13,7 @@
<redaction-team-members
(remove)="toggleSelected($event)"
[canAdd]="false"
[canRemove]="true"
[canRemove]="!disabled"
[dossierId]="dossier.dossierId"
[largeSpacing]="true"
[memberIds]="selectedApproversList"
@ -21,13 +21,11 @@
[unremovableMembers]="[selectedOwnerId]"
></redaction-team-members>
<pre *ngIf="selectedApproversList.length === 0" [innerHTML]="'assign-dossier-owner.dialog.no-approvers' | translate" class="info"></pre>
<div class="all-caps-label mt-16" translate="assign-dossier-owner.dialog.reviewers"></div>
<redaction-team-members
(remove)="toggleSelected($event)"
[canAdd]="false"
[canRemove]="true"
[canRemove]="!disabled"
[dossierId]="dossier.dossierId"
[largeSpacing]="true"
[memberIds]="selectedReviewers$ | async"
@ -35,34 +33,35 @@
[unremovableMembers]="[selectedOwnerId]"
></redaction-team-members>
<pre
*ngIf="(selectedReviewers$ | async).length === 0"
[innerHTML]="'assign-dossier-owner.dialog.no-reviewers' | translate"
class="info"
></pre>
<ng-container *ngIf="(selectedReviewers$ | async).length === 0">
<div class="info mt-4">{{ 'assign-dossier-owner.dialog.no-reviewers' | translate }}</div>
<div *ngIf="!disabled" class="info">{{ 'assign-dossier-owner.dialog.select-below' | translate }}</div>
</ng-container>
<iqser-input-with-action
(valueChange)="setMembersSelectOptions($event)"
[(value)]="searchQuery"
[placeholder]="'assign-dossier-owner.dialog.search' | translate"
[width]="560"
class="search-container"
></iqser-input-with-action>
<ng-container *ngIf="!disabled">
<iqser-input-with-action
(valueChange)="setMembersSelectOptions($event)"
[(value)]="searchQuery"
[placeholder]="'assign-dossier-owner.dialog.search' | translate"
[width]="560"
class="search-container"
></iqser-input-with-action>
<div class="members-list">
<div
(click)="!isOwner(userId) && toggleSelected(userId)"
*ngFor="let userId of membersSelectOptions"
[class.selected]="isMemberSelected(userId)"
>
<redaction-initials-avatar [user]="userId" [withName]="true" size="large"></redaction-initials-avatar>
<div class="actions">
<div (click)="toggleApprover(userId, $event)" *ngIf="!isOwner(userId)" class="make-approver">
<iqser-round-checkbox [active]="isApprover(userId)" class="mr-8"></iqser-round-checkbox>
<span translate="assign-dossier-owner.dialog.make-approver"></span>
<div class="members-list">
<div
(click)="!isOwner(userId) && toggleSelected(userId)"
*ngFor="let userId of membersSelectOptions"
[class.selected]="isMemberSelected(userId)"
>
<redaction-initials-avatar [user]="userId" [withName]="true" size="large"></redaction-initials-avatar>
<div class="actions">
<div (click)="toggleApprover(userId, $event)" *ngIf="!isOwner(userId)" class="make-approver">
<iqser-round-checkbox [active]="isApprover(userId)" class="mr-8"></iqser-round-checkbox>
<span translate="assign-dossier-owner.dialog.make-approver"></span>
</div>
<mat-icon *ngIf="!isOwner(userId)" svgIcon="iqser:check"></mat-icon>
</div>
<mat-icon *ngIf="!isOwner(userId)" svgIcon="iqser:check"></mat-icon>
</div>
</div>
</div>
</ng-container>
</form>

View File

@ -60,7 +60,3 @@ redaction-team-members {
}
}
}
.info {
margin-top: 4px;
}

View File

@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit } from '@angular/core';
import { UserService } from '@services/user.service';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { FormBuilder, FormGroup } from '@angular/forms';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { Dossier, IDossierRequest } from '@red/domain';
import { AutoUnsubscribe } from '@iqser/common-ui';
@ -146,7 +146,10 @@ export class EditDossierTeamComponent extends AutoUnsubscribe implements EditDos
private _loadData() {
this.form = this._formBuilder.group({
owner: [this.dossier?.ownerId, Validators.required],
owner: {
value: this.dossier?.ownerId,
disabled: this.disabled,
},
approvers: [[...this.dossier?.approverIds]],
members: [[...this.dossier?.memberIds]],
});

View File

@ -50,7 +50,13 @@
</div>
<div class="due-date">
<mat-checkbox (change)="hasDueDate = !hasDueDate" [checked]="hasDueDate" class="filter-menu-checkbox" color="primary">
<mat-checkbox
(change)="hasDueDate = !hasDueDate"
[checked]="hasDueDate"
[disabled]="!permissionsService.canEditDossier()"
class="filter-menu-checkbox"
color="primary"
>
{{ 'edit-dossier-dialog.general-info.form.due-date' | translate }}
</mat-checkbox>

View File

@ -68,6 +68,9 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
ngOnInit() {
this._filterInvalidDossierTemplates();
this.form = this._getForm();
if (!this.permissionsService.canEditDossier()) {
this.form.disable();
}
this.hasDueDate = !!this.dossier.dueDate;
}

View File

@ -1,9 +1,10 @@
<section class="dialog">
<form (submit)="handleForceRedaction()" [formGroup]="redactionForm">
<div class="dialog-header heading-l" translate="manual-annotation.dialog.header.force"></div>
<form (submit)="handleForceAnnotation()" [formGroup]="redactionForm">
<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>
<div class="dialog-content">
<div class="iqser-input-group required w-400">
<div class="iqser-input-group required w-400" *ngIf="!isHintDialog">
<label translate="manual-annotation.dialog.content.reason"></label>
<mat-select
[placeholder]="'manual-annotation.dialog.content.reason-placeholder' | translate"
@ -16,14 +17,14 @@
</mat-select>
</div>
<div class="iqser-input-group w-400">
<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" />
</div>
<div [class.required]="!isDocumentAdmin" class="iqser-input-group w-300">
<label translate="manual-annotation.dialog.content.comment"></label>
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4"></textarea>
</div>
</div>

View File

@ -17,11 +17,11 @@ export interface LegalBasisOption {
}
@Component({
selector: 'redaction-force-redaction-dialog',
templateUrl: './force-redaction-dialog.component.html',
styleUrls: ['./force-redaction-dialog.component.scss'],
selector: 'redaction-force-annotation-dialog',
templateUrl: './force-annotation-dialog.component.html',
styleUrls: ['./force-annotation-dialog.component.scss'],
})
export class ForceRedactionDialogComponent implements OnInit {
export class ForceAnnotationDialogComponent implements OnInit {
redactionForm: FormGroup;
isDocumentAdmin: boolean;
legalOptions: LegalBasisOption[] = [];
@ -35,17 +35,21 @@ export class ForceRedactionDialogComponent implements OnInit {
private readonly _justificationsService: JustificationsService,
private readonly _manualAnnotationService: ManualAnnotationService,
private readonly _permissionsService: PermissionsService,
public dialogRef: MatDialogRef<ForceRedactionDialogComponent>,
@Inject(MAT_DIALOG_DATA) private readonly _data: { readonly dossier: Dossier },
public dialogRef: MatDialogRef<ForceAnnotationDialogComponent>,
@Inject(MAT_DIALOG_DATA) private readonly _data: { readonly dossier: Dossier; readonly hint: boolean },
) {
this.redactionForm = this._getForm();
}
get isHintDialog() {
return this._data.hint;
}
private _getForm(): FormGroup {
this.isDocumentAdmin = this._permissionsService.isApprover(this._data.dossier);
return this._formBuilder.group({
reason: [null, Validators.required],
reason: this._data.hint ? ['Forced Hint'] : [null, Validators.required],
comment: this.isDocumentAdmin ? [null] : [null, Validators.required],
});
}
@ -62,7 +66,7 @@ export class ForceRedactionDialogComponent implements OnInit {
this.legalOptions.sort((a, b) => a.label.localeCompare(b.label));
}
handleForceRedaction() {
handleForceAnnotation() {
this.dialogRef.close(this._createForceRedactionRequest());
}

View File

@ -4,7 +4,7 @@
(data.removeFromDictionary
? 'remove-annotations-dialog.remove-from-dictionary.title'
: 'remove-annotations-dialog.remove-only-here.title'
) | translate
) | translate: { hint: data.hint }
}}
</div>
<form (submit)="confirm()" [formGroup]="redactionForm">
@ -13,7 +13,7 @@
(data.removeFromDictionary
? 'remove-annotations-dialog.remove-from-dictionary.question'
: 'remove-annotations-dialog.remove-only-here.question'
) | translate
) | translate: { hint: data.hint }
}}
<div *ngIf="data.removeFromDictionary" class="content-wrapper">

View File

@ -10,6 +10,7 @@ import { Dossier } from '@red/domain';
export interface RemoveAnnotationsDialogInput {
annotationsToRemove: AnnotationWrapper[];
removeFromDictionary: boolean;
hint: boolean;
dossier: Dossier;
}

View File

@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common';
import { AddDossierDialogComponent } from './dialogs/add-dossier-dialog/add-dossier-dialog.component';
import { AssignReviewerApproverDialogComponent } from './dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component';
import { ManualAnnotationDialogComponent } from './dialogs/manual-redaction-dialog/manual-annotation-dialog.component';
import { ForceRedactionDialogComponent } from './dialogs/force-redaction-dialog/force-redaction-dialog.component';
import { ForceAnnotationDialogComponent } from './dialogs/force-redaction-dialog/force-annotation-dialog.component';
import { RemoveAnnotationsDialogComponent } from './dialogs/remove-annotations-dialog/remove-annotations-dialog.component';
import { DocumentInfoDialogComponent } from './dialogs/document-info-dialog/document-info-dialog.component';
import { SharedModule } from '@shared/shared.module';
@ -35,7 +35,7 @@ const dialogs = [
AddDossierDialogComponent,
EditDossierDialogComponent,
ManualAnnotationDialogComponent,
ForceRedactionDialogComponent,
ForceAnnotationDialogComponent,
RemoveAnnotationsDialogComponent,
ResizeAnnotationDialogComponent,
DocumentInfoDialogComponent,

View File

@ -54,7 +54,3 @@ redaction-file-workload {
redaction-file-actions:not(.keep-visible) {
display: none;
}
.mt-4 {
margin-top: 4px;
}

View File

@ -9,10 +9,10 @@
>
<iqser-circle-button
(action)="openEditDossierDialog($event, dossier.dossierId)"
*ngIf="currentUser.isManager"
[tooltip]="'dossier-listing.edit.action' | translate"
*ngIf="currentUser.isUser"
[tooltip]="(currentUser.isManager ? 'dossier-listing.edit.action' : 'dossier-listing.dossier-info.action') | translate"
[type]="circleButtonTypes.dark"
icon="iqser:edit"
[icon]="currentUser.isManager ? 'iqser:edit' : 'iqser:dossier-info'"
iqserHelpMode="edit-dossier-from-list"
></iqser-circle-button>

View File

@ -88,7 +88,7 @@
></iqser-circle-button>
<iqser-circle-button
(action)="annotationActionsService.forceRedaction($event, annotations, file, annotationsChanged)"
(action)="annotationActionsService.forceAnnotation($event, annotations, file, annotationsChanged, false)"
*ngIf="annotationPermissions.canForceRedaction"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.force-redaction.label' | translate"
@ -96,6 +96,15 @@
icon="red:thumb-up"
></iqser-circle-button>
<iqser-circle-button
(action)="annotationActionsService.forceAnnotation($event, annotations, file, annotationsChanged, true)"
*ngIf="annotationPermissions.canForceHint"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.force-hint.label' | translate"
[type]="buttonType"
icon="red:thumb-up"
></iqser-circle-button>
<iqser-circle-button
(action)="hideAnnotation($event)"
*ngIf="isImage && isVisible"
@ -115,7 +124,7 @@
></iqser-circle-button>
<iqser-circle-button
(action)="suggestRemoveAnnotations($event, true)"
(action)="removeOrSuggestRemoveAnnotation($event, true)"
*ngIf="annotationPermissions.canRemoveOrSuggestToRemoveFromDictionary"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.remove-annotation.remove-from-dict' | translate"
@ -135,7 +144,7 @@
></iqser-circle-button>
<iqser-circle-button
(action)="suggestRemoveAnnotations($event, false)"
(action)="removeOrSuggestRemoveAnnotation($event, false)"
*ngIf="annotationPermissions.canRemoveOrSuggestToRemoveOnlyHere"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.remove-annotation.only-here' | translate"

View File

@ -77,9 +77,15 @@ export class AnnotationActionsComponent implements OnChanges {
this._setPermissions();
}
suggestRemoveAnnotations($event, removeFromDict: boolean) {
removeOrSuggestRemoveAnnotation($event, removeFromDict: boolean) {
$event.stopPropagation();
this.annotationActionsService.suggestRemoveAnnotation($event, this.annotations, this.file, removeFromDict, this.annotationsChanged);
this.annotationActionsService.removeOrSuggestRemoveAnnotation(
$event,
this.annotations,
this.file,
removeFromDict,
this.annotationsChanged,
);
}
markAsFalsePositive($event) {

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnDestroy, Output } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnDestroy, Output } from '@angular/core';
import { PermissionsService } from '@services/permissions.service';
import { ConfigService } from '@services/config.service';
import { DossiersService } from '@services/entity-services/dossiers.service';
@ -31,6 +31,7 @@ export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy
private readonly _filesMapService: FilesMapService,
private readonly _dossiersService: DossiersService,
private readonly _configService: ConfigService,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _permissionService: PermissionsService,
) {
super();
@ -41,12 +42,16 @@ 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();
}
ngOnChanges() {

View File

@ -583,7 +583,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
try {
this.instance.UI.enableTools(['AnnotationCreateRectangle']);
} catch (e) {
console.log(e);
// happens
}
this.instance.UI.enableElements(elements);

View File

@ -33,7 +33,7 @@ export class TypeAnnotationIconComponent implements OnChanges {
this.type =
this.annotation.isSuggestion || this.annotation.isDeclinedSuggestion
? 'rhombus'
: this.annotation.isHint
: this.annotation.isHint || this.annotation.isIgnoredHint
? 'circle'
: this.annotation.isRecommendation
? 'hexagon'

View File

@ -26,12 +26,7 @@
<div class="vertical-line"></div>
<redaction-file-actions
#fileActions
(ocredFile)="ocredFile()"
[file]="file"
type="file-preview"
></redaction-file-actions>
<redaction-file-actions [file]="file" type="file-preview"></redaction-file-actions>
<iqser-circle-button
(action)="toggleFullScreen()"
@ -77,7 +72,7 @@
(pageChanged)="viewerPageChanged($event)"
(viewerReady)="viewerReady($event)"
*ngIf="displayPdfViewer"
[annotations]="annotations"
[annotations]="visibleAnnotations"
[canPerformActions]="canPerformAnnotationActions$ | async"
[class.hidden]="!ready"
[dossier]="dossier"
@ -112,7 +107,7 @@
[(shouldDeselectAnnotationsOnPageChange)]="shouldDeselectAnnotationsOnPageChange"
[activeViewerPage]="activeViewerPage"
[annotationActionsTemplate]="annotationActionsTemplate"
[annotations]="annotations"
[annotations]="visibleAnnotations"
[dialogRef]="dialogRef"
[file]="file"
[hideSkipped]="hideSkipped"
@ -143,7 +138,7 @@
<ng-container *ngIf="!filter.topLevelFilter">
<redaction-dictionary-annotation-icon [dictionaryKey]="filter.id" [dossierTemplateId]="dossier.dossierTemplateId">
</redaction-dictionary-annotation-icon>
{{ filter.id | humanize: false }}
{{ filter.label | humanize: false }}
</ng-container>
</ng-template>
</ng-container>

View File

@ -53,10 +53,6 @@
margin-right: 8px;
}
.ml-2 {
margin-left: 2px;
}
.analysis-progress {
padding: 12px 20px;
max-width: 400px;

View File

@ -1,6 +1,5 @@
import { ChangeDetectorRef, Component, HostListener, NgZone, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { ActivatedRoute, ActivatedRouteSnapshot, NavigationExtras, Router } from '@angular/router';
import { AppStateService } from '@state/app-state.service';
import { Core, WebViewerInstance } from '@pdftron/webviewer';
import { PdfViewerComponent } from './components/pdf-viewer/pdf-viewer.component';
import {
@ -19,14 +18,13 @@ 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 { AnnotationData, FileDataModel } from '@models/file/file-data.model';
import { FileDataModel } from '@models/file/file-data.model';
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';
import { combineLatest, Observable, timer } from 'rxjs';
import { UserPreferenceService } from '@services/user-preference.service';
import { UserService } from '@services/user.service';
import { PdfViewerDataService } from '../../services/pdf-viewer-data.service';
import { download } from '@utils/file-download-utils';
import { FileWorkloadComponent } from './components/file-workload/file-workload.component';
@ -70,7 +68,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
fullScreen = false;
shouldDeselectAnnotationsOnPageChange = true;
fileData: FileDataModel;
annotationData: AnnotationData;
selectedAnnotations: AnnotationWrapper[] = [];
hideSkipped = false;
displayPdfViewer = false;
@ -84,7 +81,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
ready = false;
private _instance: WebViewerInstance;
private _lastPage: string;
private _reloadFileOnReanalysis = false;
@ViewChild('fileWorkloadComponent') private readonly _workloadComponent: FileWorkloadComponent;
@ViewChild('annotationFilterTemplate', {
read: TemplateRef,
@ -95,8 +91,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
constructor(
readonly permissionsService: PermissionsService,
readonly userPreferenceService: UserPreferenceService,
private readonly _appStateService: AppStateService,
private readonly _userService: UserService,
private readonly _watermarkService: WatermarkService,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _activatedRoute: ActivatedRoute,
@ -138,8 +132,12 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
});
}
get annotations(): AnnotationWrapper[] {
return this.annotationData ? this.annotationData.visibleAnnotations : [];
get visibleAnnotations(): AnnotationWrapper[] {
return this.fileData ? this.fileData.getVisibleAnnotations(this.viewModeService.viewMode) : [];
}
get allAnnotations(): AnnotationWrapper[] {
return this.fileData ? this.fileData.allAnnotations : [];
}
get activeViewer(): WebViewerInstance {
@ -167,7 +165,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
async updateViewMode(): Promise<void> {
const ocrAnnotationIds = this.annotationData.allAnnotations.filter(a => a.isOCR).map(a => a.id);
const ocrAnnotationIds = this.fileData.allAnnotations.filter(a => a.isOCR).map(a => a.id);
const annotations = this._getAnnotations(a => a.getCustomData('redact-manager'));
const redactions = annotations.filter(a => a.getCustomData('redaction'));
@ -249,19 +247,8 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
console.log(`[REDACTION] Delete previous annotations time: ${new Date().getTime() - startTime} ms`);
}
const processStartTime = new Date().getTime();
const dossier = this._dossiersService.find(this.dossierId);
const newAnnotationsData = this.fileData.getAnnotations(
this._appStateService.dictionaryData[dossier.dossierTemplateId],
this._userService.currentUser,
this.viewModeService.viewMode,
this.userPreferenceService.areDevFeaturesEnabled,
);
if (this.annotationData) {
this._setHiddenPropertyToNewAnnotations(newAnnotationsData.visibleAnnotations, this.annotationData.visibleAnnotations);
this._setHiddenPropertyToNewAnnotations(newAnnotationsData.allAnnotations, this.annotationData.allAnnotations);
}
this.annotationData = newAnnotationsData;
const annotationFilters = this._annotationProcessingService.getAnnotationFilter(this.annotations);
const annotationFilters = this._annotationProcessingService.getAnnotationFilter(this.visibleAnnotations);
const primaryFilters = this._filterService.getGroup('primaryFilters')?.filters;
this._filterService.addFilterGroup({
slug: 'primaryFilters',
@ -282,7 +269,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
handleAnnotationSelected(annotationIds: string[]) {
// TODO: use includes() here
this.selectedAnnotations = annotationIds
.map(id => this.annotations.find(annotationWrapper => annotationWrapper.id === id))
.map(id => this.visibleAnnotations.find(annotationWrapper => annotationWrapper.id === id))
.filter(ann => ann !== undefined);
if (this.selectedAnnotations.length > 1) {
this.multiSelectService.activate();
@ -429,10 +416,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
await this._reloadAnnotationsForPage(annotation?.pageNumber || this.activeViewerPage);
}
ocredFile(): void {
this._reloadFileOnReanalysis = true;
}
closeFullScreen() {
if (!!document.fullscreenElement && document.exitFullscreen) {
document.exitFullscreen().then();
@ -478,19 +461,10 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
private async _reloadFile(file: File): Promise<void> {
await this._loadFileData(file, true);
await this._loadFileData(file);
await this._stampPDF();
}
private _setHiddenPropertyToNewAnnotations(newAnnotations: AnnotationWrapper[], oldAnnotations: AnnotationWrapper[]) {
newAnnotations.forEach(newAnnotation => {
const oldAnnotation = oldAnnotations.find(a => a.annotationId === newAnnotation.annotationId);
if (oldAnnotation) {
newAnnotation.hidden = oldAnnotation.hidden;
}
});
}
private async _stampPDF() {
if (!this._instance) {
return;
@ -552,36 +526,26 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
this.addSubscription = this._filesMapService.fileReanalysed$
.pipe(filter(file => file.fileId === this.fileId))
.subscribe(async file => {
await this._loadFileData(file, !this._reloadFileOnReanalysis);
this._reloadFileOnReanalysis = false;
await this._reloadAnnotations();
if (file.lastProcessed !== this.fileData?.file.lastProcessed) {
await this._loadFileData(file);
await this._reloadAnnotations();
}
this._loadingService.stop();
});
}
private async _loadFileData(file: File, performUpdate = false): Promise<void | boolean> {
private async _loadFileData(file: File): Promise<void | boolean> {
if (!file || file.isError) {
return this._router.navigate([this._dossiersService.find(this.dossierId).routerLink]);
}
const fileData = await this._fileDownloadService.loadDataFor(file).toPromise();
const fileData = await this._fileDownloadService.loadDataFor(file, this.fileData).toPromise();
if (file.isPending) {
return;
}
if (performUpdate && !!this.fileData) {
this.fileData.redactionLog = fileData.redactionLog;
this.fileData.viewedPages = fileData.viewedPages;
const excludedOrIncludedPages = new Set(diff(this.fileData.file.excludedPages, file.excludedPages));
const currentPageAnnotations = this.annotations.filter(a => excludedOrIncludedPages.has(a.pageNumber));
this.fileData.file = file;
if (excludedOrIncludedPages?.size) {
await this._cleanupAndRedrawAnnotations(currentPageAnnotations, a => excludedOrIncludedPages.has(a.pageNumber));
}
} else {
this.fileData = fileData;
}
this.fileData = fileData;
}
@Debounce(0)
@ -592,19 +556,30 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
private async _reloadAnnotations() {
this.fileData.redactionLog = await this._fileDownloadService.loadRedactionLogFor(this.dossierId, this.fileId).toPromise();
await this._cleanupAndRedrawAnnotations(this.annotations);
this._instance.Core.annotationManager.deleteAnnotations(this._instance.Core.annotationManager.getAnnotationsList(), {
imported: true,
force: true,
});
await this._cleanupAndRedrawAnnotations();
}
private async _reloadAnnotationsForPage(page: number) {
const currentPageAnnotations = this.annotations.filter(a => a.pageNumber === page);
this.fileData.file = await this._filesService.reload(this.dossierId, this.fileId).toPromise();
// if this action triggered a re-processing,
// we don't want to redraw for this page since they will get redrawn as soon as processing ends;
if (this.fileData.file.isProcessing) {
return;
}
const currentPageAnnotations = this.visibleAnnotations.filter(a => a.pageNumber === page);
this.fileData.redactionLog = await this._fileDownloadService.loadRedactionLogFor(this.dossierId, this.fileId).toPromise();
await this._cleanupAndRedrawAnnotations(currentPageAnnotations, annotation => annotation.pageNumber === page);
}
private async _cleanupAndRedrawAnnotations(
annotationsToDelete: AnnotationWrapper[],
annotationsToDelete?: AnnotationWrapper[],
newAnnotationsFilter?: (annotation: AnnotationWrapper) => boolean,
) {
this.rebuildFilters();
@ -614,7 +589,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
annotationsToDelete?.forEach(annotation => {
this._findAndDeleteAnnotation(annotation.id);
});
const newAnnotations = newAnnotationsFilter ? this.annotations.filter(newAnnotationsFilter) : this.annotations;
const newAnnotations = newAnnotationsFilter ? this.allAnnotations.filter(newAnnotationsFilter) : this.allAnnotations;
this._handleDeltaAnnotationFilters(annotationsToDelete ?? [], newAnnotations);
await this._redrawAnnotations(newAnnotations);
console.log(
@ -623,7 +598,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
}
private _redrawAnnotations(annotations = this.annotationData.allAnnotations) {
private _redrawAnnotations(annotations = this.allAnnotations) {
return this._annotationDrawService.drawAnnotations(
this._instance,
annotations,

View File

@ -59,9 +59,15 @@ export class AnnotationActionsService {
});
}
forceRedaction($event: MouseEvent, annotations: AnnotationWrapper[], file: File, annotationsChanged: EventEmitter<AnnotationWrapper>) {
const data = { dossier: this._dossier(file) };
this._dialogService.openDialog('forceRedaction', $event, data, (request: ILegalBasisChangeRequest) => {
forceAnnotation(
$event: MouseEvent,
annotations: AnnotationWrapper[],
file: File,
annotationsChanged: EventEmitter<AnnotationWrapper>,
hint: boolean = false,
) {
const data = { dossier: this._dossier(file), hint };
this._dialogService.openDialog('forceAnnotation', $event, data, (request: ILegalBasisChangeRequest) => {
annotations.forEach(annotation => {
this._processObsAndEmit(
this._manualAnnotationService.force(
@ -107,14 +113,19 @@ export class AnnotationActionsService {
);
}
suggestRemoveAnnotation(
removeOrSuggestRemoveAnnotation(
$event: MouseEvent,
annotations: AnnotationWrapper[],
file: File,
removeFromDictionary: boolean,
annotationsChanged: EventEmitter<AnnotationWrapper>,
) {
const data = { annotationsToRemove: annotations, removeFromDictionary, dossier: this._dossier(file) };
const data = {
annotationsToRemove: annotations,
removeFromDictionary,
dossier: this._dossier(file),
hint: annotations[0].hintDictionary,
};
this._dialogService.openDialog('removeAnnotations', $event, data, (result: { comment: string }) => {
annotations.forEach(annotation => {
this._processObsAndEmit(
@ -279,7 +290,7 @@ export class AnnotationActionsService {
title: this._translateService.instant('annotation-actions.remove-annotation.remove-from-dict'),
onClick: () => {
this._ngZone.run(() => {
this.suggestRemoveAnnotation(null, annotations, file, true, annotationsChanged);
this.removeOrSuggestRemoveAnnotation(null, annotations, file, true, annotationsChanged);
});
},
});
@ -349,7 +360,21 @@ export class AnnotationActionsService {
title: this._translateService.instant('annotation-actions.force-redaction.label'),
onClick: () => {
this._ngZone.run(() => {
this.forceRedaction(null, annotations, file, annotationsChanged);
this.forceAnnotation(null, annotations, file, annotationsChanged);
});
},
});
}
const canForceHint = annotationPermissions.reduce((acc, next) => acc && next.permissions.canForceHint, true);
if (canForceHint) {
availableActions.push({
type: 'actionButton',
img: this._convertPath('/assets/icons/general/thumb-up.svg'),
title: this._translateService.instant('annotation-actions.force-hint.label'),
onClick: () => {
this._ngZone.run(() => {
this.forceAnnotation(null, annotations, file, annotationsChanged, true);
});
},
});
@ -380,7 +405,7 @@ export class AnnotationActionsService {
title: this._translateService.instant('annotation-actions.remove-annotation.only-here'),
onClick: () => {
this._ngZone.run(() => {
this.suggestRemoveAnnotation(null, annotations, file, false, annotationsChanged);
this.removeOrSuggestRemoveAnnotation(null, annotations, file, false, annotationsChanged);
});
},
});
@ -416,7 +441,7 @@ export class AnnotationActionsService {
viewer: WebViewerInstance,
file: File,
annotationWrapper: AnnotationWrapper,
annotationsChanged: EventEmitter<AnnotationWrapper>,
annotationsChanged?: EventEmitter<AnnotationWrapper>,
) {
const data = { dossier: this._dossier(file) };
this._dialogService.openDialog('resizeAnnotation', $event, data, async (result: { comment: string }) => {

View File

@ -42,27 +42,26 @@ export class AnnotationProcessingService {
const filters: INestedFilter[] = [];
annotations?.forEach(a => {
const topLevelFilter = !['hint', 'redaction', 'recommendation', 'skipped'].includes(a.superType);
const key = topLevelFilter ? a.superType : a.superType + a.type;
const filter = filterMap.get(key);
const topLevelFilter = a.topLevelFilter;
const filter = filterMap.get(a.filterKey);
if (filter) {
filter.matches += 1;
} else {
// top level filter
if (topLevelFilter) {
this._createParentFilter(key, filterMap, filters);
this._createParentFilter(a.superType, filterMap, filters);
} else {
let parentFilter = filterMap.get(a.superType);
if (!parentFilter) {
parentFilter = this._createParentFilter(a.superType, filterMap, filters);
}
const childFilter: IFilter = {
id: a.type,
id: a.filterKey,
label: a.type,
checked: false,
matches: 1,
};
filterMap.set(key, childFilter);
filterMap.set(a.filterKey, childFilter);
parentFilter.children.push(new Filter(childFilter));
}
}
@ -173,12 +172,7 @@ export class AnnotationProcessingService {
return true;
};
private _checkByFilterKey = (filter: INestedFilter, annotation: AnnotationWrapper) => {
const superType = annotation.superType;
const isNotTopLevelFilter = superType === 'hint' || superType === 'redaction' || superType === 'recommendation';
return filter.id === superType || (filter.id === annotation.type && isNotTopLevelFilter);
};
private _checkByFilterKey = (filter: NestedFilter | IFilter, annotation: AnnotationWrapper) => filter.id === annotation.filterKey;
private _sortAnnotations(annotations: AnnotationWrapper[]): AnnotationWrapper[] {
return annotations.sort((ann1, ann2) => {

View File

@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { AddDossierDialogComponent } from '../dialogs/add-dossier-dialog/add-dossier-dialog.component';
import { RemoveAnnotationsDialogComponent } from '../dialogs/remove-annotations-dialog/remove-annotations-dialog.component';
import { ForceRedactionDialogComponent } from '../dialogs/force-redaction-dialog/force-redaction-dialog.component';
import { ForceAnnotationDialogComponent } from '../dialogs/force-redaction-dialog/force-annotation-dialog.component';
import { DocumentInfoDialogComponent } from '../dialogs/document-info-dialog/document-info-dialog.component';
import { ManualAnnotationDialogComponent } from '../dialogs/manual-redaction-dialog/manual-annotation-dialog.component';
import { EditDossierDialogComponent } from '../dialogs/edit-dossier-dialog/edit-dossier-dialog.component';
@ -22,7 +22,7 @@ type DialogType =
| 'changeLegalBasis'
| 'removeAnnotations'
| 'resizeAnnotation'
| 'forceRedaction'
| 'forceAnnotation'
| 'manualAnnotation';
@Injectable()
@ -58,8 +58,8 @@ export class DossiersDialogService extends DialogService<DialogType> {
resizeAnnotation: {
component: ResizeAnnotationDialogComponent,
},
forceRedaction: {
component: ForceRedactionDialogComponent,
forceAnnotation: {
component: ForceAnnotationDialogComponent,
},
manualAnnotation: {
component: ManualAnnotationDialogComponent,

View File

@ -7,14 +7,20 @@ import { File } 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';
@Injectable()
export class PdfViewerDataService {
constructor(
private readonly _dossiersService: DossiersService,
private readonly _permissionsService: PermissionsService,
private readonly _fileManagementService: FileManagementService,
private readonly _redactionLogService: RedactionLogService,
private readonly _viewedPagesService: ViewedPagesService,
private readonly _appStateService: AppStateService,
private readonly _userPreferenceService: UserPreferenceService,
) {}
loadRedactionLogFor(dossierId: string, fileId: string) {
@ -24,12 +30,24 @@ export class PdfViewerDataService {
);
}
loadDataFor(file: File): Observable<FileDataModel> {
const file$ = this.downloadOriginalFile(file);
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);
const viewedPages$ = this.getViewedPagesFor(file);
return forkJoin([file$, reactionLog$, viewedPages$]).pipe(map(data => new FileDataModel(file, ...data)));
const dossier = this._dossiersService.find(file.dossierId);
return forkJoin([file$, reactionLog$, viewedPages$]).pipe(
map(
data =>
new FileDataModel(
file,
...data,
this._appStateService.dictionaryData[dossier.dossierTemplateId],
this._userPreferenceService.areDevFeaturesEnabled,
),
),
);
}
getViewedPagesFor(file: File) {

View File

@ -51,7 +51,6 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
@Input() file: File;
@Input() type: 'file-preview' | 'dossier-overview-list' | 'dossier-overview-workflow';
@Input() maxWidth: number;
@Output() readonly ocredFile = new EventEmitter<void>();
toggleTooltip?: string;
assignTooltip?: string;
@ -308,7 +307,6 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
$event.stopPropagation();
this._loadingService.start();
await this._reanalysisService.ocrFiles([this.file.fileId], this.file.dossierId).toPromise();
this.ocredFile.emit();
this._loadingService.stop();
}

View File

@ -14,6 +14,7 @@ export const processingFileStatusTranslations: { [key in ProcessingFileStatus]:
DELETED: _('file-status.deleted'),
ERROR: _('file-status.error'),
FULLREPROCESS: _('file-status.full-reprocess'),
IMAGE_ANALYZING: _('file-status.image-analyzing'),
INDEXING: _('file-status.indexing'),
OCR_PROCESSING: _('file-status.ocr-processing'),
PROCESSING: _('file-status.processing'),

View File

@ -28,5 +28,5 @@
</div>
<ng-template #avatar let-userId="userId">
<redaction-initials-avatar [user]="userId" [withName]="true" color="gray" size="small"></redaction-initials-avatar>
<redaction-initials-avatar [user]="userId" [withName]="true" color="gray"></redaction-initials-avatar>
</ng-template>

View File

@ -1,5 +1,5 @@
<button [class.overlay]="showDot" mat-button>
<redaction-initials-avatar [user]="userService.currentUser$ | async" [withName]="true" size="small"></redaction-initials-avatar>
<redaction-initials-avatar [user]="userService.currentUser$ | async" [withName]="true"></redaction-initials-avatar>
<mat-icon svgIcon="iqser:arrow-down"></mat-icon>
</button>
<div *ngIf="showDot" class="dot"></div>

View File

@ -3,6 +3,7 @@
:host {
@extend .user-button;
min-width: fit-content;
button {
padding: 0 10px 0 5px;

View File

@ -50,10 +50,6 @@ form {
align-items: center;
}
.mr-32 {
margin-right: 32px;
}
.w-450 {
width: 100%;
max-width: 450px;

View File

@ -68,6 +68,7 @@ export class EditorComponent implements OnInit, OnChanges {
glyphMargin: true,
automaticLayout: true,
readOnly: !this.canEdit,
extraEditorClassName: this.canEdit ? '' : 'disabled',
};
}
@ -79,19 +80,12 @@ export class EditorComponent implements OnInit, OnChanges {
this._diffEditor.getModifiedEditor().onDidChangeModelContent(() => {
this.value = this._diffEditor.getModel().modified.getValue();
});
this._setTheme();
}
onCodeEditorInit(editor: ICodeEditor): void {
this.codeEditor = editor;
(window as any).monaco.editor.defineTheme('redaction', {
base: 'vs',
inherit: true,
rules: [],
colors: {
'editor.lineHighlightBackground': '#f4f5f7',
},
});
(window as any).monaco.editor.setTheme('redaction');
this._setTheme();
}
@Debounce()
@ -107,6 +101,34 @@ export class EditorComponent implements OnInit, OnChanges {
this._diffEditor?.getModifiedEditor().setValue(this.diffValue);
}
private _defineThemes(): void {
(window as any).monaco.editor.defineTheme('redaction', {
base: 'vs',
inherit: true,
rules: [],
colors: {
'editor.lineHighlightBackground': '#f4f5f7',
},
});
(window as any).monaco.editor.defineTheme('redaction-disabled', {
base: 'vs',
inherit: true,
rules: [],
colors: {
'editor.background': '#f4f5f7',
'editor.foreground': '#9398a0',
'editor.lineHighlightBackground': '#f4f5f7',
'editorLineNumber.foreground': '#9398a0',
'editorActiveLineNumber.foreground': '#9398a0',
},
});
}
private _setTheme(): void {
this._defineThemes();
(window as any).monaco.editor.setTheme(this.canEdit ? 'redaction' : 'redaction-disabled');
}
private _handleMarginButtonClick(event: IEditorMouseEvent) {
const isMarginButtonClick = event.target.detail.glyphMarginWidth && event.target.detail.glyphMarginWidth !== 0;
if (isMarginButtonClick) {

View File

@ -21,7 +21,3 @@ mat-slide-toggle {
color: rgba(var(--iqser-accent-rgb), 0.3);
}
}
.ml-0 {
margin-left: 0;
}

View File

@ -6,7 +6,7 @@
>
{{ _user | name: { showInitials: true } }}
</div>
<div *ngIf="withName" [class.disabled]="disabled" class="clamp-2 username">
<div *ngIf="withName" [class.disabled]="disabled" class="clamp-1 username">
{{ userName }}
</div>
</div>

View File

@ -1,8 +1,13 @@
<div class="label-header">
<div class="all-caps-label">{{ label }}</div>
<div class="actions">
<div (click)="selectAll($event)" class="all-caps-label primary pointer" translate="actions.all"></div>
<div (click)="deselectAll($event)" class="all-caps-label primary pointer" translate="actions.none"></div>
<div (click)="selectAll($event)" class="all-caps-label primary pointer" [class.disabled]="disabled" translate="actions.all"></div>
<div
(click)="deselectAll($event)"
class="all-caps-label primary pointer"
[class.disabled]="disabled"
translate="actions.none"
></div>
</div>
</div>

View File

@ -69,3 +69,8 @@ mat-chip {
.mat-standard-chip:focus::after {
opacity: 0;
}
.disabled {
pointer-events: none;
color: variables.$grey-5;
}

View File

@ -24,6 +24,12 @@
label="S"
type="square"
></redaction-annotation-icon>
<redaction-annotation-icon
*ngIf="filter.id === 'ignored-hint'"
[color]="dictionaryColor"
label="I"
type="circle"
></redaction-annotation-icon>
<redaction-annotation-icon
*ngIf="isSuggestion(filter.id)"
[color]="dictionaryColor"

View File

@ -14,7 +14,6 @@ import { FileDropOverlayService } from './services/file-drop-overlay.service';
@NgModule({
imports: [CommonModule, SharedModule, OverlayModule],
declarations: [FileDropComponent, UploadStatusOverlayComponent, OverwriteFilesDialogComponent],
entryComponents: [FileDropComponent, UploadStatusOverlayComponent],
providers: [UploadDownloadDialogService, FileUploadService, FileDownloadService, StatusOverlayService, FileDropOverlayService],
exports: [FileDropComponent, UploadStatusOverlayComponent],
})

View File

@ -15,7 +15,7 @@ export class PlatformSearchService extends GenericService<ISearchResponse> {
private readonly _dossiersService: DossiersService,
private readonly _filesMapService: FilesMapService,
) {
super(_injector, 'search');
super(_injector, 'search-v2');
}
search({ dossierIds, query }: ISearchInput): Observable<ISearchResponse> {
@ -29,7 +29,7 @@ export class PlatformSearchService extends GenericService<ISearchResponse> {
const body: ISearchRequest = {
dossierIds,
queryString: query ?? '',
page: 1,
page: 0,
returnSections: false,
pageSize: 300,
};

View File

@ -1,17 +1,17 @@
import { Injectable, Injector } from '@angular/core';
import { GenericService, RequiredParam, Validate } from '@iqser/common-ui';
import { IGeneralConfiguration } from '@red/domain';
import { UserService } from '@services/user.service';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class GeneralSettingsService extends GenericService<IGeneralConfiguration> {
constructor(protected readonly _injector: Injector, private readonly _userService: UserService) {
constructor(protected readonly _injector: Injector) {
super(_injector, 'configuration');
}
getGeneralConfigurations() {
getGeneralConfigurations(): Observable<IGeneralConfiguration> {
return this._getOne(['general']);
}

View File

@ -122,6 +122,10 @@ export class PermissionsService {
return dossier.ownerId === this._userService.currentUser.id;
}
canEditDossier(user = this._userService.currentUser) {
return user.isManager;
}
isAdmin(user = this._userService.currentUser): boolean {
return user.isAdmin;
}
@ -131,7 +135,7 @@ export class PermissionsService {
}
canExcludePages(file: File): boolean {
return this.isFileAssignee(file);
return this.canPerformAnnotationActions(file);
}
canDeleteComment(comment: IComment, file: File) {

View File

@ -50,15 +50,17 @@ export class UserService extends EntitiesService<User, IUser> {
);
}
async loadCurrentUser() {
async loadCurrentUser(): Promise<User> {
const token = await this._keycloakService.getToken();
const decoded = jwt_decode(token);
const userId = (<{ sub: string }>decoded).sub;
const roles = this._keycloakService.getUserRoles(true).filter(role => role.startsWith('RED_'));
this.replace(new User(await this._keycloakService.loadUserProfile(true), roles, userId));
const user = new User(await this._keycloakService.loadUserProfile(true), roles, userId);
this.replace(user);
this._currentUser$.next(this.find(userId));
return user;
}
getNameForId(userId: string): string | undefined {

View File

@ -176,6 +176,14 @@ export class AppStateService {
true,
);
dictionaryData['ignored-hint'] = new Dictionary(
{
hexColor: colors.ignoredHintColor || FALLBACK_COLOR,
type: 'ignored-hint',
},
true,
);
dictionaryData['manual-redaction'] = new Dictionary(
{
hexColor: colors.manualRedactionColor || FALLBACK_COLOR,

View File

@ -4,6 +4,7 @@ import { AnnotationSuperType } from '../models/file/annotation.wrapper';
export const annotationTypesTranslations: { [key in AnnotationSuperType]: string } = {
'declined-suggestion': _('annotation-type.declined-suggestion'),
hint: _('annotation-type.hint'),
'ignored-hint': _('annotation-type.ignored-hint'),
'manual-redaction': _('annotation-type.manual-redaction'),
recommendation: _('annotation-type.recommendation'),
redaction: _('annotation-type.redaction'),

View File

@ -6,6 +6,7 @@ import { KeycloakEventType, KeycloakService } from 'keycloak-angular';
import { GeneralSettingsService } from '@services/general-settings.service';
import { LanguageService } from '@i18n/language.service';
import { UserPreferenceService } from '@services/user-preference.service';
import { UserService } from '@services/user.service';
export function configurationInitializer(
keycloakService: KeycloakService,
@ -13,6 +14,7 @@ export function configurationInitializer(
configService: ConfigService,
generalSettingsService: GeneralSettingsService,
languageService: LanguageService,
userService: UserService,
userPreferenceService: UserPreferenceService,
) {
return () =>
@ -21,14 +23,16 @@ export function configurationInitializer(
filter(event => event.type === KeycloakEventType.OnReady),
switchMap(() => from(keycloakService.isLoggedIn())),
switchMap(loggedIn => (!loggedIn ? throwError('Not Logged In') : of({}))),
switchMap(() => from(userService.loadCurrentUser())),
switchMap(user => (!user.hasAnyREDRoles ? throwError('Not user has no red roles') : of({}))),
mergeMap(() => generalSettingsService.getGeneralConfigurations()),
tap(configuration => configService.updateDisplayName(configuration.displayName)),
switchMap(() => userPreferenceService.reload()),
tap(() => languageService.chooseAndSetInitialLanguage()),
catchError(() => {
title.setTitle('RedactManager');
return of({});
}),
tap(() => languageService.chooseAndSetInitialLanguage()),
take(1),
)
.toPromise();

View File

@ -10,6 +10,7 @@ export const SuperTypeSorter: { [key in AnnotationSuperType]: number } = {
'suggestion-remove-dictionary': 13,
'suggestion-add': 10,
'suggestion-remove': 11,
'ignored-hint': 45,
skipped: 50,
redaction: 1,
'manual-redaction': 2,

View File

@ -1,7 +1,7 @@
{
"ADMIN_CONTACT_NAME": null,
"ADMIN_CONTACT_URL": null,
"API_URL": "https://dev-03.iqser.cloud/redaction-gateway-v1",
"API_URL": "https://dev-08.iqser.cloud/redaction-gateway-v1",
"APP_NAME": "RedactManager",
"AUTO_READ_TIME": 1.5,
"BACKEND_APP_VERSION": "4.4.40",
@ -17,7 +17,7 @@
"MAX_RETRIES_ON_SERVER_ERROR": 3,
"OAUTH_CLIENT_ID": "redaction",
"OAUTH_IDP_HINT": null,
"OAUTH_URL": "https://dev-03.iqser.cloud/auth/realms/redaction",
"OAUTH_URL": "https://dev-08.iqser.cloud/auth/realms/redaction",
"RECENT_PERIOD_IN_HOURS": 24,
"SELECTION_MODE": "structural"
}

View File

@ -473,7 +473,8 @@
"previewColor": "Vorschau",
"requestAdd": "Neuen Wörterbucheintrag vorschlagen",
"requestRemove": "Anfrage entfernt",
"updatedColor": "Aktualisiert"
"updatedColor": "Aktualisiert",
"ignoredHintColor": "Ignorierter Hinweis"
}
},
"dev-mode": "DEV",

View File

@ -151,6 +151,9 @@
"edit-reason": {
"label": "Edit Reason"
},
"force-hint": {
"label": "Force Hint"
},
"force-redaction": {
"label": "Force Redaction"
},
@ -277,6 +280,7 @@
"annotation-type": {
"declined-suggestion": "Declined Suggestion",
"hint": "Hint",
"ignored-hint": "Ignored Hint",
"manual-redaction": "Manual Redaction",
"recommendation": "Recommendation",
"redaction": "Redaction",
@ -296,10 +300,10 @@
"dialog": {
"approvers": "Approvers",
"make-approver": "Make Approver",
"no-approvers": "No approvers yet.\nSelect from the list below.",
"no-reviewers": "No reviewers yet.\nSelect from the list below.",
"no-reviewers": "No members with \"review only\" permission.",
"reviewers": "Reviewers",
"search": "Search...",
"select-below": "Select from the list below.",
"single-user": "Owner"
}
},
@ -351,10 +355,10 @@
"to": "to"
},
"auth-error": {
"heading": "Your user doesn't have the required RED-* roles to access this application. Please contact your admin for access!",
"heading-with-link": "Your user doesn't have the required RED-* roles to access this application. Please contact <a href={adminUrl} target=_blank >your admin</a> for access!",
"heading-with-name": "Your user doesn't have the required RED-* roles to access this application. Please contact {adminName} for access!",
"heading-with-name-and-link": "Your user doesn't have the required RED-* roles to access this application. Please contact <a href={adminUrl} target=_blank >{adminName}</a> for access!",
"heading": "Your user is successfully logged in but has no role assigned yet. Please contact your RedactManager administrator to assign appropriate roles.",
"heading-with-link": "Your user is successfully logged in but has no role assigned yet. Please contact <a href={adminUrl} target=_blank >your RedactManager administrator</a> to assign appropriate roles!",
"heading-with-name": "Your user is successfully logged in but has no role assigned yet. Please contact {adminName} to assign appropriate roles.",
"heading-with-name-and-link": "Your user is successfully logged in but has no role assigned yet. Please contact <a href={adminUrl} target=_blank >{adminName}</a> to assign appropriate roles.",
"logout": "Logout"
},
"by": "by",
@ -476,6 +480,7 @@
"analysisColor": "Analysis",
"defaultColor": "Default Color",
"dictionaryRequestColor": "Dictionary Request",
"ignoredHintColor": "Ignored Hint",
"manualRedactionColor": "Manual Redaction",
"notRedacted": "Skipped",
"previewColor": "Preview",
@ -639,6 +644,9 @@
"action": "Delete Dossier",
"delete-failed": "Failed to delete dossier: {dossierName}"
},
"dossier-info": {
"action": "Dossier Info"
},
"edit": {
"action": "Edit Dossier"
},
@ -882,9 +890,11 @@
"title": "There are no deleted documents."
},
"table-col-names": {
"assignee": "Assignee",
"deleted-on": "Deleted On",
"name": "Name",
"pages": "Pages",
"status": "Status",
"time-to-restore": "Time To Restore"
},
"table-header": {
@ -1110,6 +1120,7 @@
"deleted": "Deleted",
"error": "Re-processing required",
"full-reprocess": "Processing",
"image-analyzing": "Image Analyzing",
"indexing": "Processing",
"new": "New",
"ocr-processing": "OCR Processing",
@ -1283,7 +1294,8 @@
"header": {
"dictionary": "Add to dictionary",
"false-positive": "Set false positive",
"force": "Force Redaction",
"force-hint": "Force Hint",
"force-redaction": "Force Redaction",
"redaction": "Redaction",
"request-dictionary": "Request add to dictionary",
"request-false-positive": "Request false positive",
@ -1306,19 +1318,19 @@
"sep": "Sep."
},
"notification": {
"assign-approver": "You have been assigned as approver for <b><a href=\"{fileHref}\" target=\"_blank\">{fileName}</a></b> in the <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a><b>!",
"assign-reviewer": "You have been assigned as reviewer for <b><a href=\"{fileHref}\" target=\"_blank\">{fileName}</a></b> in the <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a><b>!",
"assign-approver": "You have been assigned as approver for <b><a href=\"{fileHref}\" target=\"_blank\">{fileName}</a></b> in dossier: <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a><b>!",
"assign-reviewer": "You have been assigned as reviewer for <b><a href=\"{fileHref}\" target=\"_blank\">{fileName}</a></b> in dossier: <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a><b>!",
"document-approved": " <b><a href=\"{fileHref}\" target=\"_blank\">{fileName}</a></b> has been approved!",
"dossier-deleted": "Dossier: <b>{dossierName}</b> has been deleted!",
"dossier-owner-removed": "<b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a></b> owner removed!",
"dossier-owner-set": " <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a></b> owner changed to <b>{user}</b>!",
"download-ready": "Your <b><a href='/ui/main/downloads', target=\"_blank\">download</a><b> is ready!",
"download-ready": "Your <b><a href='/ui/main/downloads', target=\"_blank\">download</a></b> is ready!",
"no-data": "You currently have no notifications",
"unassigned-from-file": "You have been unassigned from <b><a href=\"{fileHref}\" target=\"_blank\">{fileName}</a></b> in the <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a><b>!",
"user-becomes-dossier-member": "<b>{user}</b> joined dossier: <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a></b>!",
"user-demoted-to-reviewer": "<b>{user}</b> demoted to reviewer in dossier: <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a></b>!",
"user-promoted-to-approver": "<b>{user}</b> promoted to approver in dossier: <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a></b>!",
"user-removed-as-dossier-member": "<b>{user}</b> removed as a member of: <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a></b> !"
"unassigned-from-file": "You have been unassigned from <b><a href=\"{fileHref}\" target=\"_blank\">{fileName}</a></b> in dossier: <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a><b>!",
"user-becomes-dossier-member": "You have been added to dossier: <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a></b>!",
"user-demoted-to-reviewer": "You have been demoted to reviewer in dossier: <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a></b>!",
"user-promoted-to-approver": "You have been promoted to approver in dossier: <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a></b>!",
"user-removed-as-dossier-member": "You have been removed as a member from dossier: <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a></b> !"
},
"notifications": "Notifications",
"notifications-screen": {

View File

@ -1,5 +1,39 @@
@use 'variables';
.monaco-diff-editor {
.editor.original {
right: 30px !important; // diffOverviewRuler size
left: unset !important;
}
.editor.modified {
left: 0 !important;
width: unset !important;
}
.diffOverview {
.diffOverviewRuler.original {
left: 15px !important;
}
.diffOverviewRuler.modified {
right: 15px !important;
}
}
}
.monaco-editor.disabled {
cursor: default;
.monaco-mouse-cursor-text {
cursor: default;
}
.cursors-layer > .cursor {
display: none !important;
}
}
.changed-row-marker {
background: rgba(variables.$primary, 0.1);
}

View File

@ -32,25 +32,3 @@
$iqser-yellow-2: vars.$yellow-2,
$iqser-helpmode-primary: vars.$green-2
);
.monaco-diff-editor {
.editor.original {
right: 30px !important; // diffOverviewRuler size
left: unset !important;
}
.editor.modified {
left: 0 !important;
width: unset !important;
}
.diffOverview {
.diffOverviewRuler.original {
left: 15px !important;
}
.diffOverviewRuler.modified {
right: 15px !important;
}
}
}

View File

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Maven: com.atlassian.bamboo:bamboo-specs-api:8.0.2" level="project" />
<orderEntry type="library" name="Maven: org.jetbrains:annotations:20.1.0" level="project" />
<orderEntry type="library" name="Maven: com.google.code.findbugs:jsr305:3.0.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.12.0" level="project" />
<orderEntry type="library" name="Maven: com.atlassian.bamboo:bamboo-specs:8.0.2" level="project" />
<orderEntry type="library" name="Maven: com.google.code.gson:gson:2.8.6" level="project" />
<orderEntry type="library" name="Maven: commons-validator:commons-validator:1.7" level="project" />
<orderEntry type="library" name="Maven: commons-beanutils:commons-beanutils:1.9.4" level="project" />
<orderEntry type="library" name="Maven: commons-digester:commons-digester:2.1" level="project" />
<orderEntry type="library" name="Maven: commons-logging:commons-logging:1.2" level="project" />
<orderEntry type="library" name="Maven: commons-collections:commons-collections:3.2.2" level="project" />
<orderEntry type="library" name="Maven: org.yaml:snakeyaml:1.28" level="project" />
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpclient:4.5.13" level="project" />
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpcore:4.4.13" level="project" />
<orderEntry type="library" name="Maven: commons-codec:commons-codec:1.11" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: junit:junit:4.13.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-core:1.3" level="project" />
</component>
</module>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>com.atlassian.bamboo</groupId>
<artifactId>bamboo-specs-parent</artifactId>
<version>8.0.2</version>
<version>8.1.1</version>
<relativePath/>
</parent>

View File

@ -59,7 +59,8 @@ public class PlanSpec {
public Plan createDockerBuildPlan() {
return new Plan(project(), "Redaction UI", new BambooKey("UI"))
.description("Docker build for Redaction UI.")
.stages(new Stage("Build Stage").jobs(creatGinCloudPlatformImagesJob("red-ui")))
.stages(new Stage("UI Build Stage")
.jobs(creatGinCloudPlatformImagesJob("red-ui")))
.stages(new Stage("Release")
.manual(true)
.jobs(createRelease()))
@ -70,14 +71,12 @@ public class PlanSpec {
}
public Job creatGinCloudPlatformImagesJob(String project) {
return new Job("Build Job: " + project, new BambooKey(project.toUpperCase().replaceAll("-", "")))
return new Job("Build Job UI" , new BambooKey("UIBUILD"))
.tasks(
new CleanWorkingDirectoryTask().description("My clean working directory task"),
// Checkout
new VcsCheckoutTask().description("Checkout Default Repository")
.checkoutItems(new CheckoutItem().defaultRepository().path("redaction-ui")),
new VcsCheckoutTask().description("Checkout UI Shared Lib")
.checkoutItems(new CheckoutItem().repository("Shared Libraries / common-ui").path("common-ui")),
// Build
new ScriptTask().description("Build")

View File

@ -5,13 +5,8 @@ set -e
imageName="nexus.iqser.com:5001/red/$PROJECT"
dockerfileLocation="docker/$PROJECT/Dockerfile"
echo "submodule status"
git submodule status
commonUIVersion=$(git submodule status | awk '{ print $1 }' | sed 's|-||g')
echo $commonUIVersion
cd ../common-ui
git checkout $commonUIVersion
cd ../redaction-ui
mv ../common-ui ./libs/
echo "On branch $bamboo_planRepository_branchName building project $PROJECT"
# shellcheck disable=SC2154

View File

@ -9,4 +9,5 @@ export interface IColors {
requestAdd?: string;
requestRemove?: string;
updatedColor?: string;
ignoredHintColor?: string;
}

View File

@ -2,6 +2,8 @@ import { IFileAttributeConfig } from './file-attribute-config';
export interface IFileAttributesConfig {
delimiter?: string;
fileAttributeConfigs?: IFileAttributeConfig[];
encoding?: string;
filenameMappingColumnHeaderName?: string;
fileAttributeConfigs?: IFileAttributeConfig[];
}

View File

@ -93,9 +93,7 @@ export class File extends Entity<IFile> implements IFile {
this.hasSuggestions = !!file.hasSuggestions;
this.statusSort = StatusSorter[this.workflowStatus];
if (this.lastUpdated && this.lastOCRTime) {
this.cacheIdentifier = btoa((this.lastUploaded ?? '') + this.lastOCRTime);
}
this.cacheIdentifier = btoa((this.lastUploaded ?? '') + (this.lastOCRTime ?? ''));
this.hintsOnly = this.hasHints && !this.hasRedactions;
this.hasNone = !this.hasRedactions && !this.hasHints && !this.hasSuggestions;
this.isPending = this.processingStatus === ProcessingFileStatuses.UNPROCESSED;

View File

@ -14,6 +14,7 @@ export const ProcessingFileStatuses = {
DELETED: 'DELETED',
ERROR: 'ERROR',
FULLREPROCESS: 'FULLREPROCESS',
IMAGE_ANALYZING: 'IMAGE_ANALYZING',
INDEXING: 'INDEXING',
OCR_PROCESSING: 'OCR_PROCESSING',
PROCESSED: 'PROCESSED',
@ -28,6 +29,7 @@ export const isProcessingStatuses: List<ProcessingFileStatus> = [
ProcessingFileStatuses.REPROCESS,
ProcessingFileStatuses.FULLREPROCESS,
ProcessingFileStatuses.OCR_PROCESSING,
ProcessingFileStatuses.IMAGE_ANALYZING,
ProcessingFileStatuses.INDEXING,
ProcessingFileStatuses.PROCESSING,
] as const;

View File

@ -3,6 +3,7 @@ export type DefaultColorType =
| 'defaultColor'
| 'dictionaryRequestColor'
| 'manualRedactionColor'
| 'ignoredHintColor'
| 'notRedacted'
| 'previewColor'
| 'requestAdd'

View File

@ -1,11 +1,11 @@
{
"name": "redaction",
"version": "3.115.0",
"version": "3.140.0",
"private": true,
"license": "MIT",
"scripts": {
"build": "nx build",
"build-lint-all": "ng lint --project=red-ui --fix && ng lint --project=common-ui --fix && ng build --project=red-ui --configuration production --base-href /ui/",
"build-lint-all": "ng build --project=red-ui --configuration production --base-href /ui/",
"build-paligo-styles": "mkdir -p dist/paligo-styles && sass --load-path=. paligo-styles/style.scss > dist/paligo-styles/redacto-theme.css",
"i18n:extract": "ngx-translate-extract --input ./apps/red-ui/src ./libs/common-ui/src --output apps/red-ui/src/assets/i18n/en.json --clean --sort --format namespaced-json && prettier apps/red-ui/src/assets/i18n/*.json --write",
"postinstall": "ngcc --properties es2015 browser module main",
@ -23,36 +23,36 @@
}
},
"dependencies": {
"@angular/animations": "13.0.2",
"@angular/cdk": "13.0.2",
"@angular/common": "13.0.2",
"@angular/compiler": "13.0.2",
"@angular/core": "13.0.2",
"@angular/forms": "13.0.2",
"@angular/material": "13.0.2",
"@angular/animations": "13.1.1",
"@angular/cdk": "13.1.1",
"@angular/common": "13.1.1",
"@angular/compiler": "13.1.1",
"@angular/core": "13.1.1",
"@angular/forms": "13.1.1",
"@angular/material": "13.1.1",
"@angular/material-moment-adapter": "^13.0.2",
"@angular/platform-browser": "13.0.2",
"@angular/platform-browser-dynamic": "13.0.2",
"@angular/router": "13.0.2",
"@angular/service-worker": "13.0.2",
"@angular/platform-browser": "13.1.1",
"@angular/platform-browser-dynamic": "13.1.1",
"@angular/router": "13.1.1",
"@angular/service-worker": "13.1.1",
"@biesbjerg/ngx-translate-extract-marker": "^1.0.0",
"@materia-ui/ngx-monaco-editor": "^6.0.0-beta.1",
"@ngx-translate/core": "^13.0.0",
"@ngx-translate/http-loader": "^6.0.0",
"@ngx-translate/core": "^14.0.0",
"@ngx-translate/http-loader": "^7.0.0",
"@nrwl/angular": "13.2.3",
"@pdftron/webviewer": "8.2.0",
"@swimlane/ngx-charts": "^17.0.1",
"file-saver": "^2.0.5",
"jwt-decode": "^3.1.2",
"keycloak-angular": "^8.4.0",
"keycloak-js": "15.0.2",
"keycloak-angular": "^9.0.0",
"keycloak-js": "^16.0.0",
"lodash.orderby": "^4.6.0",
"messageformat": "^2.3.0",
"moment": "^2.29.1",
"monaco-editor": "^0.30.1",
"ngx-color-picker": "^11.0.0",
"ngx-toastr": "^14.1.3",
"ngx-translate-messageformat-compiler": "^4.10.0",
"ngx-translate-messageformat-compiler": "^4.11.0",
"papaparse": "^5.3.1",
"rxjs": "~6.6.7",
"sass": "^1.39.2",
@ -62,13 +62,13 @@
"zone.js": "0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "13.0.3",
"@angular-devkit/build-angular": "13.1.2",
"@angular-eslint/eslint-plugin": "13.0.1",
"@angular-eslint/eslint-plugin-template": "13.0.1",
"@angular-eslint/template-parser": "13.0.1",
"@angular/cli": "13.0.3",
"@angular/compiler-cli": "13.0.2",
"@angular/language-service": "13.0.2",
"@angular/cli": "13.1.2",
"@angular/compiler-cli": "13.1.1",
"@angular/language-service": "13.1.1",
"@nrwl/cli": "13.2.3",
"@nrwl/cypress": "13.2.3",
"@nrwl/eslint-plugin-nx": "13.2.3",

Binary file not shown.

View File

@ -9,10 +9,10 @@
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "es2015",
"module": "esnext",
"target": "es2017",
"module": "es2020",
"typeRoots": ["node_modules/@types"],
"lib": ["es2019", "dom"],
"lib": ["es2020", "dom"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"baseUrl": ".",

1948
yarn.lock

File diff suppressed because it is too large Load Diff