diff --git a/apps/red-ui/src/app/app.component.html b/apps/red-ui/src/app/app.component.html
index ff64625af..20a8fecb2 100644
--- a/apps/red-ui/src/app/app.component.html
+++ b/apps/red-ui/src/app/app.component.html
@@ -1,3 +1,4 @@
+
diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/screen/dossier-overview-screen.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/screen/dossier-overview-screen.component.ts
index bf8865bdd..19e8e7e71 100644
--- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/screen/dossier-overview-screen.component.ts
+++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/screen/dossier-overview-screen.component.ts
@@ -15,19 +15,19 @@ import { FileDropOverlayService } from '@upload-download/services/file-drop-over
import { FileUploadModel } from '@upload-download/model/file-upload.model';
import { FileUploadService } from '@upload-download/services/file-upload.service';
import { StatusOverlayService } from '@upload-download/services/status-overlay.service';
-import * as moment from 'moment';
-import { Observable, timer } from 'rxjs';
+import { Observable, Subscription } from 'rxjs';
import { filter, skip, switchMap, tap } from 'rxjs/operators';
import { convertFiles, Files, handleFileDrop } from '@utils/index';
import {
CircleButtonTypes,
+ CustomError,
DefaultListingServices,
+ ErrorService,
ListingComponent,
ListingModes,
LoadingService,
NestedFilter,
OnAttach,
- OnDetach,
TableColumnConfig,
TableComponent,
WorkflowConfig,
@@ -45,7 +45,7 @@ import { LongPressEvent } from '@shared/directives/long-press.directive';
import { UserPreferenceService } from '@services/user-preference.service';
import { FilesMapService } from '@services/entity-services/files-map.service';
import { FilesService } from '@services/entity-services/files.service';
-import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
+import { DossierStatsService } from '@services/entity-services/dossier-stats.service';
@Component({
templateUrl: './dossier-overview-screen.component.html',
@@ -53,7 +53,7 @@ import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DossierOverviewScreenComponent) }],
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class DossierOverviewScreenComponent extends ListingComponent implements OnInit, OnDestroy, OnDetach, OnAttach {
+export class DossierOverviewScreenComponent extends ListingComponent implements OnInit, OnDestroy, OnAttach {
readonly listingModes = ListingModes;
readonly circleButtonTypes = CircleButtonTypes;
readonly tableHeaderLabel = _('dossier-overview.table-header.title');
@@ -74,6 +74,7 @@ export class DossierOverviewScreenComponent extends ListingComponent imple
@ViewChild('fileInput', { static: true }) private readonly _fileInput: ElementRef;
@ViewChild(TableComponent) private readonly _tableComponent: TableComponent;
private _fileAttributeConfigs: IFileAttributeConfig[];
+ private readonly _removableSubscriptions = new Subscription();
constructor(
protected readonly _injector: Injector,
@@ -81,6 +82,7 @@ export class DossierOverviewScreenComponent extends ListingComponent imple
readonly permissionsService: PermissionsService,
private readonly _loadingService: LoadingService,
private readonly _dossiersService: DossiersService,
+ private readonly _dossierStatsService: DossierStatsService,
private readonly _dossierTemplatesService: DossierTemplatesService,
private readonly _appConfigService: AppConfigService,
private readonly _fileUploadService: FileUploadService,
@@ -92,6 +94,7 @@ export class DossierOverviewScreenComponent extends ListingComponent imple
readonly configService: ConfigService,
private readonly _userPreferenceService: UserPreferenceService,
private readonly _fileMapService: FilesMapService,
+ private readonly _errorService: ErrorService,
activatedRoute: ActivatedRoute,
) {
super(_injector);
@@ -128,18 +131,17 @@ export class DossierOverviewScreenComponent extends ListingComponent imple
this._fileDropOverlayService.initFileDropHandling(this.dossierId);
- this.addSubscription = timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL)
- .pipe(
- switchMap(() => this._filesService.hasChanges$(this.dossierId)),
- filter(changed => changed),
- switchMap(() => this._reloadFiles()),
- )
- .subscribe();
-
this.addSubscription = this.configService.listingMode$.subscribe(() => {
this._computeAllFilters();
});
+ this.addSubscription = this._dossiersService.dossierFileChanges$
+ .pipe(
+ filter(dossierId => dossierId === this.dossierId),
+ switchMap(dossierId => this._filesService.loadAll(dossierId)),
+ )
+ .subscribe();
+
this.addSubscription = this._dossierTemplatesService
.getEntityChanged$(this.currentDossier.dossierTemplateId)
.pipe(
@@ -166,11 +168,10 @@ export class DossierOverviewScreenComponent extends ListingComponent imple
ngOnAttach() {
this._fileDropOverlayService.initFileDropHandling(this.dossierId);
+ this._setRemovableSubscriptions();
this._tableComponent?.scrollToLastIndex();
}
- ngOnDetach() {}
-
forceReanalysisAction($event: LongPressEvent) {
this.analysisForced = !$event.touchEnd && this._userPreferenceService.areDevFeaturesEnabled;
}
@@ -192,8 +193,20 @@ export class DossierOverviewScreenComponent extends ListingComponent imple
(this._fileInput as any).nativeElement.value = null;
}
- recentlyModifiedChecker = (file: File) =>
- moment(file.lastUpdated).add(this._appConfigService.values.RECENT_PERIOD_IN_HOURS, 'hours').isAfter(moment());
+ private _setRemovableSubscriptions(): void {
+ this._removableSubscriptions.add(
+ this._dossiersService
+ .getEntityDeleted$(this.dossierId)
+ .pipe(tap(() => this._handleDeletedDossier()))
+ .subscribe(),
+ );
+ }
+
+ private _handleDeletedDossier(): void {
+ this._errorService.set(
+ new CustomError(_('error.deleted-entity.dossier.label'), _('error.deleted-entity.dossier.action'), 'iqser:expand'),
+ );
+ }
private _updateFileAttributes(): void {
this._fileAttributeConfigs =
@@ -204,11 +217,6 @@ export class DossierOverviewScreenComponent extends ListingComponent imple
this._computeAllFilters();
}
- private async _reloadFiles() {
- await this._filesService.loadAll(this.dossierId).toPromise();
- this._computeAllFilters();
- }
-
private _loadEntitiesFromState() {
this.currentDossier = this._dossiersService.find(this.dossierId);
this._computeAllFilters();
diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/screen/dossiers-listing-screen.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/screen/dossiers-listing-screen.component.ts
index c7ba3f10b..d70a2ae00 100644
--- a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/screen/dossiers-listing-screen.component.ts
+++ b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/screen/dossiers-listing-screen.component.ts
@@ -1,19 +1,17 @@
-import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
+import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { Dossier } from '@red/domain';
import { UserService } from '@services/user.service';
import { PermissionsService } from '@services/permissions.service';
import { TranslateChartService } from '@services/translate-chart.service';
-import { timer } from 'rxjs';
import { Router } from '@angular/router';
import { DossiersDialogService } from '../../../services/dossiers-dialog.service';
-import { DefaultListingServicesTmp, EntitiesService, ListingComponent, OnAttach, OnDetach, TableComponent } from '@iqser/common-ui';
+import { DefaultListingServicesTmp, EntitiesService, ListingComponent, OnAttach, TableComponent } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { ConfigService } from '../config.service';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { FilesService } from '@services/entity-services/files.service';
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
-import { switchMap, tap } from 'rxjs/operators';
-import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
+import { tap } from 'rxjs/operators';
@Component({
templateUrl: './dossiers-listing-screen.component.html',
@@ -25,7 +23,7 @@ import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class DossiersListingScreenComponent extends ListingComponent implements OnInit, OnDestroy, OnAttach, OnDetach {
+export class DossiersListingScreenComponent extends ListingComponent implements OnInit, OnAttach {
readonly currentUser = this._userService.currentUser;
readonly tableColumnConfigs = this._configService.tableConfig;
readonly tableHeaderLabel = _('dossier-listing.table-header.title');
@@ -57,22 +55,13 @@ export class DossiersListingScreenComponent extends ListingComponent im
}
ngOnInit(): void {
- this.addSubscription = timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL)
- .pipe(switchMap(() => this._dossiersService.loadAllIfChanged()))
- .subscribe();
-
this.addSubscription = this._dossiersService.all$.pipe(tap(() => this._computeAllFilters())).subscribe();
}
ngOnAttach(): void {
- this.ngOnInit();
this._tableComponent?.scrollToLastIndex();
}
- ngOnDetach(): void {
- this.ngOnDestroy();
- }
-
openAddDossierDialog(): void {
this._dialogService.openDialog('addDossier', null, null, async (addResponse: { dossier: Dossier; addMembers: boolean }) => {
await this._router.navigate([addResponse.dossier.routerLink]);
diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts
index 445d22bbd..4ff4c076a 100644
--- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts
+++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts
@@ -5,7 +5,9 @@ import { PdfViewerComponent } from './components/pdf-viewer/pdf-viewer.component
import {
AutoUnsubscribe,
CircleButtonTypes,
+ CustomError,
Debounce,
+ ErrorService,
FilterService,
LoadingService,
OnAttach,
@@ -43,19 +45,12 @@ import { ViewModeService } from './services/view-mode.service';
import { MultiSelectService } from './services/multi-select.service';
import { DocumentInfoService } from './services/document-info.service';
import { ReanalysisService } from '../../../../services/reanalysis.service';
+import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import Annotation = Core.Annotations.Annotation;
import PDFNet = Core.PDFNet;
const ALL_HOTKEY_ARRAY = ['Escape', 'F', 'f', 'ArrowUp', 'ArrowDown'];
-function diff(first: readonly T[], second: readonly T[]): T[] {
- // symmetrical difference between two arrays
- const a = new Set(first);
- const b = new Set(second);
-
- return [...first.filter(x => !b.has(x)), ...second.filter(x => !a.has(x))];
-}
-
@Component({
templateUrl: './file-preview-screen.component.html',
styleUrls: ['./file-preview-screen.component.scss'],
@@ -108,6 +103,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
private readonly _filesMapService: FilesMapService,
private readonly _dossiersService: DossiersService,
private readonly _reanalysisService: ReanalysisService,
+ private readonly _errorService: ErrorService,
readonly excludedPagesService: ExcludedPagesService,
readonly viewModeService: ViewModeService,
readonly multiSelectService: MultiSelectService,
@@ -190,7 +186,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
ngOnDetach(): void {
this.displayPdfViewer = false;
- super.ngOnDestroy();
+ super.ngOnDetach();
this._changeDetectorRef.markForCheck();
}
@@ -529,10 +525,10 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
private _subscribeToFileUpdates(): void {
- this.addSubscription = timer(0, 5000)
+ this.addActiveScreenSubscription = timer(0, 5000)
.pipe(switchMap(() => this._filesService.reload(this.dossierId, this.fileId)))
.subscribe();
- this.addSubscription = this._filesMapService.fileReanalysed$
+ this.addActiveScreenSubscription = this._filesMapService.fileReanalysed$
.pipe(filter(file => file.fileId === this.fileId))
.subscribe(async file => {
if (file.lastProcessed !== this.fileData?.file.lastProcessed) {
@@ -541,6 +537,28 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
this._loadingService.stop();
});
+
+ this.addActiveScreenSubscription = this._dossiersService
+ .getEntityDeleted$(this.dossierId)
+ .pipe(tap(() => this._handleDeletedDossier()))
+ .subscribe();
+
+ this.addActiveScreenSubscription = this._filesMapService
+ .watchDeleted$(this.fileId)
+ .pipe(tap(() => this._handleDeletedFile()))
+ .subscribe();
+ }
+
+ private _handleDeletedDossier(): void {
+ this._errorService.set(
+ new CustomError(_('error.deleted-entity.file-dossier.label'), _('error.deleted-entity.file-dossier.action'), 'iqser:expand'),
+ );
+ }
+
+ private _handleDeletedFile(): void {
+ this._errorService.set(
+ new CustomError(_('error.deleted-entity.file.label'), _('error.deleted-entity.file.action'), 'iqser:expand'),
+ );
}
private async _loadFileData(file: File): Promise {
diff --git a/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.ts b/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.ts
index 150f44c55..95a6427cd 100644
--- a/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.ts
+++ b/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.ts
@@ -1,5 +1,6 @@
import {
ChangeDetectionStrategy,
+ ChangeDetectorRef,
Component,
EventEmitter,
HostBinding,
@@ -96,6 +97,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
private readonly _userPreferenceService: UserPreferenceService,
private readonly _reanalysisService: ReanalysisService,
private readonly _router: Router,
+ private readonly _changeRef: ChangeDetectorRef,
) {
super();
}
@@ -224,7 +226,10 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
}
ngOnInit() {
- this._dossiersService.getEntityChanged$(this.file.dossierId).pipe(tap(() => this._setup()));
+ this.addSubscription = this._dossiersService
+ .getEntityChanged$(this.file.dossierId)
+ .pipe(tap(() => this._setup()))
+ .subscribe();
}
ngOnChanges() {
@@ -353,6 +358,8 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
this.showReanalyseDossierOverview = this.canReanalyse && this.isDossierOverview && this.analysisForced;
this.buttons = this._buttons;
+
+ this._changeRef.markForCheck();
}
private async _setFileApproved() {
diff --git a/apps/red-ui/src/app/services/entity-services/dossiers.service.ts b/apps/red-ui/src/app/services/entity-services/dossiers.service.ts
index 7f4bbdc68..5d14236fe 100644
--- a/apps/red-ui/src/app/services/entity-services/dossiers.service.ts
+++ b/apps/red-ui/src/app/services/entity-services/dossiers.service.ts
@@ -2,16 +2,27 @@ import { Injectable, Injector } from '@angular/core';
import { EntitiesService, List, mapEach, QueryParam, RequiredParam, shareLast, Toaster, Validate } from '@iqser/common-ui';
import { Dossier, IDossier, IDossierRequest } from '@red/domain';
import { catchError, filter, map, mapTo, switchMap, tap } from 'rxjs/operators';
-import { combineLatest, iif, Observable, of, throwError } from 'rxjs';
+import { combineLatest, Observable, of, Subject, throwError, timer } from 'rxjs';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
import { DossierStatsService } from '@services/entity-services/dossier-stats.service';
+import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
export interface IDossiersStats {
totalPeople: number;
totalAnalyzedPages: number;
}
+interface ChangesDetails {
+ dossierChanges: [
+ {
+ dossierChanges: boolean;
+ dossierId: string;
+ fileChanges: boolean;
+ },
+ ];
+}
+
const DOSSIER_EXISTS_MSG = _('add-dossier-dialog.errors.dossier-already-exists');
const GENERIC_MGS = _('add-dossier-dialog.errors.generic');
@@ -20,6 +31,7 @@ const GENERIC_MGS = _('add-dossier-dialog.errors.generic');
})
export class DossiersService extends EntitiesService {
readonly generalStats$ = this.all$.pipe(switchMap(entities => this._generalStats$(entities)));
+ readonly dossierFileChanges$ = new Subject();
constructor(
private readonly _toaster: Toaster,
@@ -27,6 +39,13 @@ export class DossiersService extends EntitiesService {
private readonly _dossierStatsService: DossierStatsService,
) {
super(_injector, Dossier, 'dossier');
+
+ timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL)
+ .pipe(
+ switchMap(() => this.loadAllIfChanged()),
+ tap(changes => this._emitFileChanges(changes)),
+ )
+ .subscribe();
}
loadAll(): Observable {
@@ -39,8 +58,15 @@ export class DossiersService extends EntitiesService {
);
}
- loadAllIfChanged(): Observable {
- return this.hasChanges$().pipe(switchMap(changed => iif(() => changed, this.loadAll()).pipe(mapTo(changed))));
+ loadAllIfChanged(): Observable {
+ return this.hasChangesDetails$().pipe(switchMap(changes => this.loadAll().pipe(mapTo(changes))));
+ }
+
+ hasChangesDetails$(): Observable {
+ const body = { value: this._lastCheckedForChanges.get('root') ?? '0' };
+ return this._post(body, `${this._defaultModelPath}/changes/details`).pipe(
+ filter(changes => changes.dossierChanges.length > 0),
+ );
}
@Validate()
@@ -82,6 +108,10 @@ export class DossiersService extends EntitiesService {
return super.delete(body, 'deleted-dossiers/hard-delete', body).toPromise();
}
+ private _emitFileChanges(changes: ChangesDetails): void {
+ changes.dossierChanges.filter(change => change.fileChanges).forEach(change => this.dossierFileChanges$.next(change.dossierId));
+ }
+
private _computeStats(entities: List): IDossiersStats {
let totalAnalyzedPages = 0;
const totalPeople = new Set();
diff --git a/apps/red-ui/src/app/services/entity-services/files-map.service.ts b/apps/red-ui/src/app/services/entity-services/files-map.service.ts
index b861090da..9d04a9d99 100644
--- a/apps/red-ui/src/app/services/entity-services/files-map.service.ts
+++ b/apps/red-ui/src/app/services/entity-services/files-map.service.ts
@@ -7,6 +7,7 @@ import { filter, startWith } from 'rxjs/operators';
export class FilesMapService {
readonly fileReanalysed$ = new Subject();
private readonly _entityChanged$ = new Subject();
+ private readonly _entityDeleted$ = new Subject();
private readonly _map = new Map>();
get$(dossierId: string) {
@@ -39,6 +40,7 @@ export class FilesMapService {
const reanalysedEntities = [];
const changedEntities = [];
+ const deletedEntities = this.get(key).filter(oldEntity => !entities.find(newEntity => newEntity.id === oldEntity.id));
// Keep old object references for unchanged entities
const newEntities = entities.map(newEntity => {
@@ -67,6 +69,10 @@ export class FilesMapService {
for (const file of changedEntities) {
this._entityChanged$.next(file);
}
+
+ for (const file of deletedEntities) {
+ this._entityDeleted$.next(file);
+ }
}
replace(entity: File) {
@@ -83,4 +89,8 @@ export class FilesMapService {
startWith(this.get(key, entityId)),
);
}
+
+ watchDeleted$(entityId: string): Observable {
+ return this._entityDeleted$.pipe(filter(entity => entity.id === entityId));
+ }
}
diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json
index 510458983..fa8141da2 100644
--- a/apps/red-ui/src/assets/i18n/en.json
+++ b/apps/red-ui/src/assets/i18n/en.json
@@ -938,6 +938,20 @@
"unsaved-changes": "You have unsaved changes. Save or revert before changing the tab."
},
"error": {
+ "deleted-entity": {
+ "dossier": {
+ "action": "Back to overview",
+ "label": "This dossier has been deleted!"
+ },
+ "file-dossier": {
+ "action": "Back to overview",
+ "label": "The dossier of this file has been deleted!"
+ },
+ "file": {
+ "action": "Back to dossier",
+ "label": "This file has been deleted!"
+ }
+ },
"http": {
"generic": "Action failed with code {status}"
},
diff --git a/libs/common-ui b/libs/common-ui
index 51f3d7502..a1c1be7ed 160000
--- a/libs/common-ui
+++ b/libs/common-ui
@@ -1 +1 @@
-Subproject commit 51f3d7502b5545663afa2fa0a62abc3578f92905
+Subproject commit a1c1be7edc783bdea42b86c0f8b1f74437065b76