Merge branch 'master' into VM/RED-6774

This commit is contained in:
Valentin Mihai 2023-06-18 22:39:43 +03:00
commit 8f26b3bffc
15 changed files with 2617 additions and 2651 deletions

View File

@ -14,7 +14,6 @@
</ng-container>
<a (click)="selectTenant()" mat-menu-item>
<mat-icon svgIcon="red:open-in-new"></mat-icon>
<span translate="top-bar.navigation-items.my-account.children.join-another-tenant"> </span>
</a>
</div>

View File

@ -1,7 +1,8 @@
import { Component, inject } from '@angular/core';
import { BASE_HREF, getCurrentUser, IStoredTenant, TenantsService } from '@iqser/common-ui';
import { BASE_HREF, getConfig, getCurrentUser, IStoredTenant, KeycloakStatusService, TenantsService } from '@iqser/common-ui';
import { User } from '@red/domain';
import { KeyValue } from '@angular/common';
import { KeycloakService } from 'keycloak-angular';
@Component({
selector: 'app-tenants-menu',
@ -12,7 +13,10 @@ export class TenantsMenuComponent {
readonly tenantsService = inject(TenantsService);
readonly storedTenants: Map<string, IStoredTenant[]>;
readonly #baseHref = inject(BASE_HREF);
readonly #keycloakService = inject(KeycloakService);
readonly #keycloakStatusService = inject(KeycloakStatusService);
readonly #currentUser = getCurrentUser<User>();
readonly #config = getConfig();
constructor() {
this.storedTenants = this.#getStoredTenants();
@ -22,11 +26,21 @@ export class TenantsMenuComponent {
return item.key;
}
selectTenant(tenantId?: string, email?: string) {
let tenantUrl = tenantId ? '/' + tenantId + '/main' : '/';
async selectTenant(tenantId?: string, email?: string) {
let tenantUrl = tenantId ? '/' + tenantId : '/';
if (email) {
tenantUrl += '?username=' + email;
}
if (tenantId === this.tenantsService.activeTenantId) {
const url = this.#keycloakService.getKeycloakInstance().createLoginUrl({
redirectUri: this.#keycloakStatusService.createLoginUrl(),
idpHint: this.#config.OAUTH_IDP_HINT,
loginHint: email ?? undefined,
});
return this.#keycloakService.logout(url);
}
window.open(window.location.origin + this.#baseHref + tenantUrl, '_blank');
}

View File

@ -1,23 +1,9 @@
<section class="dialog">
<div
class="dialog-header heading-l"
[innerHTML]="
(data.removeFromDictionary
? 'remove-annotations-dialog.remove-from-dictionary.title'
: 'remove-annotations-dialog.remove-only-here.title'
) | translate : { hint: data.hint }
"
></div>
<div [innerHTML]="_headerTitle" class="dialog-header heading-l"></div>
<form (submit)="save()" [formGroup]="form">
<div
class="dialog-content"
[innerHTML]="
(data.removeFromDictionary
? 'remove-annotations-dialog.remove-from-dictionary.question'
: 'remove-annotations-dialog.remove-only-here.question'
) | translate : { hint: data.hint }
"
>
<div class="dialog-content">
<span [innerHTML]="_contentTitle"></span>
<div *ngIf="data.removeFromDictionary" class="content-wrapper">
<table class="default-table">
<thead>
@ -55,9 +41,9 @@
[type]="iconButtonTypes.primary"
></iqser-icon-button>
<div class="all-caps-label cancel" mat-dialog-close [translate]="'remove-annotations-dialog.cancel'"></div>
<div [translate]="'remove-annotations-dialog.cancel'" class="all-caps-label cancel" mat-dialog-close></div>
</div>
</form>
<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>

View File

@ -17,6 +17,13 @@ export interface RemoveAnnotationsDialogInput {
styleUrls: ['./remove-annotations-dialog.component.scss'],
})
export class RemoveAnnotationsDialogComponent extends BaseDialogComponent {
protected readonly _headerTitle = this.data.removeFromDictionary
? this._translateService.instant('remove-annotations-dialog.remove-from-dictionary.title', { hint: this.data.hint })
: this._translateService.instant('remove-annotations-dialog.remove-only-here.title', { hint: this.data.hint });
protected readonly _contentTitle = this.data.removeFromDictionary
? this._translateService.instant('remove-annotations-dialog.remove-from-dictionary.question', { hint: this.data.hint })
: this._translateService.instant('remove-annotations-dialog.remove-only-here.question', { hint: this.data.hint });
constructor(
private readonly _translateService: TranslateService,
protected readonly _dialogRef: MatDialogRef<RemoveAnnotationsDialogComponent>,

View File

@ -5,7 +5,6 @@ import {
effect,
ElementRef,
HostListener,
Injector,
NgZone,
OnDestroy,
OnInit,
@ -139,7 +138,6 @@ export class FilePreviewScreenComponent
private readonly _helpModeService: HelpModeService,
private readonly _suggestionsService: SuggestionsService,
private readonly _dialog: MatDialog,
private readonly _injector: Injector,
) {
super();
effect(() => {
@ -175,6 +173,12 @@ export class FilePreviewScreenComponent
// this.pdf.disable(textActions);
// }
});
effect(() => {
if (this._viewModeService.viewMode()) {
this.updateViewMode().then();
}
});
}
get changed() {
@ -338,30 +342,28 @@ export class FilePreviewScreenComponent
}
openManualAnnotationDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) {
return this._ngZone.run(() => {
const file = this.state.file();
const file = this.state.file();
this._dialogService.openDialog(
'manualAnnotation',
{ manualRedactionEntryWrapper, dossierId: this.dossierId, file },
(result: { annotations: ManualRedactionEntryWrapper[]; dictionary?: Dictionary }) => {
const selectedAnnotations = this._annotationManager.selected;
if (selectedAnnotations.length > 0) {
this._annotationManager.delete([selectedAnnotations[0].Id]);
}
this._dialogService.openDialog(
'manualAnnotation',
{ manualRedactionEntryWrapper, dossierId: this.dossierId, file },
(result: { annotations: ManualRedactionEntryWrapper[]; dictionary?: Dictionary }) => {
const selectedAnnotations = this._annotationManager.selected;
if (selectedAnnotations.length > 0) {
this._annotationManager.delete([selectedAnnotations[0].Id]);
}
const add$ = this._manualRedactionService.addAnnotation(
result.annotations.map(w => w.manualRedactionEntry).filter(e => e.positions[0].page <= file.numberOfPages),
this.dossierId,
this.fileId,
result.dictionary?.label,
);
const add$ = this._manualRedactionService.addAnnotation(
result.annotations.map(w => w.manualRedactionEntry).filter(e => e.positions[0].page <= file.numberOfPages),
this.dossierId,
this.fileId,
result.dictionary?.label,
);
const addAndReload$ = add$.pipe(switchMap(() => this._filesService.reload(this.dossierId, file)));
return firstValueFrom(addAndReload$.pipe(catchError(() => of(undefined))));
},
);
});
const addAndReload$ = add$.pipe(switchMap(() => this._filesService.reload(this.dossierId, file)));
return firstValueFrom(addAndReload$.pipe(catchError(() => of(undefined))));
},
);
}
openRedactTextDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) {
@ -499,9 +501,8 @@ export class FilePreviewScreenComponent
return combineLatest([currentPageAnnotations$, this._documentViewer.loaded$]).pipe(
filter(([, loaded]) => loaded),
map(([annotations]) => annotations),
map(annotations => this.drawChangedAnnotations(...annotations)),
tap(annotations => this.drawChangedAnnotations(...annotations)?.then(() => this.updateViewMode())),
tap(([, newAnnotations]) => this.#highlightSelectedAnnotations(newAnnotations)),
tap(() => this.updateViewMode()),
);
}
@ -519,8 +520,7 @@ export class FilePreviewScreenComponent
const annotationsToDraw = this.#getAnnotationsToDraw(oldAnnotations, newAnnotations);
this._logger.info('[ANNOTATIONS] To draw: ', annotationsToDraw);
this._annotationManager.delete(annotationsToDraw);
this.#cleanupAndRedrawAnnotations(annotationsToDraw);
return [oldAnnotations, newAnnotations];
return this.#cleanupAndRedrawAnnotations(annotationsToDraw);
}
@Debounce(30)
@ -704,12 +704,12 @@ export class FilePreviewScreenComponent
return of([true, annotations] as const);
}),
map(([confirmed, annotations]) => {
if (confirmed) {
this.drawChangedAnnotations([], annotations);
filter(([confirmed]) => confirmed),
map(([, annotations]) => {
this.drawChangedAnnotations([], annotations).then(() => {
this._toaster.success(_('load-all-annotations-success'));
this._viewerHeaderService.disableLoadAllAnnotations();
}
});
}),
)
.subscribe();
@ -755,7 +755,7 @@ export class FilePreviewScreenComponent
#cleanupAndRedrawAnnotations(newAnnotations: List<AnnotationWrapper>) {
if (!newAnnotations.length) {
return;
return undefined;
}
const currentFilters = this._filterService.getGroup('primaryFilters')?.filters || [];
@ -765,7 +765,7 @@ export class FilePreviewScreenComponent
this.#handleDeltaAnnotationFilters(currentFilters);
}
this._annotationDrawService.draw(newAnnotations, this._skippedService.hideSkipped(), this.state.dossierTemplateId).then();
return this._annotationDrawService.draw(newAnnotations, this._skippedService.hideSkipped(), this.state.dossierTemplateId);
}
#handleDeltaAnnotationFilters(currentFilters: NestedFilter[]) {

View File

@ -1,7 +1,6 @@
import { computed, effect, Injectable, Injector, Signal } from '@angular/core';
import { computed, effect, inject, Injectable, Signal } from '@angular/core';
import { firstValueFrom, from, merge, Observable, of, pairwise, Subject, switchMap } from 'rxjs';
import { Dictionary, Dossier, DOSSIER_ID, DOSSIER_TEMPLATE_ID, File, FILE_ID } from '@red/domain';
import { Router } from '@angular/router';
import { FilesMapService } from '@services/files/files-map.service';
import { PermissionsService } from '@services/permissions.service';
import { getParam, LoadingService, wipeCache } from '@iqser/common-ui';
@ -34,7 +33,6 @@ function isDownload(event: HttpEvent<Blob>): event is HttpProgressEvent {
export class FilePreviewStateService {
readonly file$: Observable<File>;
readonly file: Signal<File>;
readonly dossier$: Observable<Dossier>;
readonly dossier: Signal<Dossier>;
readonly isReadonly: Signal<boolean>;
readonly isWritable: Signal<boolean>;
@ -54,23 +52,17 @@ export class FilePreviewStateService {
// );
constructor(
router: Router,
private readonly _filesMapService: FilesMapService,
private readonly _injector: Injector,
private readonly _permissionsService: PermissionsService,
private readonly _filesService: FilesService,
private readonly _dossiersService: DossiersService,
private readonly _fileManagementService: FileManagementService,
private readonly _dossierDictionariesMapService: DossierDictionariesMapService,
private readonly _dictionariesMapService: DictionariesMapService,
private readonly _translateService: TranslateService,
private readonly _loadingService: LoadingService,
private readonly _viewModeService: ViewModeService,
) {
const dossiersService = dossiersServiceResolver(_injector, router);
this.dossier$ = dossiersService.getEntityChanged$(this.dossierId);
this.dossier = toSignal(this.dossier$);
this.file$ = _filesMapService.watch$(this.dossierId, this.fileId);
this.dossier = toSignal(dossiersServiceResolver().getEntityChanged$(this.dossierId));
this.file$ = inject(FilesMapService).watch$(this.dossierId, this.fileId);
this.file = toSignal(this.file$);
// this.file$ = combineLatest([this.isAttached, file$]).pipe(
// filter(([isAttached]) => isAttached),
@ -83,7 +75,7 @@ export class FilePreviewStateService {
this.blob$ = this.#blob$;
this.dossierDictionary = toSignal(this._dossierDictionariesMapService.watch$(this.dossierId, 'dossier_redaction'));
this.dossierDictionary = toSignal(inject(DossierDictionariesMapService).watch$(this.dossierId, 'dossier_redaction'));
this.#dossierFileChange = toSignal(this.#dossierFilesChange$);
effect(() => {
if (this.#dossierFileChange()) {

View File

@ -2,11 +2,10 @@ import { Injectable } from '@angular/core';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { bool } from '@iqser/common-ui';
import { Core } from '@pdftron/webviewer';
import Annotation = Core.Annotations.Annotation;
import { REDAnnotationManager } from '../../pdf-viewer/services/annotation-manager.service';
import { UserPreferenceService } from '@users/user-preference.service';
import { AnnotationDrawService } from '../../pdf-viewer/services/annotation-draw.service';
import { ReadableRedactionsService } from '../../pdf-viewer/services/readable-redactions.service';
import Annotation = Core.Annotations.Annotation;
@Injectable()
export class SuggestionsService {
@ -15,7 +14,6 @@ export class SuggestionsService {
constructor(
private readonly _annotationManager: REDAnnotationManager,
private readonly _userPreferenceService: UserPreferenceService,
private readonly _annotationDrawService: AnnotationDrawService,
private readonly _readableRedactionsService: ReadableRedactionsService,
) {}

View File

@ -7,9 +7,7 @@ import { RedactionLogService } from '@services/files/redaction-log.service';
import { IRectangle, ISectionGrid, ISectionRectangle, SuperTypes } from '@red/domain';
import { firstValueFrom } from 'rxjs';
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
import { PdfViewer } from './pdf-viewer.service';
import { ActivatedRoute } from '@angular/router';
import { REDAnnotationManager } from './annotation-manager.service';
import { List } from '@iqser/common-ui';
import { REDDocumentViewer } from './document-viewer.service';
@ -23,10 +21,8 @@ const DEFAULT_REMOVED_ANNOTATION_OPACITY = 0.2;
@Injectable()
export class AnnotationDrawService {
constructor(
private readonly _dictionariesMapService: DictionariesMapService,
private readonly _redactionLogService: RedactionLogService,
private readonly _userPreferenceService: UserPreferenceService,
private readonly _activatedRoute: ActivatedRoute,
private readonly _annotationManager: REDAnnotationManager,
private readonly _pdf: PdfViewer,
private readonly _documentViewer: REDDocumentViewer,
@ -35,7 +31,7 @@ export class AnnotationDrawService {
async draw(annotations: List<AnnotationWrapper>, hideSkipped: boolean, dossierTemplateId: string) {
try {
this._pdf.runWithCleanup(() => this._draw(annotations, hideSkipped, dossierTemplateId)).then();
return this._pdf.runWithCleanup(() => this._draw(annotations, hideSkipped, dossierTemplateId));
} catch (e) {
console.error(e);
}

View File

@ -144,12 +144,12 @@ export class REDDocumentViewer {
}
#listenForDocEvents() {
// this.#document.addEventListener('textSelected', ([, selectedText, pageNumber]: [Quad, string, number]) => {
// this.#ngZone.run(() => {
// this.#disableTextPopupIfCompareMode(pageNumber);
// this.#selectedText.set(selectedText);
// });
// });
this.#document.addEventListener('textSelected', (quads: Quad, selectedText: string, pageNumber: number) => {
this.#ngZone.run(() => {
this.#disableTextPopupIfCompareMode(pageNumber);
this.#selectedText.set(selectedText);
});
});
this.#document.addEventListener('pageComplete', event => {
this.#ngZone.run(() => {
@ -187,7 +187,7 @@ export class REDDocumentViewer {
await pdfDoc.flattenAnnotations(false);
}
#disableTextPopupIfCompareMode(pageNumber) {
#disableTextPopupIfCompareMode(pageNumber: number) {
if (this.#pdf.isCompareMode() && pageNumber % 2 === 0) {
return this.#pdf.disable('textPopup');
}

View File

@ -42,9 +42,8 @@ export class EditorComponent implements OnInit, OnChanges {
editorOptions: IStandaloneEditorConstructionOptions = {};
codeEditor: ICodeEditor;
value: string;
protected _initialEntriesMap = new Set<string>();
protected readonly _editorTextChanged$ = new Subject<string>();
#initialEntriesSet = new Set<string>();
private _diffEditor: IDiffEditor;
private _decorations: string[] = [];
@ -120,7 +119,7 @@ export class EditorComponent implements OnInit, OnChanges {
revert() {
this.value = this.initialEntries.join('\n');
this._initialEntriesMap = new Set<string>(this.initialEntries);
this.#initialEntriesSet = new Set<string>(this.initialEntries);
this.diffValue = this.value;
this._diffEditor?.getModifiedEditor().setValue(this.diffValue);
this._editorTextChanged$.next(this.value);
@ -136,7 +135,7 @@ export class EditorComponent implements OnInit, OnChanges {
continue;
}
if (this._initialEntriesMap.has(entry)) {
if (this.#initialEntriesSet.has(entry)) {
continue;
}

View File

@ -1,10 +1,10 @@
import { Router } from '@angular/router';
import { Injector, ProviderToken } from '@angular/core';
import { inject, Injector, ProviderToken } from '@angular/core';
import { DossiersService } from '../dossiers/dossiers.service';
/** Usage in components or services is only allowed in guards or in constructors.
* Otherwise, it causes errors on navigation to other screens if the component is reused. */
export const dossiersServiceResolver = (injector: Injector, router: Router) => {
export const dossiersServiceResolver = (injector = inject(Injector), router = inject(Router)) => {
let route = router.routerState.root;
while (route.firstChild) {
route = route.firstChild;

View File

@ -1,7 +1,7 @@
{
"ADMIN_CONTACT_NAME": null,
"ADMIN_CONTACT_URL": null,
"API_URL": "https://dev-08.iqser.cloud/redaction-gateway-v1",
"API_URL": "https://dev-04.iqser.cloud/redaction-gateway-v1",
"APP_NAME": "RedactManager",
"AUTO_READ_TIME": 3,
"BACKEND_APP_VERSION": "4.4.40",
@ -11,7 +11,7 @@
"MAX_RETRIES_ON_SERVER_ERROR": 3,
"OAUTH_CLIENT_ID": "redaction",
"OAUTH_IDP_HINT": null,
"OAUTH_URL": "https://dev-08.iqser.cloud/auth",
"OAUTH_URL": "https://dev-04.iqser.cloud/auth",
"RECENT_PERIOD_IN_HOURS": 24,
"SELECTION_MODE": "structural",
"MANUAL_BASE_URL": "https://docs.redactmanager.com/preview",

0
package-lock.json generated
View File

View File

@ -1,6 +1,6 @@
{
"name": "redaction",
"version": "4.102.0",
"version": "4.126.0",
"private": true,
"license": "MIT",
"scripts": {
@ -22,30 +22,30 @@
"*.{ts,js,html}": "eslint --fix"
},
"dependencies": {
"@angular/animations": "16.0.3",
"@angular/cdk": "16.0.2",
"@angular/common": "16.0.3",
"@angular/compiler": "16.0.3",
"@angular/core": "16.0.3",
"@angular/forms": "16.0.3",
"@angular/material": "16.0.2",
"@angular/platform-browser": "16.0.3",
"@angular/platform-browser-dynamic": "16.0.3",
"@angular/router": "16.0.3",
"@angular/service-worker": "16.0.3",
"@angular/animations": "16.1.1",
"@angular/cdk": "16.1.1",
"@angular/common": "16.1.1",
"@angular/compiler": "16.1.1",
"@angular/core": "16.1.1",
"@angular/forms": "16.1.1",
"@angular/material": "16.1.1",
"@angular/platform-browser": "16.1.1",
"@angular/platform-browser-dynamic": "16.1.1",
"@angular/router": "16.1.1",
"@angular/service-worker": "16.1.1",
"@materia-ui/ngx-monaco-editor": "^6.0.0",
"@messageformat/core": "^3.1.0",
"@ngx-translate/core": "15.0.0",
"@ngx-translate/http-loader": "8.0.0",
"@pdftron/webviewer": "10.1.0",
"@swimlane/ngx-charts": "20.3.0",
"@pdftron/webviewer": "10.1.1",
"@swimlane/ngx-charts": "20.4.1",
"dayjs": "^1.11.5",
"file-saver": "^2.0.5",
"jwt-decode": "^3.1.2",
"keycloak-angular": "14.0.0",
"keycloak-js": "21.1.1",
"lodash-es": "^4.17.21",
"monaco-editor": "0.38.0",
"monaco-editor": "0.39.0",
"ngx-color-picker": "^14.0.0",
"ngx-logger": "^5.0.11",
"ngx-toastr": "17.0.2",
@ -53,39 +53,39 @@
"object-hash": "^3.0.0",
"papaparse": "^5.4.0",
"rxjs": "7.8.1",
"sass": "^1.59.2",
"sass": "1.63.4",
"scroll-into-view-if-needed": "^3.0.6",
"streamsaver": "^2.0.5",
"tslib": "2.5.2",
"zone.js": "0.13.0",
"@nx/angular": "16.3.0"
"tslib": "2.5.3",
"zone.js": "0.13.1",
"@nx/angular": "16.3.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "16.0.3",
"@angular-devkit/core": "16.0.3",
"@angular-devkit/schematics": "16.0.3",
"@angular-devkit/build-angular": "16.1.0",
"@angular-devkit/core": "16.1.0",
"@angular-devkit/schematics": "16.1.0",
"@angular-eslint/builder": "16.0.3",
"@angular-eslint/eslint-plugin": "16.0.3",
"@angular-eslint/eslint-plugin-template": "16.0.3",
"@angular-eslint/schematics": "16.0.3",
"@angular-eslint/template-parser": "16.0.3",
"@angular/cli": "16.0.3",
"@angular/compiler-cli": "16.0.3",
"@angular/language-service": "16.0.3",
"@angular/cli": "16.1.0",
"@angular/compiler-cli": "16.1.1",
"@angular/language-service": "16.1.1",
"@bartholomej/ngx-translate-extract": "^8.0.2",
"@nx/eslint-plugin": "16.3.0",
"@nx/jest": "16.3.0",
"@nx/linter": "16.3.0",
"@nx/workspace": "16.3.0",
"@schematics/angular": "16.0.3",
"@nx/eslint-plugin": "16.3.2",
"@nx/jest": "16.3.2",
"@nx/linter": "16.3.2",
"@nx/workspace": "16.3.2",
"@schematics/angular": "16.1.0",
"@types/jest": "29.5.2",
"@types/lodash-es": "^4.17.6",
"@types/node": "20.2.5",
"@typescript-eslint/eslint-plugin": "5.59.8",
"@typescript-eslint/parser": "5.59.8",
"@types/node": "20.3.1",
"@typescript-eslint/eslint-plugin": "5.59.11",
"@typescript-eslint/parser": "5.59.11",
"axios": "^1.3.4",
"dotenv": "16.1.3",
"eslint": "8.41.0",
"dotenv": "16.1.4",
"eslint": "8.42.0",
"eslint-config-prettier": "8.8.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-rxjs": "^5.0.2",
@ -93,22 +93,22 @@
"husky": "^8.0.3",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"jest-extended": "^3.2.4",
"jest-extended": "4.0.0",
"jest-preset-angular": "13.1.1",
"lint-staged": "^13.2.0",
"nx": "16.3.0",
"nx": "16.3.2",
"nx-cloud": "16.0.5",
"postcss": "8.4.24",
"postcss-import": "15.1.0",
"postcss-preset-env": "8.4.1",
"postcss-preset-env": "8.5.0",
"postcss-url": "10.1.3",
"prettier": "2.8.8",
"sonarqube-scanner": "^3.0.1",
"superagent": "^8.0.9",
"superagent-promise": "^1.1.0",
"ts-node": "10.9.1",
"typescript": "5.0.4",
"webpack": "5.85.0",
"typescript": "5.1.3",
"webpack": "5.87.0",
"webpack-bundle-analyzer": "^4.8.0",
"xliff": "^6.1.0"
}

5013
yarn.lock

File diff suppressed because it is too large Load Diff