From b8f072e20a52bdd45bcc4c7e174fb9095bd44fc6 Mon Sep 17 00:00:00 2001 From: Timo Bejan Date: Sat, 14 Nov 2020 02:19:35 +0200 Subject: [PATCH] fixed stuff --- .../app/common/filter/filter.component.html | 90 +++++++------------ .../app/common/filter/filter.component.scss | 3 +- .../src/app/common/filter/filter.component.ts | 24 ++++- .../app/common/service/permissions.service.ts | 7 +- .../type-filter/type-filter.component.html | 2 +- .../add-edit-project-dialog.component.ts | 6 ++ apps/red-ui/src/app/dialogs/dialog.service.ts | 4 +- .../manual-annotation-dialog.component.html | 4 +- .../manual-annotation-dialog.component.ts | 8 +- .../needs-work-badge.component.html | 2 +- .../file-preview-screen.component.html | 5 +- .../file-preview-screen.component.scss | 5 ++ .../file-preview-screen.component.ts | 24 +++-- .../file/service/annotation-draw.service.ts | 8 +- .../file/service/file-download.service.ts | 11 +-- .../project-listing-screen.component.ts | 20 ++++- .../bulk-actions/bulk-actions.component.ts | 1 - .../project-details.component.html | 2 +- .../project-details.component.ts | 3 +- .../project-overview-screen.component.html | 2 +- .../project-overview-screen.component.ts | 16 ++++ .../red-ui/src/app/state/app-state.service.ts | 81 ++++++++--------- .../src/app/state/model/project.wrapper.ts | 17 ++++ .../src/app/upload/file-upload.service.ts | 53 +++++------ apps/red-ui/src/assets/i18n/en.json | 17 ++-- 25 files changed, 238 insertions(+), 177 deletions(-) diff --git a/apps/red-ui/src/app/common/filter/filter.component.html b/apps/red-ui/src/app/common/filter/filter.component.html index b17080504..6e141a0d6 100644 --- a/apps/red-ui/src/app/common/filter/filter.component.html +++ b/apps/red-ui/src/app/common/filter/filter.component.html @@ -1,77 +1,49 @@
-
-
-
-
-
-
-
-
-
-
-
- - - - +
+
+
+
+
+
- - - -
-
-
+
+
+
+ + +
- - +
+
+
+ + + + +
+
diff --git a/apps/red-ui/src/app/common/filter/filter.component.scss b/apps/red-ui/src/app/common/filter/filter.component.scss index 16ca6d85e..cb51b163f 100644 --- a/apps/red-ui/src/app/common/filter/filter.component.scss +++ b/apps/red-ui/src/app/common/filter/filter.component.scss @@ -26,10 +26,11 @@ } } -.mat-checkbox { +::ng-deep .filter-menu-checkbox { width: 100%; label { width: 100%; + height: 100%; } } diff --git a/apps/red-ui/src/app/common/filter/filter.component.ts b/apps/red-ui/src/app/common/filter/filter.component.ts index 13a1df555..40757b53a 100644 --- a/apps/red-ui/src/app/common/filter/filter.component.ts +++ b/apps/red-ui/src/app/common/filter/filter.component.ts @@ -1,8 +1,8 @@ -import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, TemplateRef } from '@angular/core'; +import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, TemplateRef, ViewChild } from '@angular/core'; import { AppStateService } from '../../state/app-state.service'; import { FilterModel } from './model/filter.model'; import { handleCheckedValue } from './utils/filter-utils'; -import { PermissionsService } from '../service/permissions.service'; +import { MatMenu, MatMenuTrigger } from '@angular/material/menu'; @Component({ selector: 'redaction-filter', @@ -17,6 +17,11 @@ export class FilterComponent implements OnChanges { @Input() hasArrow = true; @Input() icon: string; + @ViewChild(MatMenuTrigger) trigger: MatMenuTrigger; + + mouseOver: boolean = true; + mouseOverTimeout: number; + constructor(public readonly appStateService: AppStateService, private readonly _changeDetectorRef: ChangeDetectorRef) {} ngOnChanges(changes: SimpleChanges): void { @@ -53,6 +58,7 @@ export class FilterComponent implements OnChanges { filter.filters?.forEach((f) => (f.checked = filter.checked)); } this._changeDetectorRef.detectChanges(); + this.applyFilters(); } activateAllFilters() { @@ -90,4 +96,18 @@ export class FilterComponent implements OnChanges { }); }); } + + filterMouseEnter() { + this.mouseOver = true; + if (this.mouseOverTimeout) { + clearTimeout(this.mouseOverTimeout); + } + } + + filterMouseLeave() { + this.mouseOver = false; + this.mouseOverTimeout = setTimeout(() => { + this.trigger.closeMenu(); + }, 1000); + } } diff --git a/apps/red-ui/src/app/common/service/permissions.service.ts b/apps/red-ui/src/app/common/service/permissions.service.ts index eda23950d..49726bea3 100644 --- a/apps/red-ui/src/app/common/service/permissions.service.ts +++ b/apps/red-ui/src/app/common/service/permissions.service.ts @@ -53,7 +53,7 @@ export class PermissionsService { if (!fileStatus) { fileStatus = this._appStateService.activeFile; } - return this.fileRequiresReanalysis(fileStatus) && this.isReviewerOrOwner(fileStatus); + return (this.fileRequiresReanalysis(fileStatus) && this.isReviewerOrOwner(fileStatus)) || (fileStatus.isError && fileStatus.isUnassigned); } isFileReviewer(fileStatus?: FileStatusWrapper) { @@ -89,7 +89,10 @@ export class PermissionsService { } canApprove(fileStatus?: FileStatusWrapper) { - return this.canSetUnderReview && !fileStatus.hasRequests; + if (!fileStatus) { + fileStatus = this._appStateService.activeFile; + } + return this.canSetUnderReview(fileStatus) && !fileStatus.hasRequests; } canSetUnderApproval(fileStatus?: FileStatusWrapper) { diff --git a/apps/red-ui/src/app/components/type-filter/type-filter.component.html b/apps/red-ui/src/app/components/type-filter/type-filter.component.html index c7cff8b22..f545a04c6 100644 --- a/apps/red-ui/src/app/components/type-filter/type-filter.component.html +++ b/apps/red-ui/src/app/components/type-filter/type-filter.component.html @@ -1,6 +1,6 @@ - + {{ filter.label | translate }} diff --git a/apps/red-ui/src/app/dialogs/add-edit-project-dialog/add-edit-project-dialog.component.ts b/apps/red-ui/src/app/dialogs/add-edit-project-dialog/add-edit-project-dialog.component.ts index ebd0c9728..da4ed3962 100644 --- a/apps/red-ui/src/app/dialogs/add-edit-project-dialog/add-edit-project-dialog.component.ts +++ b/apps/red-ui/src/app/dialogs/add-edit-project-dialog/add-edit-project-dialog.component.ts @@ -30,6 +30,12 @@ export class AddEditProjectDialogComponent implements OnInit { async saveProject() { const project: Project = this._formToObject(); project.projectId = this.project?.projectId; + + let foundProject = this._appStateService.allProjects.find((p) => p.project.projectId === project.projectId); + if (foundProject) { + project.memberIds = foundProject.memberIds; + } + const savedProject = await this._appStateService.addOrUpdateProject(project); if (savedProject) { this.dialogRef.close(true); diff --git a/apps/red-ui/src/app/dialogs/dialog.service.ts b/apps/red-ui/src/app/dialogs/dialog.service.ts index ee8f80ee9..aa3396f31 100644 --- a/apps/red-ui/src/app/dialogs/dialog.service.ts +++ b/apps/red-ui/src/app/dialogs/dialog.service.ts @@ -146,7 +146,9 @@ export class DialogService { data: { type: 'project', project: project } }); ref.afterClosed().subscribe((result) => { - if (result && cb) cb(); + if (cb) { + cb(result); + } }); return ref; } diff --git a/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.html b/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.html index beb85b116..97c622c52 100644 --- a/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.html +++ b/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.html @@ -11,7 +11,7 @@
- + {{ option.label }} @@ -19,7 +19,7 @@
- +
diff --git a/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts b/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts index c0464d8df..baa194311 100644 --- a/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts +++ b/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts @@ -111,12 +111,16 @@ export class ManualAnnotationDialogComponent implements OnInit { } private _enhanceManualRedaction(addRedactionRequest: AddRedactionRequest) { + const legalOption: any = this.redactionForm.get('reason').value; addRedactionRequest.type = this.redactionForm.get('dictionary').value; - addRedactionRequest.reason = this.redactionForm.get('reason').value; + if (legalOption) { + addRedactionRequest.reason = legalOption.label; + addRedactionRequest.legalBasis = legalOption.legalBasis; + } addRedactionRequest.addToDictionary = this.isDictionaryRequest; // todo fix this in backend if (!addRedactionRequest.reason) { - addRedactionRequest.reason = '-'; + addRedactionRequest.reason = 'Dictionary Request'; } const commentValue = this.redactionForm.get('comment').value; addRedactionRequest.comment = commentValue ? { text: commentValue } : null; diff --git a/apps/red-ui/src/app/screens/common/needs-work-badge/needs-work-badge.component.html b/apps/red-ui/src/app/screens/common/needs-work-badge/needs-work-badge.component.html index 986f06c3d..1f21c65bb 100644 --- a/apps/red-ui/src/app/screens/common/needs-work-badge/needs-work-badge.component.html +++ b/apps/red-ui/src/app/screens/common/needs-work-badge/needs-work-badge.component.html @@ -1,6 +1,6 @@
- +
diff --git a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html index 95dfa1e2b..f5779ce62 100644 --- a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html +++ b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html @@ -12,7 +12,6 @@
-  {{ appStateService.activeFile.filename }} @@ -77,6 +76,10 @@ {{ activeViewerPage }}
+
+ {{ 'file-preview.no-annotations-for-page' | translate }} +
+
{ if (fileStatus.fileId === this.fileId) { - this._loadFileData().subscribe(() => { + this._loadFileData(true).subscribe(() => { this.viewReady = true; this.loadingMessage = null; this.canPerformAnnotationActions = this.permissionsService.canPerformAnnotationActions(this.fileData.fileStatus); @@ -116,12 +116,20 @@ export class FilePreviewScreenComponent implements OnInit { }); } - private _loadFileData() { + private _loadFileData(performUpdate: boolean = false) { return this._fileDownloadService.loadActiveFileData().pipe( tap((fileDataModel) => { if (fileDataModel.fileStatus.isWorkable) { - this.fileData = fileDataModel; - this._rebuildFilters(); + if (performUpdate) { + this.fileData.redactionLog = fileDataModel.redactionLog; + this.fileData.fileStatus = fileDataModel.fileStatus; + this.fileData.manualRedactions = fileDataModel.manualRedactions; + this.fileData.redactedFileData = null; + this._rebuildFilters(true); + } else { + this.fileData = fileDataModel; + this._rebuildFilters(); + } } else { if (fileDataModel.fileStatus.isError) { this._router.navigate(['/ui/projects/' + this.appStateService.activeProjectId]); @@ -133,7 +141,10 @@ export class FilePreviewScreenComponent implements OnInit { ); } - private _rebuildFilters() { + private _rebuildFilters(deletePreviousAnnotations: boolean = false) { + if (deletePreviousAnnotations) { + this.activeViewer.annotManager.deleteAnnotations(this.annotations.map((a) => this.activeViewer.annotManager.getAnnotationById(a.id))); + } this.annotations = this.fileData.getAnnotations(this.appStateService.dictionaryData, this.permissionsService.currentUser); this.filters = this._annotationProcessingService.getAnnotationFilter(this.annotations); this.filtersChanged(this.filters); @@ -381,7 +392,8 @@ export class FilePreviewScreenComponent implements OnInit { if (!this.redactedView) { this._annotationDrawService.drawAnnotations( this.instance, - this.annotations.filter((item) => (annotationIdToDraw ? item.id === annotationIdToDraw : true)) + this.annotations.filter((item) => (annotationIdToDraw ? item.id === annotationIdToDraw : true)), + !!annotationIdToDraw ); } }); diff --git a/apps/red-ui/src/app/screens/file/service/annotation-draw.service.ts b/apps/red-ui/src/app/screens/file/service/annotation-draw.service.ts index 9a223589b..b4d1c838c 100644 --- a/apps/red-ui/src/app/screens/file/service/annotation-draw.service.ts +++ b/apps/red-ui/src/app/screens/file/service/annotation-draw.service.ts @@ -11,7 +11,7 @@ import { AnnotationWrapper } from '../model/annotation.wrapper'; export class AnnotationDrawService { constructor(private readonly _appStateService: AppStateService) {} - public drawAnnotations(activeViewer: WebViewerInstance, annotationWrappers: AnnotationWrapper[]) { + public drawAnnotations(activeViewer: WebViewerInstance, annotationWrappers: AnnotationWrapper[], redraw: boolean = false) { const annotations = []; annotationWrappers.forEach((annotation) => { annotations.push(this.drawAnnotation(activeViewer, annotation)); @@ -19,7 +19,11 @@ export class AnnotationDrawService { const annotationManager = activeViewer.annotManager; annotationManager.addAnnotations(annotations, true); - // annotationManager.redrawAnnotations(annotations); + if (redraw) { + annotations.forEach((annotation) => { + annotationManager.redrawAnnotation(annotation); + }); + } } public drawAnnotation(activeViewer: WebViewerInstance, annotationWrapper: AnnotationWrapper) { diff --git a/apps/red-ui/src/app/screens/file/service/file-download.service.ts b/apps/red-ui/src/app/screens/file/service/file-download.service.ts index 5041a0709..224058c29 100644 --- a/apps/red-ui/src/app/screens/file/service/file-download.service.ts +++ b/apps/red-ui/src/app/screens/file/service/file-download.service.ts @@ -12,6 +12,7 @@ import { FileType } from '../model/file-type'; import { FileDataModel } from '../model/file-data.model'; import { AppStateService } from '../../../state/app-state.service'; import { PermissionsService } from '../../../common/service/permissions.service'; +import { FileData } from '@nrwl/workspace/src/core/file-utils'; @Injectable({ providedIn: 'root' @@ -26,12 +27,12 @@ export class FileDownloadService { private readonly _viewedPagesControllerService: ViewedPagesControllerService ) {} - public loadActiveFileManualAnnotations() { + loadActiveFileManualAnnotations() { return this._manualRedactionControllerService.getManualRedaction(this._appStateService.activeProjectId, this._appStateService.activeFileId); } - public loadActiveFileData(): Observable { - const annotatedObs = this.loadFile('ORIGINAL', this._appStateService.activeFile); + loadActiveFileData(): Observable { + const fileObs = this.loadFile('ORIGINAL', this._appStateService.activeFile); const reactionLogObs = this._redactionLogControllerService.getRedactionLog(this._appStateService.activeFileId); const manualRedactionsObs = this._manualRedactionControllerService.getManualRedaction( this._appStateService.activeProjectId, @@ -39,14 +40,14 @@ export class FileDownloadService { ); const viewedPagesObs = this.getViewedPagesForActiveFile(); - return forkJoin([annotatedObs, reactionLogObs, manualRedactionsObs, viewedPagesObs]).pipe( + return forkJoin([fileObs, reactionLogObs, manualRedactionsObs, viewedPagesObs]).pipe( map((data) => { return new FileDataModel(this._appStateService.activeFile, ...data); }) ); } - public loadRedactedView(fileData: FileDataModel) { + loadRedactedView(fileData: FileDataModel) { this.loadFile('REDACTED', fileData.fileStatus).subscribe((redactedFileData) => (fileData.redactedFileData = redactedFileData)); } diff --git a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.ts b/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.ts index 8665f4b01..5dda93461 100644 --- a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.ts +++ b/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { Project } from '@redaction/red-ui-http'; import { AppStateService } from '../../state/app-state.service'; import { UserService } from '../../user/user.service'; @@ -19,15 +19,15 @@ import { TranslateService } from '@ngx-translate/core'; import { SortingOption, SortingService } from '../../utils/sorting.service'; import { PermissionsService } from '../../common/service/permissions.service'; import { ProjectWrapper } from '../../state/model/project.wrapper'; +import { Subscription, timer } from 'rxjs'; +import { tap } from 'rxjs/operators'; @Component({ selector: 'redaction-project-listing-screen', templateUrl: './project-listing-screen.component.html', styleUrls: ['./project-listing-screen.component.scss'] }) -export class ProjectListingScreenComponent implements OnInit { - private _selectedProjectIds: string[] = []; - +export class ProjectListingScreenComponent implements OnInit, OnDestroy { public projectsChartData: DoughnutChartConfig[] = []; public documentsChartData: DoughnutChartConfig[] = []; @@ -41,6 +41,7 @@ export class ProjectListingScreenComponent implements OnInit { }; public displayedProjects: ProjectWrapper[] = []; + private projectAutoUpdateTimer: Subscription; constructor( public readonly appStateService: AppStateService, @@ -54,12 +55,23 @@ export class ProjectListingScreenComponent implements OnInit { public ngOnInit(): void { this.appStateService.reset(); + this.projectAutoUpdateTimer = timer(0, 10000) + .pipe( + tap(async () => { + await this.appStateService.loadAllProjects(); + }) + ) + .subscribe(); this._calculateData(); this.appStateService.fileChanged.subscribe(() => { this._calculateData(); }); } + ngOnDestroy(): void { + this.projectAutoUpdateTimer.unsubscribe(); + } + private _calculateData() { this._computeAllFilters(); this._filterProjects(); diff --git a/apps/red-ui/src/app/screens/project-overview-screen/bulk-actions/bulk-actions.component.ts b/apps/red-ui/src/app/screens/project-overview-screen/bulk-actions/bulk-actions.component.ts index 1b9b83062..df4ce2349 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/bulk-actions/bulk-actions.component.ts +++ b/apps/red-ui/src/app/screens/project-overview-screen/bulk-actions/bulk-actions.component.ts @@ -116,7 +116,6 @@ export class BulkActionsComponent { Promise.all(promises).then(() => { this.reload.emit(); - console.log('done'); }); } diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.html b/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.html index 63f4a673b..583dec076 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.html +++ b/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.html @@ -67,7 +67,7 @@
- {{ 'project-overview.project-details.stats.people' | translate: { count: appStateService.activeProject.project.memberIds.length } }} + {{ 'project-overview.project-details.stats.people' | translate: { count: appStateService.activeProject.memberCount } }}
diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.ts b/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.ts index 312fc2e7e..a3af1af6d 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.ts +++ b/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.ts @@ -1,6 +1,5 @@ import { Component, OnInit, Output, EventEmitter, Input, ChangeDetectorRef } from '@angular/core'; import { AppStateService } from '../../../state/app-state.service'; -import { UserService } from '../../../user/user.service'; import { groupBy } from '../../../utils/functions'; import { DoughnutChartConfig } from '../../../components/simple-doughnut-chart/simple-doughnut-chart.component'; import { DialogService } from '../../../dialogs/dialog.service'; @@ -55,6 +54,8 @@ export class ProjectDetailsComponent implements OnInit { public openAssignProjectMembersDialog(): void { this._dialogService.openAssignProjectMembersAndOwnerDialog(null, this.appStateService.activeProject.project, () => { this.reloadProjects.emit(); + this._changeDetectorRef.detectChanges(); + console.log(this.appStateService.activeProject); }); } diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html index de83b3ec2..6547ce0aa 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html +++ b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html @@ -68,7 +68,7 @@ {{ 'project-overview.table-header.title' | translate: { length: displayedFiles.length || 0 } }} - +
diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts index 9ec7f9381..ef4c890a9 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts +++ b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts @@ -19,6 +19,8 @@ import { SortingOption, SortingService } from '../../utils/sorting.service'; import { PermissionsService } from '../../common/service/permissions.service'; import { UserService } from '../../user/user.service'; import { FileStatus } from '@redaction/red-ui-http'; +import { Subscription, timer } from 'rxjs'; +import { tap } from 'rxjs/operators'; @Component({ selector: 'redaction-project-overview-screen', @@ -40,6 +42,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { @ViewChild('projectDetailsComponent', { static: false }) private _projectDetailsComponent: ProjectDetailsComponent; + private filesAutoUpdateTimer: Subscription; constructor( public readonly appStateService: AppStateService, @@ -67,6 +70,13 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { } ngOnInit(): void { + this.filesAutoUpdateTimer = timer(0, 5000) + .pipe( + tap(async () => { + await this.appStateService.reloadActiveProjectFilesIfNecessary(); + }) + ) + .subscribe(); this._fileDropOverlayService.initFileDropHandling(); this.calculateData(); this._displayOutdatedToast(); @@ -74,6 +84,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { ngOnDestroy(): void { this._fileDropOverlayService.cleanupFileDropHandling(); + this.filesAutoUpdateTimer.unsubscribe(); } private _displayOutdatedToast() { @@ -279,4 +290,9 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { }; this._changeDetectorRef.detectChanges(); } + + bulkActionPerformed() { + this.selectedFileIds = []; + this.reloadProjects(); + } } diff --git a/apps/red-ui/src/app/state/app-state.service.ts b/apps/red-ui/src/app/state/app-state.service.ts index 2d232de9b..b5fb62b9e 100644 --- a/apps/red-ui/src/app/state/app-state.service.ts +++ b/apps/red-ui/src/app/state/app-state.service.ts @@ -23,8 +23,8 @@ import { ProjectWrapper } from './model/project.wrapper'; export interface AppState { projects: ProjectWrapper[]; - activeProject: ProjectWrapper; - activeFile: FileStatusWrapper; + activeProjectId: string; + activeFileId: string; totalAnalysedPages?: number; totalDocuments?: number; totalPeople?: number; @@ -55,33 +55,16 @@ export class AppStateService { ) { this._appState = { projects: [], - activeProject: null, - activeFile: null + activeProjectId: null, + activeFileId: null }; + } - timer(5000, 5000) - .pipe( - tap(() => { - this.reloadActiveProjectFiles(); - }) - ) - .subscribe(); - - timer(5000, 5000) - .pipe( - tap(() => { - this.updateDictionaryVersion(); - }) - ) - .subscribe(); - - timer(5000, 30000) - .pipe( - tap(() => { - this.loadAllProjects(); - }) - ) - .subscribe(); + async reloadActiveProjectFilesIfNecessary() { + if (this.activeProject?.hasPendingOrProcessing) { + await this.reloadActiveProjectFiles(); + await this.updateDictionaryVersion(); + } } get dictionaryVersion() { @@ -121,11 +104,11 @@ export class AppStateService { } get activeProjectId(): string { - return this._appState.activeProject?.project?.projectId; + return this._appState.activeProjectId; } get activeProject(): ProjectWrapper { - return this._appState.activeProject; + return this._appState.projects.find((p) => p.projectId === this._appState.activeProjectId); } get allProjects(): ProjectWrapper[] { @@ -137,11 +120,11 @@ export class AppStateService { } get activeFile(): FileStatusWrapper { - return this._appState.activeFile; + return this.activeProject?.files.find((f) => f.fileId === this.activeFileId); } get activeFileId(): string { - return this._appState.activeFile?.fileId; + return this._appState.activeFileId; } get totalAnalysedPages() { @@ -192,6 +175,7 @@ export class AppStateService { const activeFileWrapper = new FileStatusWrapper(activeFile, this._userService.getNameForId(activeFile.currentReviewer)); this.activeProject.files = this.activeProject.files.map((file) => (file.fileId === activeFileWrapper.fileId ? activeFileWrapper : file)); + await this.updateDictionaryVersion(); this._computeStats(); if (activeFileWrapper.lastProcessed !== oldProcessedDate) { this.fileReanalysed.emit(activeFileWrapper); @@ -260,23 +244,32 @@ export class AppStateService { } activateProject(projectId: string) { - this._appState.activeFile = null; - this._appState.activeProject = this._appState.projects.find((p) => p.project.projectId === projectId); - if (!this._appState.activeProject) { + this._appState.activeFileId = null; + this._appState.activeProjectId = projectId; + if (!this.activeProject) { + this._appState.activeProjectId = null; this._router.navigate(['/ui/projects']); + return; } - return this._appState.activeProject; + return this.activeProject; } activateFile(projectId: string, fileId: string) { - this._appState.activeFile = null; - this._appState.activeProject = this._appState.projects.find((p) => p.project.projectId === projectId); - this._appState.activeFile = this._appState.activeProject.files.find((f) => f.fileId === fileId); + const activeProject = this.activateProject(projectId); + if (activeProject) { + this._appState.activeFileId = fileId; + if (!this.activeFile) { + this._appState.activeFileId = null; + this._router.navigate(['/ui/projects/' + projectId]); + return; + } + return this.activateFile; + } } reset() { - this._appState.activeFile = null; - this._appState.activeProject = null; + this._appState.activeFileId = null; + this._appState.activeProjectId = null; } deleteProject(project: Project) { @@ -304,7 +297,7 @@ export class AppStateService { const updatedProject = await this._projectControllerService.createProjectOrUpdateProject(project).toPromise(); let foundProject = this._appState.projects.find((p) => p.project.projectId === updatedProject.projectId); if (foundProject) { - Object.assign(foundProject.project, updatedProject); + Object.assign((foundProject.project = updatedProject)); } else { foundProject = new ProjectWrapper(updatedProject, []); this._appState.projects.push(foundProject); @@ -347,8 +340,8 @@ export class AppStateService { } async reloadActiveProjectFiles() { - if (this._appState.activeProject) { - await this.getFiles(this._appState.activeProject); + if (this.activeProjectId) { + await this.getFiles(this.activeProject); } } @@ -462,7 +455,7 @@ export class AppStateService { await forkJoin([typeObs, colorsObs]).toPromise(); - this._dictionaryData['hint'] = { hexColor: '#283241', type: 'hint', virtual: true }; + this._dictionaryData['hint'] = { hexColor: '#9398a0', type: 'hint', virtual: true }; this._dictionaryData['redaction'] = { hexColor: '#283241', type: 'redaction', diff --git a/apps/red-ui/src/app/state/model/project.wrapper.ts b/apps/red-ui/src/app/state/model/project.wrapper.ts index 3297d2c0f..433b9ddc3 100644 --- a/apps/red-ui/src/app/state/model/project.wrapper.ts +++ b/apps/red-ui/src/app/state/model/project.wrapper.ts @@ -9,6 +9,7 @@ export class ProjectWrapper { hasRedactions?: boolean; hasRequests?: boolean; hasNone?: boolean; + hasPendingOrProcessing?: boolean; allFilesApproved?: boolean; @@ -19,11 +20,19 @@ export class ProjectWrapper { this._recomputeFileStatus(); } + get projectId() { + return this.project.projectId; + } + set files(files: FileStatusWrapper[]) { this._files = files ? files : []; this._recomputeFileStatus(); } + get memberIds() { + return this.project.memberIds; + } + get files() { return this._files; } @@ -60,17 +69,25 @@ export class ProjectWrapper { return moment(this.projectDate).format('DD/MM/YYYY') === key; } + get memberCount() { + return this.project.memberIds.length; + } + private _recomputeFileStatus() { this.hintsOnly = false; this.hasRedactions = false; this.hasRequests = false; this.hasNone = false; this.allFilesApproved = true; + this.totalNumberOfPages = 0; + this.hasPendingOrProcessing = false; this._files.forEach((f) => { this.hintsOnly = this.hintsOnly || f.hintsOnly; this.hasRedactions = this.hasRedactions || f.hasRedactions; this.hasRequests = this.hasRequests || f.hasRequests; this.allFilesApproved = this.allFilesApproved && f.isApproved; + this.totalNumberOfPages += f.numberOfPages; + this.hasPendingOrProcessing = this.hasPendingOrProcessing || f.isPending || f.isProcessing; }); this.hasNone = !this.hasRequests && !this.hasRedactions && !this.hintsOnly; } diff --git a/apps/red-ui/src/app/upload/file-upload.service.ts b/apps/red-ui/src/app/upload/file-upload.service.ts index ffcd611a0..214e5b835 100644 --- a/apps/red-ui/src/app/upload/file-upload.service.ts +++ b/apps/red-ui/src/app/upload/file-upload.service.ts @@ -10,43 +10,32 @@ import { HttpEventType } from '@angular/common/http'; export class FileUploadService { files: FileUploadModel[] = []; - constructor( - private readonly _appStateService: AppStateService, - private readonly _fileUploadControllerService: FileUploadControllerService - ) {} + constructor(private readonly _appStateService: AppStateService, private readonly _fileUploadControllerService: FileUploadControllerService) {} uploadFiles(files: FileUploadModel[]) { this.files.push(...files); files.forEach((newFile) => { - this._fileUploadControllerService - .uploadFileForm( - newFile.file, - this._appStateService.activeProject.project.projectId, - 'events', - true - ) - .subscribe( - (event) => { - if (event.type === HttpEventType.UploadProgress) { - newFile.progress = Math.round( - (event.loaded / (event.total || event.loaded)) * 100 - ); - } - if (event.type === HttpEventType.Response) { - if (event.status < 300) { - newFile.progress = 100; - newFile.completed = true; - } else { - newFile.completed = true; - newFile.error = event.body; - } - } - }, - (error) => { - newFile.completed = true; - newFile.error = error; + this._fileUploadControllerService.uploadFileForm(newFile.file, this._appStateService.activeProject.project.projectId, 'events', true).subscribe( + async (event) => { + if (event.type === HttpEventType.UploadProgress) { + newFile.progress = Math.round((event.loaded / (event.total || event.loaded)) * 100); } - ); + if (event.type === HttpEventType.Response) { + if (event.status < 300) { + newFile.progress = 100; + newFile.completed = true; + } else { + newFile.completed = true; + newFile.error = event.body; + } + await this._appStateService.reloadActiveProjectFiles(); + } + }, + (error) => { + newFile.completed = true; + newFile.error = error; + } + ); }); } diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index d8121db8c..34eb09a2f 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -58,7 +58,7 @@ "created-on": "Created On", "project": "Project", "document": "Document", - "needs-work": "Analysed" + "needs-work": "Analyzed" }, "report": { "unavailable": "Redaction Report is only available once all files have been approved.", @@ -79,13 +79,13 @@ }, "table-col-names": { "name": "Document", - "needs-work": "Analysed", + "needs-work": "Analyzed", "owner": "Owner", "status": "Status" }, "stats": { "analyzed-pages": "Analyzed pages", - "total-people": "Total people", + "total-people": "Total users", "charts": { "projects": "Projects", "total-documents": "Total Documents" @@ -100,8 +100,8 @@ "due-date": "Due Date" }, "actions": { - "save": "Save Project", - "save-and-add-members": "Save Project & define Team" + "save": "Save", + "save-and-add-members": "Save and edit Team" } }, "header": "Projects", @@ -141,7 +141,7 @@ "members": "Members" }, "project-overview": { - "under-approval": "Under Approval", + "under-approval": "For Approval", "approve": "Approve", "under-review": "Under Review", "no-files": "Project is empty!", @@ -151,7 +151,7 @@ "new-rule": { "label": "Outdated", "toast": { - "message-project": "Some documents were not processed with the latest rule/dictionary set. They are marked with:\n\n", + "message-project": "Documents need to be re-analyzed.", "actions": { "reanalyse-all": "Reanalyze all", "reanalyse-file": "Reanalyze this file", @@ -224,6 +224,7 @@ } }, "file-preview": { + "no-annotations-for-page": "There are no redactions, hints or requests on this page.", "show-redacted-view": "Show Redacted Preview", "cannot-show-redacted-view": "Redactions out of sync. Redacted Preview only available after reanalysis", "reanalyse-notification": "This document was not processed with the latest rule/dictionary set. Reanalyse now to get updated annotations.", @@ -235,7 +236,7 @@ "label": "Workload" } }, - "reviewer": "Reviewer", + "reviewer": "Assignee", "unassigned": "Unassigned" }, "annotation-actions": {