RED-3687 - WIP on removing subscriptions

This commit is contained in:
Valentin Mihai 2022-07-13 21:11:30 +03:00
parent e030b20f04
commit 8716739b52
6 changed files with 150 additions and 106 deletions

View File

@ -1,39 +1,34 @@
<ng-container *ngIf="file$ | async as file">
<ng-container *ngIf="dossier$ | async as dossier">
<div *ngFor="let comment of annotation.comments; trackBy: trackBy" class="comment">
<div class="comment-details-wrapper">
<div [matTooltipPosition]="'above'" [matTooltip]="comment.date | date: 'exactDate'" class="small-label">
<strong> {{ comment.user | name }} </strong>
{{ comment.date | date: 'sophisticatedDate' }}
</div>
<div class="comment-actions">
<iqser-circle-button
(action)="deleteComment($event, comment)"
*ngIf="permissionsService.canDeleteComment(comment, file, dossier)"
[iconSize]="10"
[size]="20"
class="pointer"
icon="iqser:trash"
></iqser-circle-button>
</div>
<ng-container *ngIf="componentContext$ | async as ctx">
<div *ngFor="let comment of annotation.comments; trackBy: trackBy" class="comment">
<div class="comment-details-wrapper">
<div [matTooltipPosition]="'above'" [matTooltip]="comment.date | date: 'exactDate'" class="small-label">
<strong> {{ comment.user | name }} </strong>
{{ comment.date | date: 'sophisticatedDate' }}
</div>
<div>{{ comment.text }}</div>
<div class="comment-actions">
<iqser-circle-button
(action)="deleteComment($event, comment)"
*ngIf="permissionsService.canDeleteComment(comment, ctx.file, ctx.dossier)"
[iconSize]="10"
[size]="20"
class="pointer"
icon="iqser:trash"
></iqser-circle-button>
</div>
</div>
<iqser-input-with-action
(action)="addComment($event)"
*ngIf="permissionsService.canAddComment(file, dossier)"
[placeholder]="'comments.add-comment' | translate"
autocomplete="off"
icon="iqser:collapse"
width="full"
></iqser-input-with-action>
</ng-container>
<div>{{ comment.text }}</div>
</div>
<iqser-input-with-action
(action)="addComment($event)"
*ngIf="permissionsService.canAddComment(ctx.file, ctx.dossier)"
[placeholder]="'comments.add-comment' | translate"
autocomplete="off"
icon="iqser:collapse"
width="full"
></iqser-input-with-action>
<div (click)="toggleExpandComments($event)" class="all-caps-label pointer hide-comments" translate="comments.hide-comments"></div>
</ng-container>
<div (click)="toggleExpandComments($event)" class="all-caps-label pointer hide-comments" translate="comments.hide-comments"></div>
<!-- A hack to avoid subscribing in component -->
<ng-container *ngIf="hiddenComments$ | async"></ng-container>

View File

@ -1,14 +1,15 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostBinding, Input, OnChanges, ViewChild } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostBinding, Input, OnInit, ViewChild } from '@angular/core';
import { Dossier, File, IComment } from '@red/domain';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { UserService } from '@services/user.service';
import { PermissionsService } from '@services/permissions.service';
import { AutoUnsubscribe, InputWithActionComponent, LoadingService, trackByFactory } from '@iqser/common-ui';
import { InputWithActionComponent, LoadingService, trackByFactory } from '@iqser/common-ui';
import { firstValueFrom, Observable } from 'rxjs';
import { CommentingService } from '../../services/commenting.service';
import { tap } from 'rxjs/operators';
import { FilePreviewStateService } from '../../services/file-preview-state.service';
import { ManualRedactionService } from '../../services/manual-redaction.service';
import { ContextComponent } from '@utils/context.component';
@Component({
selector: 'redaction-comments',
@ -16,7 +17,7 @@ import { ManualRedactionService } from '../../services/manual-redaction.service'
styleUrls: ['./comments.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CommentsComponent extends AutoUnsubscribe implements OnChanges {
export class CommentsComponent extends ContextComponent implements OnInit {
@Input() annotation: AnnotationWrapper;
readonly trackBy = trackByFactory();
readonly file$: Observable<File>;
@ -39,12 +40,14 @@ export class CommentsComponent extends AutoUnsubscribe implements OnChanges {
this.dossier$ = _stateService.dossier$;
}
ngOnChanges() {
ngOnInit() {
this.hiddenComments$ = this._commentingService.isActive$(this.annotation.id).pipe(
tap(active => {
this._hidden = !active;
}),
);
super._initContext([{ file: this.file$ }, { dossier: this.dossier$ }, this.hiddenComments$]);
}
async addComment(value: string): Promise<void> {

View File

@ -1,22 +1,22 @@
<ng-container *ngIf="state.dossier$ | async as dossier">
<section *ngIf="file$ | async as file" [class.fullscreen]="fullScreen">
<ng-container *ngIf="componentContext$ | async as ctx">
<section *ngIf="ctx.dossier && ctx.file" [class.fullscreen]="fullScreen">
<div class="page-header">
<div class="flex flex-1">
<redaction-view-switch (switchView)="switchView($event)"></redaction-view-switch>
</div>
<div class="flex-1 actions-container">
<redaction-processing-indicator [file]="file" class="mr-16"></redaction-processing-indicator>
<redaction-processing-indicator [file]="ctx.file" class="mr-16"></redaction-processing-indicator>
<redaction-user-management></redaction-user-management>
<ng-container *ngIf="permissionsService.isApprover(dossier) && !!file.lastReviewer">
<ng-container *ngIf="permissionsService.isApprover(ctx.dossier) && !!ctx.file.lastReviewer">
<div class="vertical-line"></div>
<div class="all-caps-label mr-16 ml-8">
{{ 'file-preview.last-assignee' | translate: { status: file.workflowStatus } }}
{{ 'file-preview.last-assignee' | translate: { status: ctx.file.workflowStatus } }}
</div>
<redaction-initials-avatar
[user]="file.isApproved ? file.lastApprover : file.lastReviewer"
[user]="ctx.file.isApproved ? ctx.file.lastApprover : ctx.file.lastReviewer"
[withName]="true"
></redaction-initials-avatar>
</ng-container>
@ -24,8 +24,8 @@
<div class="vertical-line"></div>
<redaction-file-actions
[dossier]="dossier"
[file]="file"
[dossier]="ctx.dossier"
[file]="ctx.file"
fileActionsHelpModeKey="document_features_in_editor"
type="file-preview"
></redaction-file-actions>
@ -40,7 +40,7 @@
<!-- Dev Mode Features-->
<iqser-circle-button
(action)="downloadOriginalFile(file)"
(action)="downloadOriginalFile(ctx.file)"
*ngIf="userPreferenceService.areDevFeaturesEnabled"
[tooltip]="'file-preview.download-original-file' | translate"
[type]="circleButtonTypes.primary"
@ -53,7 +53,7 @@
<iqser-circle-button
(action)="closeFullScreen()"
*ngIf="!fullScreen"
[routerLink]="dossier.routerLink"
[routerLink]="ctx.dossier.routerLink"
[tooltip]="'common.close' | translate"
class="ml-8"
icon="iqser:close"

View File

@ -1,8 +1,7 @@
import { ChangeDetectorRef, Component, HostListener, Injector, NgZone, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { ChangeDetectorRef, Component, HostListener, Injector, NgZone, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { ActivatedRoute, ActivatedRouteSnapshot, NavigationExtras, Router } from '@angular/router';
import { Core } from '@pdftron/webviewer';
import {
AutoUnsubscribe,
bool,
CircleButtonTypes,
CustomError,
@ -12,7 +11,6 @@ import {
LoadingService,
NestedFilter,
OnAttach,
OnDetach,
processFilters,
} from '@iqser/common-ui';
import { MatDialogState } from '@angular/material/dialog';
@ -52,6 +50,7 @@ import { REDDocumentViewer } from '../pdf-viewer/services/document-viewer.servic
import { AnnotationsListingService } from './services/annotations-listing.service';
import { PdfProxyService } from './services/pdf-proxy.service';
import Annotation = Core.Annotations.Annotation;
import { ContextComponent } from '@utils/context.component';
const textActions = [TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE];
@ -60,13 +59,13 @@ const textActions = [TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE];
styleUrls: ['./file-preview-screen.component.scss'],
providers: filePreviewScreenProviders,
})
export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnInit, OnDestroy, OnAttach, OnDetach, ComponentCanDeactivate {
readonly circleButtonTypes = CircleButtonTypes;
fullScreen = false;
export class FilePreviewScreenComponent extends ContextComponent implements OnInit, OnAttach, ComponentCanDeactivate {
readonly fileId = this.state.fileId;
readonly dossierId = this.state.dossierId;
readonly file$ = this.state.file$.pipe(tap(file => this._fileDataService.loadAnnotations(file)));
readonly circleButtonTypes = CircleButtonTypes;
fullScreen = false;
@ViewChild('annotationFilterTemplate', {
read: TemplateRef,
static: false,
@ -192,7 +191,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
ngOnDetach() {
this._viewerHeaderService.resetCompareButtons();
super.ngOnDetach();
this._changeRef.markForCheck();
}
@ -219,7 +217,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
this._loadingService.start();
await this.userPreferenceService.saveLastOpenedFileForDossier(this.dossierId, this.fileId);
this._subscribeToFileUpdates();
this._initContext();
if (file?.analysisRequired && !file.excludedFromAutomaticAnalysis) {
const reanalysisService = this._injector.get(ReanalysisService);
@ -487,59 +485,66 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}, 100);
}
private _subscribeToFileUpdates(): void {
this.addActiveScreenSubscription = this.loadAnnotations().subscribe();
protected _initContext(): void {
const loadAnnotations$ = this.loadAnnotations();
this.addActiveScreenSubscription = this._dossiersService
.getEntityDeleted$(this.dossierId)
.pipe(tap(() => this._handleDeletedDossier()))
.subscribe();
const handleDeletedDossier$ = this._dossiersService.getEntityDeleted$(this.dossierId).pipe(tap(() => this._handleDeletedDossier()));
this.addActiveScreenSubscription = this._filesMapService
.watchDeleted$(this.fileId)
.pipe(tap(() => this._handleDeletedFile()))
.subscribe();
const watchDeleted$ = this._filesMapService.watchDeleted$(this.fileId).pipe(tap(() => this._handleDeletedFile()));
this.addActiveScreenSubscription = combineLatest([this._viewModeService.viewMode$, this.state.file$])
.pipe(
tap(([viewMode, file]) => {
if (viewMode === ViewModes.TEXT_HIGHLIGHTS && !file.hasHighlights) {
this._viewModeService.switchToStandard();
}
}),
)
.subscribe();
this.addActiveScreenSubscription = this._documentViewer.pageComplete$.subscribe(() => {
this._setExcludedPageStyles();
});
this.addActiveScreenSubscription = this._documentViewer.keyUp$.subscribe($event => {
this.handleKeyEvent($event);
});
this.addActiveScreenSubscription = this.#textSelected$.subscribe();
this.addActiveScreenSubscription = this.state.dossierFileChange$.subscribe();
this.addActiveScreenSubscription = this.state.blob$
.pipe(
switchMap(blob => from(this._documentViewer.lock()).pipe(map(() => blob))),
tap(() => this._errorService.clear()),
tap(blob => this.pdf.loadDocument(blob, this.state.file, () => this.state.reloadBlob())),
)
.subscribe();
this.addActiveScreenSubscription = this.pdfProxyService.manualAnnotationRequested$.subscribe($event => {
this.openManualAnnotationDialog($event);
});
this.addActiveScreenSubscription = this.pdfProxyService.pageChanged$.subscribe(page =>
this._ngZone.run(() => {
console.log('viewerPageChanged', page);
return this.#updateQueryParamsPage(page);
const switchToStandard$ = combineLatest([this._viewModeService.viewMode$, this.state.file$]).pipe(
tap(([viewMode, file]) => {
if (viewMode === ViewModes.TEXT_HIGHLIGHTS && !file.hasHighlights) {
this._viewModeService.switchToStandard();
}
}),
);
this.addActiveScreenSubscription = this.pdfProxyService.annotationSelected$.subscribe();
const excludedPageStyles$ = this._documentViewer.pageComplete$.pipe(tap(() => this._setExcludedPageStyles()));
const handleKeyEvent$ = this._documentViewer.keyUp$.pipe(tap($event => this.handleKeyEvent($event)));
const textSelected$ = this.#textSelected$;
const blobLoadDocument$ = this.state.blob$.pipe(
switchMap(blob => from(this._documentViewer.lock()).pipe(map(() => blob))),
tap(() => this._errorService.clear()),
tap(blob => this.pdf.loadDocument(blob, this.state.file, () => this.state.reloadBlob())),
);
const openManualAnnotationDialog$ = this.pdfProxyService.manualAnnotationRequested$.pipe(
tap($event => {
this.openManualAnnotationDialog($event);
}),
);
const updateQueryParamsPage$ = this.pdfProxyService.pageChanged$.pipe(
tap(page => {
this._ngZone.run(() => {
console.log('viewerPageChanged', page);
return this.#updateQueryParamsPage(page);
});
}),
);
super._initContext([
{ dossier: this.state.dossier$ },
{ file: this.file$ },
this.pdf.currentPage$,
this.pdfProxyService.canPerformAnnotationActions$,
this.state.dossierFileChange$,
this.pdfProxyService.annotationSelected$,
loadAnnotations$,
handleDeletedDossier$,
watchDeleted$,
switchToStandard$,
excludedPageStyles$,
handleKeyEvent$,
textSelected$,
blobLoadDocument$,
openManualAnnotationDialog$,
updateQueryParamsPage$,
]);
}
private _handleDeletedDossier(): void {

View File

@ -136,7 +136,7 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
}
getTitle(type: ManualRedactionEntryType, dossier: Dossier) {
if (this._permissionsService.isApprover(dossier)) {
if (dossier && this._permissionsService.isApprover(dossier)) {
switch (type) {
case 'DICTIONARY':
return _('manual-annotation.dialog.header.dictionary');

View File

@ -0,0 +1,41 @@
import { combineLatest, Observable, of } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
type ContextObservable = Observable<unknown> | { [key: string]: Observable<unknown> };
export class ContextComponent {
componentContext$: Observable<any> = of({});
#templateContextKeys = {};
#observables = [];
protected _initContext(observables: ContextObservable[]) {
this.#extractKeysAndObservables(observables);
this.componentContext$ = combineLatest(this.#observables).pipe(map(values => this.#mapContext(values)));
}
#extractKeysAndObservables(observables: ContextObservable[]) {
observables.forEach((value, index) => {
const keys = Object.keys(value);
let observable: any;
if (keys.length === 1) {
this.#templateContextKeys[index] = keys[0];
observable = value[keys[0]];
} else {
observable = value;
}
this.#observables.push(observable.pipe(startWith(null)));
});
}
#mapContext(values: Array<unknown>) {
const context = {};
values.forEach((value, index) => {
const key = this.#templateContextKeys[index];
context[key ? key : index] = value;
});
return context;
}
}