diff --git a/apps/red-ui/src/app/app.module.ts b/apps/red-ui/src/app/app.module.ts index 00dbd4679..b9ae234d3 100644 --- a/apps/red-ui/src/app/app.module.ts +++ b/apps/red-ui/src/app/app.module.ts @@ -20,7 +20,7 @@ import { languageInitializer } from './i18n/language.initializer'; import { LanguageService } from './i18n/language.service'; import { MatIconModule } from '@angular/material/icon'; import { IconsModule } from './icons/icons.module'; -import { AddEditProjectDialogComponent } from './screens/project-listing-screen/add-edit-project-dialog/add-edit-project-dialog.component'; +import { AddEditProjectDialogComponent } from './dialogs/add-edit-project-dialog/add-edit-project-dialog.component'; import { MatDialogModule } from '@angular/material/dialog'; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatTooltipModule } from '@angular/material/tooltip'; @@ -33,11 +33,11 @@ import { NgpSortModule } from 'ngp-sort-pipe'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatSelectModule } from '@angular/material/select'; import { MatSidenavModule } from '@angular/material/sidenav'; -import { FileDetailsDialogComponent } from './screens/file/file-preview-screen/file-details-dialog/file-details-dialog.component'; +import { FileDetailsDialogComponent } from './dialogs/file-details-dialog/file-details-dialog.component'; import { ToastrModule } from 'ngx-toastr'; import { ServiceWorkerModule } from '@angular/service-worker'; import { environment } from '../environments/environment'; -import { ProjectDetailsDialogComponent } from './screens/project-overview-screen/project-details-dialog/project-details-dialog.component'; +import { ProjectDetailsDialogComponent } from './dialogs/project-details-dialog/project-details-dialog.component'; import { AuthModule } from './auth/auth.module'; import { FileUploadModule } from './upload/file-upload.module'; import { FullPageLoadingIndicatorComponent } from './utils/full-page-loading-indicator/full-page-loading-indicator.component'; @@ -48,15 +48,16 @@ import { LogoComponent } from './logo/logo.component'; import { CompositeRouteGuard } from './utils/composite-route.guard'; import { AppStateGuard } from './state/app-state.guard'; import { SimpleDoughnutChartComponent } from './components/simple-doughnut-chart/simple-doughnut-chart.component'; -import { ManualRedactionDialogComponent } from './screens/file/manual-redaction-dialog/manual-redaction-dialog.component'; +import { ManualRedactionDialogComponent } from './dialogs/manual-redaction-dialog/manual-redaction-dialog.component'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { AnnotationIconComponent } from './components/annotation-icon/annotation-icon.component'; import { AuthGuard } from './auth/auth.guard'; import { AuthErrorComponent } from './screens/auth-error/auth-error.component'; import { RedRoleGuard } from './auth/red-role.guard'; +import { AssignProjectMembersDialogComponent } from './dialogs/assign-project-members-dialog/assign-project-members-dialog.component'; import { MatListModule } from '@angular/material/list'; -import { AssignOwnerDialogComponent } from './components/project-members-dialog/assign-owner-dialog.component'; +import { AssignOwnerDialogComponent } from './dialogs/assign-owner-dialog/assign-owner-dialog.component'; export function HttpLoaderFactory(httpClient: HttpClient) { return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json'); @@ -74,6 +75,7 @@ export function HttpLoaderFactory(httpClient: HttpClient) { PdfViewerComponent, FileDetailsDialogComponent, ProjectDetailsDialogComponent, + AssignProjectMembersDialogComponent, AssignOwnerDialogComponent, FullPageLoadingIndicatorComponent, InitialsAvatarComponent, diff --git a/apps/red-ui/src/app/components/project-members-dialog/assign-owner-dialog.component.scss b/apps/red-ui/src/app/components/project-members-dialog/assign-owner-dialog.component.scss deleted file mode 100644 index 6914bc08f..000000000 --- a/apps/red-ui/src/app/components/project-members-dialog/assign-owner-dialog.component.scss +++ /dev/null @@ -1,5 +0,0 @@ -@import "../../../assets/styles/red-variables"; - -.owner { - background-color: rgba($primary, 0.1); -} diff --git a/apps/red-ui/src/app/components/project-members-dialog/assign-owner-dialog.component.ts b/apps/red-ui/src/app/components/project-members-dialog/assign-owner-dialog.component.ts deleted file mode 100644 index 0c32f1c8c..000000000 --- a/apps/red-ui/src/app/components/project-members-dialog/assign-owner-dialog.component.ts +++ /dev/null @@ -1,91 +0,0 @@ -import {Component, Inject} from '@angular/core'; -import {FileStatus, Project, ProjectControllerService, StatusControllerService} from '@redaction/red-ui-http'; -import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; -import {AppStateService} from '../../state/app-state.service'; -import {UserService} from '../../user/user.service'; -import {NotificationService} from '../../notification/notification.service'; -import {FormBuilder, FormGroup, Validators} from "@angular/forms"; - -class DialogData { - type: 'file' | 'project'; - project?: Project; - file?: FileStatus; -} - -@Component({ - selector: 'redaction-project-details-dialog', - templateUrl: './assign-owner-dialog.component.html', - styleUrls: ['./assign-owner-dialog.component.scss'] -}) -export class AssignOwnerDialogComponent { - - usersForm: FormGroup; - - constructor(public readonly userService: UserService, - private readonly _projectControllerService: ProjectControllerService, - private readonly _notificationService: NotificationService, - private readonly _formBuilder: FormBuilder, - private readonly _statusControllerService: StatusControllerService, - private readonly _appStateService: AppStateService, - public dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: DialogData) { - this._loadData(); - } - - - private _loadData() { - if (this.data.type === 'project') { - const project = this.data.project; - - this.usersForm = this._formBuilder.group({ - singleUser: [project?.ownerId, Validators.required], - userList: [project?.memberIds] - }); - } - - if (this.data.type === 'file') { - const file = this.data.file; - this.usersForm = this._formBuilder.group({ - singleUser: [file?.currentReviewer], - }); - } - } - - async saveUsers() { - if (this.data.type === 'project') { - - const ownerId = this.usersForm.get('singleUser').value; - const memberIds = this.usersForm.get('userList').value; - const project = this.data.project; - - await this._projectControllerService.addMembersToProject({memberIds: memberIds}, project.projectId).toPromise(); - await this._projectControllerService.assignProjectOwner(project.projectId, ownerId).toPromise(); - - const updatedProject = await this._projectControllerService.getProject(project.projectId).toPromise(); - - const toRemoveMembers = updatedProject.memberIds.filter(m => memberIds.indexOf(m) < 0); - if (toRemoveMembers.length > 0) { - await this._projectControllerService.deleteMembersToProject({memberIds: toRemoveMembers}, project.projectId).toPromise(); - } - - project.ownerId = ownerId; - project.memberIds = [...new Set([ownerId, ...memberIds])]; - - this._notificationService.showToastNotification('Successfully assigned ' + this.userService.getNameForId(ownerId) + ' to project: ' + project.projectName); - //this._notificationService.showToastNotification('Failed: ' + error.error.message, null, NotificationType.ERROR); - } - - if (this.data.type === 'file') { - - const reviewerId = this.usersForm.get('singleUser').value; - - await this._statusControllerService.assignProjectOwner1(this._appStateService.activeProjectId, this.data.file.fileId, reviewerId).toPromise(); - this.data.file.currentReviewer = reviewerId; - this._notificationService.showToastNotification('Successfully assigned ' + this.userService.getNameForId(reviewerId) + ' to file: ' + this.data.file.filename); - - } - - this.dialogRef.close(); - } - -} diff --git a/apps/red-ui/src/app/screens/project-listing-screen/add-edit-project-dialog/add-edit-project-dialog.component.html b/apps/red-ui/src/app/dialogs/add-edit-project-dialog/add-edit-project-dialog.component.html similarity index 100% rename from apps/red-ui/src/app/screens/project-listing-screen/add-edit-project-dialog/add-edit-project-dialog.component.html rename to apps/red-ui/src/app/dialogs/add-edit-project-dialog/add-edit-project-dialog.component.html diff --git a/apps/red-ui/src/app/screens/project-listing-screen/add-edit-project-dialog/add-edit-project-dialog.component.scss b/apps/red-ui/src/app/dialogs/add-edit-project-dialog/add-edit-project-dialog.component.scss similarity index 100% rename from apps/red-ui/src/app/screens/project-listing-screen/add-edit-project-dialog/add-edit-project-dialog.component.scss rename to apps/red-ui/src/app/dialogs/add-edit-project-dialog/add-edit-project-dialog.component.scss diff --git a/apps/red-ui/src/app/screens/project-listing-screen/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 similarity index 93% rename from apps/red-ui/src/app/screens/project-listing-screen/add-edit-project-dialog/add-edit-project-dialog.component.ts rename to apps/red-ui/src/app/dialogs/add-edit-project-dialog/add-edit-project-dialog.component.ts index 5706497cf..7704ac224 100644 --- a/apps/red-ui/src/app/screens/project-listing-screen/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 @@ -2,7 +2,7 @@ import {Component, Inject, OnInit} from '@angular/core'; import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; import {Project} from "@redaction/red-ui-http"; import {FormBuilder, FormGroup, Validators} from "@angular/forms"; -import {AppStateService} from "../../../state/app-state.service"; +import {AppStateService} from "../../state/app-state.service"; @Component({ selector: 'redaction-add-edit-project-dialog', @@ -32,7 +32,7 @@ export class AddEditProjectDialogComponent implements OnInit { const project: Project = this._formToObject(); project.projectId = this.project?.projectId; await this._appStateService.addOrUpdateProject(project); - this.dialogRef.close(); + this.dialogRef.close(true); } private _formToObject(): Project { diff --git a/apps/red-ui/src/app/components/project-members-dialog/assign-owner-dialog.component.html b/apps/red-ui/src/app/dialogs/assign-owner-dialog/assign-owner-dialog.component.html similarity index 100% rename from apps/red-ui/src/app/components/project-members-dialog/assign-owner-dialog.component.html rename to apps/red-ui/src/app/dialogs/assign-owner-dialog/assign-owner-dialog.component.html diff --git a/apps/red-ui/src/app/screens/file/manual-redaction-dialog/manual-redaction-dialog.component.scss b/apps/red-ui/src/app/dialogs/assign-owner-dialog/assign-owner-dialog.component.scss similarity index 100% rename from apps/red-ui/src/app/screens/file/manual-redaction-dialog/manual-redaction-dialog.component.scss rename to apps/red-ui/src/app/dialogs/assign-owner-dialog/assign-owner-dialog.component.scss diff --git a/apps/red-ui/src/app/dialogs/assign-owner-dialog/assign-owner-dialog.component.ts b/apps/red-ui/src/app/dialogs/assign-owner-dialog/assign-owner-dialog.component.ts new file mode 100644 index 000000000..59cf379c2 --- /dev/null +++ b/apps/red-ui/src/app/dialogs/assign-owner-dialog/assign-owner-dialog.component.ts @@ -0,0 +1,93 @@ +import { Component, Inject } from '@angular/core'; +import { FileStatus, Project, ProjectControllerService, StatusControllerService } from '@redaction/red-ui-http'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { AppStateService } from '../../state/app-state.service'; +import { UserService } from '../../user/user.service'; +import { NotificationService, NotificationType } from '../../notification/notification.service'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; + +class DialogData { + type: 'file' | 'project'; + project?: Project; + file?: FileStatus; +} + +@Component({ + selector: 'redaction-project-details-dialog', + templateUrl: './assign-owner-dialog.component.html', + styleUrls: ['./assign-owner-dialog.component.scss'] +}) +export class AssignOwnerDialogComponent { + + usersForm: FormGroup; + + constructor(public readonly userService: UserService, + private readonly _projectControllerService: ProjectControllerService, + private readonly _notificationService: NotificationService, + private readonly _formBuilder: FormBuilder, + private readonly _statusControllerService: StatusControllerService, + private readonly _appStateService: AppStateService, + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: DialogData) { + this._loadData(); + } + + + private _loadData() { + if (this.data.type === 'project') { + const project = this.data.project; + this.usersForm = this._formBuilder.group({ + singleUser: [project?.ownerId, Validators.required], + userList: [project?.memberIds] + }); + } + + if (this.data.type === 'file') { + const file = this.data.file; + this.usersForm = this._formBuilder.group({ + singleUser: [file?.currentReviewer] + }); + } + } + + async saveUsers() { + try { + if (this.data.type === 'project') { + + const ownerId = this.usersForm.get('singleUser').value; + const memberIds = this.usersForm.get('userList').value; + const project = this.data.project; + await this._projectControllerService.addMembersToProject({ memberIds: memberIds }, project.projectId).toPromise(); + await this._projectControllerService.assignProjectOwner(project.projectId, ownerId).toPromise(); + + const updatedProject = await this._projectControllerService.getProject(project.projectId).toPromise(); + + const toRemoveMembers = updatedProject.memberIds.filter(m => memberIds.indexOf(m) < 0); + if (toRemoveMembers.length > 0) { + await this._projectControllerService.deleteMembersToProject({ memberIds: toRemoveMembers }, project.projectId).toPromise(); + } + + project.ownerId = ownerId; + project.memberIds = [...new Set([ownerId, ...memberIds])]; + + this._notificationService.showToastNotification('Successfully assigned ' + this.userService.getNameForId(ownerId) + ' to project: ' + project.projectName); + + } + + if (this.data.type === 'file') { + + const reviewerId = this.usersForm.get('singleUser').value; + + await this._statusControllerService.assignProjectOwner1(this._appStateService.activeProjectId, this.data.file.fileId, reviewerId).toPromise(); + this.data.file.currentReviewer = reviewerId; + this._notificationService.showToastNotification('Successfully assigned ' + this.userService.getNameForId(reviewerId) + ' to file: ' + this.data.file.filename); + + } + } catch (error) { + this._notificationService.showToastNotification('Failed: ' + error.error.message, null, NotificationType.ERROR); + } + + this.dialogRef.close(); + } + +} diff --git a/apps/red-ui/src/app/dialogs/assign-project-members-dialog/assign-project-members-dialog.component.html b/apps/red-ui/src/app/dialogs/assign-project-members-dialog/assign-project-members-dialog.component.html new file mode 100644 index 000000000..b9eec293f --- /dev/null +++ b/apps/red-ui/src/app/dialogs/assign-project-members-dialog/assign-project-members-dialog.component.html @@ -0,0 +1,22 @@ +
+
+ +
+ +
+ + + + {{ userService.getNameForId(user.userId) }} + + + +
+ + +
diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-details-dialog/project-details-dialog.component.scss b/apps/red-ui/src/app/dialogs/assign-project-members-dialog/assign-project-members-dialog.component.scss similarity index 100% rename from apps/red-ui/src/app/screens/project-overview-screen/project-details-dialog/project-details-dialog.component.scss rename to apps/red-ui/src/app/dialogs/assign-project-members-dialog/assign-project-members-dialog.component.scss diff --git a/apps/red-ui/src/app/dialogs/assign-project-members-dialog/assign-project-members-dialog.component.ts b/apps/red-ui/src/app/dialogs/assign-project-members-dialog/assign-project-members-dialog.component.ts new file mode 100644 index 000000000..73be0b81a --- /dev/null +++ b/apps/red-ui/src/app/dialogs/assign-project-members-dialog/assign-project-members-dialog.component.ts @@ -0,0 +1,55 @@ +import { Component, Inject } from '@angular/core'; +import { Project, ProjectControllerService } from '@redaction/red-ui-http'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { AppStateService } from '../../state/app-state.service'; +import { UserService } from '../../user/user.service'; +import { MatSelectionListChange } from '@angular/material/list'; +import { NotificationService, NotificationType } from '../../notification/notification.service'; + +@Component({ + selector: 'redaction-project-details-dialog', + templateUrl: './assign-project-members-dialog.component.html', + styleUrls: ['./assign-project-members-dialog.component.scss'] +}) +export class AssignProjectMembersDialogComponent { + public memberIds: string[]; + + constructor(private readonly _projectControllerService: ProjectControllerService, + private readonly _notificationService: NotificationService, + public readonly userService: UserService, + private readonly _appStateService: AppStateService, + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public _project: Project) { + this._loadMembers(); + } + + private _loadMembers() { + this.memberIds = [...this._project.memberIds]; + } + + private _reloadProject() { + this._appStateService.addOrUpdateProject(this._project).then(() => { + this._loadMembers(); + }); + } + + public selectionChange($event: MatSelectionListChange) { + const userId = $event.option.value; + const selected = $event.option.selected; + const userName = this.userService.getNameForId(userId); + + if (selected) { + this._projectControllerService.addMembersToProject({ memberIds: [userId] }, this._project.projectId).subscribe(() => { + this._notificationService.showToastNotification('Successfully assigned ' + userName + ' to project: ' + this._project.projectName); + }, error => { + this._notificationService.showToastNotification('Failed: ' + error.error.message, null, NotificationType.ERROR); + }).add(() => this._reloadProject()); + } else { + this._projectControllerService.deleteMembersToProject({ memberIds: [userId] }, this._project.projectId).subscribe(() => { + this._notificationService.showToastNotification('Successfully removed ' + userName + ' from project: ' + this._project.projectName); + }, error => { + this._notificationService.showToastNotification('Failed: ' + error.error.message, null, NotificationType.ERROR); + }).add(() => this._reloadProject()); + } + } +} diff --git a/apps/red-ui/src/app/dialogs/dialog.service.ts b/apps/red-ui/src/app/dialogs/dialog.service.ts new file mode 100644 index 000000000..383cd685d --- /dev/null +++ b/apps/red-ui/src/app/dialogs/dialog.service.ts @@ -0,0 +1,178 @@ +import { Injectable } from '@angular/core'; +import { FileDetailsDialogComponent } from './file-details-dialog/file-details-dialog.component'; +import { MatDialog } from '@angular/material/dialog'; +import { + FileStatus, + FileUploadControllerService, + ManualRedactionControllerService, + ManualRedactionEntry, + Project +} from '@redaction/red-ui-http'; +import { ConfirmationDialogComponent } from '../common/confirmation-dialog/confirmation-dialog.component'; +import { NotificationService, NotificationType } from '../notification/notification.service'; +import { TranslateService } from '@ngx-translate/core'; +import { AppStateService, ProjectWrapper } from '../state/app-state.service'; +import { AddEditProjectDialogComponent } from './add-edit-project-dialog/add-edit-project-dialog.component'; +import { AssignOwnerDialogComponent } from './assign-owner-dialog/assign-owner-dialog.component'; +import { ProjectDetailsDialogComponent } from './project-details-dialog/project-details-dialog.component'; +import { AssignProjectMembersDialogComponent } from './assign-project-members-dialog/assign-project-members-dialog.component'; +import { ManualRedactionDialogComponent } from './manual-redaction-dialog/manual-redaction-dialog.component'; +import { Annotations } from '@pdftron/webviewer'; + +const dialogConfig = { + width: '600px', + maxWidth: '90vw', + autoFocus: false +}; + +@Injectable({ + providedIn: 'root' +}) +export class DialogService { + + constructor(private readonly _dialog: MatDialog, + private readonly _translateService: TranslateService, + private readonly _appStateService: AppStateService, + private readonly _fileUploadControllerService: FileUploadControllerService, + private readonly _notificationService: NotificationService, + private readonly _manualRedactionControllerService: ManualRedactionControllerService) { + + } + + public openFileDetailsDialog($event: MouseEvent, file: FileStatus) { + $event.stopPropagation(); + this._dialog.open(FileDetailsDialogComponent, { + ...dialogConfig, + data: file + }); + } + + public openDeleteFileDialog($event: MouseEvent, projectId: string, fileId: string, cb?: Function) { + $event.stopPropagation(); + this._dialog.open(ConfirmationDialogComponent, dialogConfig).afterClosed().subscribe(result => { + if (result) { + const file = this._appStateService.getFileById(projectId, fileId); + this._fileUploadControllerService.deleteFile(file.projectId, file.fileId).subscribe(async () => { + await this._appStateService.reloadActiveProjectFiles(); + if (cb) cb(); + }, () => { + this._notificationService.showToastNotification( + this._translateService.instant('delete-file-error.label', file), + null, + NotificationType.ERROR); + }); + } + }); + } + + public openManualRedactionDialog($event: ManualRedactionEntry) { + this._dialog.open(ManualRedactionDialogComponent, { + ...dialogConfig, + autoFocus: true, + data: $event + }); + } + + public acceptSuggestionAnnotation($event: MouseEvent, annotation: Annotations.Annotation, projectId: string, fileId: string) { + $event.stopPropagation(); + + const parts = annotation.Id.split(':'); + const annotationId = parts[parts.length - 1]; + + this._dialog.open(ConfirmationDialogComponent, dialogConfig) + .afterClosed().subscribe(result => { + if (result) { + this._manualRedactionControllerService.approveRequest(projectId, fileId, annotationId).subscribe(() => { + this._notificationService.showToastNotification(this._translateService.instant('manual-redaction.confirm-annotation.success.label'), null, NotificationType.SUCCESS); + }, (err) => { + this._notificationService.showToastNotification(this._translateService.instant('manual-redaction.confirm-annotation.failed.label', err), null, NotificationType.ERROR); + }); + } + }); + } + + public suggestRemoveAnnotation($event: MouseEvent, annotation: Annotations.Annotation, projectId: string, fileId: string) { + $event.stopPropagation(); + + const parts = annotation.Id.split(':'); + const annotationId = parts[parts.length - 1]; + + this._dialog.open(ConfirmationDialogComponent, { + width: '400px', + maxWidth: '90vw' + }).afterClosed().subscribe(result => { + if (result) { + this._manualRedactionControllerService.undo(projectId, fileId, annotationId).subscribe(ok => { + this._notificationService.showToastNotification(this._translateService.instant('manual-redaction.remove-annotation.success.label'), null, NotificationType.SUCCESS); + }, (err) => { + this._notificationService.showToastNotification(this._translateService.instant('manual-redaction.remove-annotation.failed.label', err), null, NotificationType.ERROR); + }); + } + }); + + } + + public openEditProjectDialog($event: MouseEvent, project: Project) { + $event.stopPropagation(); + this._dialog.open(AddEditProjectDialogComponent, { + ...dialogConfig, + autoFocus: true, + data: project + }); + } + + public openDeleteProjectDialog($event: MouseEvent, project: Project, cb?: Function) { + $event.stopPropagation(); + this._dialog.open(ConfirmationDialogComponent, dialogConfig) + .afterClosed().subscribe(async result => { + if (result) { + await this._appStateService.deleteProject(project); + if (cb) cb(); + } + }); + } + + public openAssignProjectOwnerDialog($event: MouseEvent, project: Project) { + $event.stopPropagation(); + this._dialog.open(AssignOwnerDialogComponent, { + ...dialogConfig, + data: { type: 'project', project: project } + }); + } + + public openAssignFileOwnerDialog($event: MouseEvent, file: FileStatus, cb?: Function) { + $event.stopPropagation(); + this._dialog.open(AssignOwnerDialogComponent, { + ...dialogConfig, + data: { type: 'file', file: file } + }).afterClosed().subscribe(() => { + if (cb) cb(); + }); + } + + public openProjectDetailsDialog($event: MouseEvent, project: ProjectWrapper) { + $event.stopPropagation(); + this._dialog.open(ProjectDetailsDialogComponent, { + ...dialogConfig, + data: project + }); + } + + public openAddProjectDialog(cb?: Function): void { + this._dialog.open(AddEditProjectDialogComponent, { + ...dialogConfig, + autoFocus: true + }).afterClosed().subscribe(result => { + if (result && cb) cb(); + }); + } + + public openAssignProjectMembersDialog(project: Project, cb?: Function) { + this._dialog.open(AssignProjectMembersDialogComponent, { + ...dialogConfig, + data: project + }).afterClosed().subscribe(result => { + if (result && cb) cb(); + }); + } +} diff --git a/apps/red-ui/src/app/screens/file/file-preview-screen/file-details-dialog/file-details-dialog.component.html b/apps/red-ui/src/app/dialogs/file-details-dialog/file-details-dialog.component.html similarity index 100% rename from apps/red-ui/src/app/screens/file/file-preview-screen/file-details-dialog/file-details-dialog.component.html rename to apps/red-ui/src/app/dialogs/file-details-dialog/file-details-dialog.component.html diff --git a/apps/red-ui/src/app/dialogs/file-details-dialog/file-details-dialog.component.scss b/apps/red-ui/src/app/dialogs/file-details-dialog/file-details-dialog.component.scss new file mode 100644 index 000000000..8f05b81de --- /dev/null +++ b/apps/red-ui/src/app/dialogs/file-details-dialog/file-details-dialog.component.scss @@ -0,0 +1,2 @@ +@import "../../../assets/styles/red-variables"; + diff --git a/apps/red-ui/src/app/screens/file/file-preview-screen/file-details-dialog/file-details-dialog.component.ts b/apps/red-ui/src/app/dialogs/file-details-dialog/file-details-dialog.component.ts similarity index 93% rename from apps/red-ui/src/app/screens/file/file-preview-screen/file-details-dialog/file-details-dialog.component.ts rename to apps/red-ui/src/app/dialogs/file-details-dialog/file-details-dialog.component.ts index 28e60db91..2f0d6f49c 100644 --- a/apps/red-ui/src/app/screens/file/file-preview-screen/file-details-dialog/file-details-dialog.component.ts +++ b/apps/red-ui/src/app/dialogs/file-details-dialog/file-details-dialog.component.ts @@ -1,7 +1,7 @@ import {Component, Inject, OnInit} from '@angular/core'; import {FileStatus, FileUploadControllerService} from "@redaction/red-ui-http"; import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; -import {download} from "../../../../utils/file-download-utils"; +import {download} from "../../utils/file-download-utils"; @Component({ selector: 'redaction-file-details-dialog', diff --git a/apps/red-ui/src/app/screens/file/manual-redaction-dialog/manual-redaction-dialog.component.html b/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-redaction-dialog.component.html similarity index 100% rename from apps/red-ui/src/app/screens/file/manual-redaction-dialog/manual-redaction-dialog.component.html rename to apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-redaction-dialog.component.html diff --git a/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-redaction-dialog.component.scss b/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-redaction-dialog.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/apps/red-ui/src/app/screens/file/manual-redaction-dialog/manual-redaction-dialog.component.ts b/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-redaction-dialog.component.ts similarity index 94% rename from apps/red-ui/src/app/screens/file/manual-redaction-dialog/manual-redaction-dialog.component.ts rename to apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-redaction-dialog.component.ts index 401a8b701..09c4bf056 100644 --- a/apps/red-ui/src/app/screens/file/manual-redaction-dialog/manual-redaction-dialog.component.ts +++ b/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-redaction-dialog.component.ts @@ -1,6 +1,6 @@ import {Component, Inject, OnInit} from '@angular/core'; import {FormBuilder, FormGroup, Validators} from "@angular/forms"; -import {AppStateService} from "../../../state/app-state.service"; +import {AppStateService} from "../../state/app-state.service"; import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; import { AddRedactionRequest, @@ -8,11 +8,11 @@ import { ManualRedactionControllerService, TypeValue } from "@redaction/red-ui-http"; -import {NotificationService, NotificationType} from "../../../notification/notification.service"; +import {NotificationService, NotificationType} from "../../notification/notification.service"; import {TranslateService} from "@ngx-translate/core"; import {map} from "rxjs/operators"; import {Observable} from "rxjs"; -import {UserService} from "../../../user/user.service"; +import {UserService} from "../../user/user.service"; @Component({ diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-details-dialog/project-details-dialog.component.html b/apps/red-ui/src/app/dialogs/project-details-dialog/project-details-dialog.component.html similarity index 100% rename from apps/red-ui/src/app/screens/project-overview-screen/project-details-dialog/project-details-dialog.component.html rename to apps/red-ui/src/app/dialogs/project-details-dialog/project-details-dialog.component.html diff --git a/apps/red-ui/src/app/dialogs/project-details-dialog/project-details-dialog.component.scss b/apps/red-ui/src/app/dialogs/project-details-dialog/project-details-dialog.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-details-dialog/project-details-dialog.component.ts b/apps/red-ui/src/app/dialogs/project-details-dialog/project-details-dialog.component.ts similarity index 91% rename from apps/red-ui/src/app/screens/project-overview-screen/project-details-dialog/project-details-dialog.component.ts rename to apps/red-ui/src/app/dialogs/project-details-dialog/project-details-dialog.component.ts index 826e263d0..8324c488b 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/project-details-dialog/project-details-dialog.component.ts +++ b/apps/red-ui/src/app/dialogs/project-details-dialog/project-details-dialog.component.ts @@ -1,8 +1,8 @@ import {Component, Inject, OnInit} from '@angular/core'; import {FileUploadControllerService, ReanalysisControllerService} from "@redaction/red-ui-http"; import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; -import {download} from "../../../utils/file-download-utils"; -import {ProjectWrapper} from "../../../state/app-state.service"; +import {download} from "../../utils/file-download-utils"; +import {ProjectWrapper} from "../../state/app-state.service"; @Component({ selector: 'redaction-project-details-dialog', diff --git a/apps/red-ui/src/app/screens/base-screen/base-screen.component.html b/apps/red-ui/src/app/screens/base-screen/base-screen.component.html index a7c27a36d..46735ddb7 100644 --- a/apps/red-ui/src/app/screens/base-screen/base-screen.component.html +++ b/apps/red-ui/src/app/screens/base-screen/base-screen.component.html @@ -1,6 +1,6 @@
- - diff --git a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.scss b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.scss index f778e58d1..01f94cbe5 100644 --- a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.scss +++ b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.scss @@ -1,4 +1,5 @@ -@import "../../../../assets/styles/red-variables.scss"; +@import "../../../../assets/styles/red-variables"; +@import "../../../../assets/styles/red-mixins"; redaction-pdf-viewer { position: absolute; @@ -11,6 +12,18 @@ redaction-pdf-viewer { } } +.actions-container { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 10px; +} + +.filename { + display: flex; + justify-content: center; +} + .right-fixed-container { padding: 0; width: $right-container-width; @@ -92,6 +105,7 @@ redaction-pdf-viewer { position: relative; display: flex; gap: 10px; + border-left: 2px solid transparent; redaction-annotation-icon { margin-top: 6px; diff --git a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts index 9ea60c614..9e95e7712 100644 --- a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts @@ -1,23 +1,11 @@ import { ChangeDetectorRef, Component, ElementRef, NgZone, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { - AddRedactionRequest, - FileUploadControllerService, - ManualRedactionControllerService, - ManualRedactionEntry, - ProjectControllerService, - StatusControllerService -} from '@redaction/red-ui-http'; -import { TranslateService } from '@ngx-translate/core'; -import {NotificationService, NotificationType} from '../../../notification/notification.service'; -import { MatDialog } from '@angular/material/dialog'; +import { ManualRedactionEntry, ReanalysisControllerService } from '@redaction/red-ui-http'; import { AppStateService } from '../../../state/app-state.service'; -import { FileDetailsDialogComponent } from './file-details-dialog/file-details-dialog.component'; import { ViewerSyncService } from '../service/viewer-sync.service'; import { Annotations } from '@pdftron/webviewer'; import { PdfViewerComponent } from '../pdf-viewer/pdf-viewer.component'; import { AnnotationUtils } from '../../../utils/annotation-utils'; -import { ManualRedactionDialogComponent } from '../manual-redaction-dialog/manual-redaction-dialog.component'; import { UserService } from '../../../user/user.service'; import { debounce } from '../../../utils/debounce'; import scrollIntoView from 'scroll-into-view-if-needed'; @@ -26,7 +14,7 @@ import { FiltersService } from '../service/filters.service'; import { FileDownloadService } from '../service/file-download.service'; import { saveAs } from 'file-saver'; import { FileType } from '../model/file-type'; -import { ConfirmationDialogComponent } from '../../../common/confirmation-dialog/confirmation-dialog.component'; +import { DialogService } from '../../../dialogs/dialog.service'; @Component({ selector: 'redaction-file-preview-screen', @@ -54,17 +42,12 @@ export class FilePreviewScreenComponent implements OnInit { public readonly appStateService: AppStateService, private readonly _changeDetectorRef: ChangeDetectorRef, private readonly _activatedRoute: ActivatedRoute, - private readonly _statusControllerService: StatusControllerService, - private readonly _translateService: TranslateService, - private readonly _notificationService: NotificationService, private readonly _viewerSyncService: ViewerSyncService, - private readonly _dialog: MatDialog, + private readonly _dialogService: DialogService, private readonly _router: Router, - private readonly _manualRedactionControllerService: ManualRedactionControllerService, private readonly _userService: UserService, private readonly _fileDownloadService: FileDownloadService, - private readonly _fileUploadControllerService: FileUploadControllerService, - private readonly _projectControllerService: ProjectControllerService, + private readonly _reanalysisControllerService: ReanalysisControllerService, private readonly _filtersService: FiltersService, private ngZone: NgZone) { this._activatedRoute.params.subscribe(params => { @@ -102,15 +85,28 @@ export class FilePreviewScreenComponent implements OnInit { this._viewerSyncService.activateViewer('ANNOTATED'); } - public showDetailsDialog($event: MouseEvent) { + public openFileDetailsDialog($event: MouseEvent) { + this._dialogService.openFileDetailsDialog($event, this.appStateService.activeFile); + } + + public reanalyseFile($event: MouseEvent) { $event.stopPropagation(); - this._dialog.open(FileDetailsDialogComponent, { - width: '600px', - maxWidth: '90vw', - data: this.appStateService.activeFile + this._reanalysisControllerService.reanalyzeFile(this.appStateService.activeProject.project.projectId, this.fileId).subscribe(async () => { + await this.appStateService.reloadActiveProjectFiles(); }); } + public openDeleteFileDialog($event: MouseEvent) { + this._dialogService.openDeleteFileDialog($event, this.projectId, this.fileId, () => { + this._router.navigate([`/ui/projects/${this.projectId}`]); + }); + } + + public openAssignFileOwnerDialog($event: MouseEvent) { + const file = this.appStateService.getFileById(this.projectId, this.fileId); + this._dialogService.openAssignFileOwnerDialog($event, file); + } + public fileReady(viewer: string) { this._readyViewers.push(viewer); this._changeDetectorRef.detectChanges(); @@ -165,13 +161,9 @@ export class FilePreviewScreenComponent implements OnInit { this._viewerComponent.navigateToPage(pageNumber); } - handleManualAnnotationRequest($event: ManualRedactionEntry) { + public openManualRedactionDialog($event: ManualRedactionEntry) { this.ngZone.run(() => { - this._dialog.open(ManualRedactionDialogComponent, { - width: '600px', - maxWidth: '90vw', - data: $event - }); + this._dialogService.openManualRedactionDialog($event); }); } @@ -213,59 +205,24 @@ export class FilePreviewScreenComponent implements OnInit { } } - getType(annotation: Annotations.Annotation): string { + public getType(annotation: Annotations.Annotation): string { return AnnotationUtils.getType(annotation); } - getDictionary(annotation: Annotations.Annotation): string { + public getDictionary(annotation: Annotations.Annotation): string { return AnnotationUtils.getDictionary(annotation); } - acceptSuggestionAnnotation($event: MouseEvent, annotation: Annotations.Annotation) { - $event.stopPropagation(); + public acceptSuggestionAnnotation($event: MouseEvent, annotation: Annotations.Annotation) { this.ngZone.run(() => { - const dialogRef = this._dialog.open(ConfirmationDialogComponent, { - width: '400px', - maxWidth: '90vw' - }); - - const parts = annotation.Id.split(':'); - const annotationId = parts[parts.length - 1]; - - dialogRef.afterClosed().subscribe(result => { - if (result) { - this._manualRedactionControllerService.approveRequest(this.appStateService.activeProjectId, this.appStateService.activeFile.fileId, annotationId).subscribe(ok => { - this._notificationService.showToastNotification(this._translateService.instant('manual-redaction.confirm-annotation.success.label'), null, NotificationType.SUCCESS); - }, (err) => { - this._notificationService.showToastNotification(this._translateService.instant('manual-redaction.confirm-annotation.failed.label', err), null, NotificationType.ERROR); - }); - } - }); + this._dialogService.acceptSuggestionAnnotation($event, annotation, this.projectId, this.fileId); }); } - suggestRemoveAnnotation($event: MouseEvent, annotation: Annotations.Annotation) { - $event.stopPropagation(); + public suggestRemoveAnnotation($event: MouseEvent, annotation: Annotations.Annotation) { this.ngZone.run(() => { - const dialogRef = this._dialog.open(ConfirmationDialogComponent, { - width: '400px', - maxWidth: '90vw' - }); - - const parts = annotation.Id.split(':'); - const annotationId = parts[parts.length - 1]; - - dialogRef.afterClosed().subscribe(result => { - if (result) { - this._manualRedactionControllerService.undo(this.appStateService.activeProjectId, this.appStateService.activeFile.fileId, annotationId).subscribe(ok => { - this._notificationService.showToastNotification(this._translateService.instant('manual-redaction.remove-annotation.success.label'), null, NotificationType.SUCCESS); - }, (err) => { - this._notificationService.showToastNotification(this._translateService.instant('manual-redaction.remove-annotation.failed.label', err), null, NotificationType.ERROR); - }); - } - }); + this._dialogService.suggestRemoveAnnotation($event, annotation, this.projectId, this.fileId); }); - } public downloadFile(type: FileType | string) { @@ -312,8 +269,7 @@ export class FilePreviewScreenComponent implements OnInit { this.expandedFilters[key] = value; } - isManuallyAddedAnnotation(annotation: Annotations.Annotation) { + public isManuallyAddedAnnotation(annotation: Annotations.Annotation) { return annotation.Id.indexOf('request:') >= 0; } - } diff --git a/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts b/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts index 4d8476564..37d024de8 100644 --- a/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts +++ b/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts @@ -9,15 +9,13 @@ import { Output, ViewChild } from '@angular/core'; -import {AppConfigKey, AppConfigService} from '../../../app-config/app-config.service'; -import {FileStatus, ManualRedactionEntry, Rectangle} from '@redaction/red-ui-http'; -import WebViewer, {Annotations, WebViewerInstance} from '@pdftron/webviewer'; -import {TranslateService} from '@ngx-translate/core'; -import {ViewerSyncService} from '../service/viewer-sync.service'; -import {MatDialog} from "@angular/material/dialog"; -import {FileDownloadService} from "../service/file-download.service"; -import {FileType} from "../model/file-type"; -import {AppStateService} from "../../../state/app-state.service"; +import { AppConfigKey, AppConfigService } from '../../../app-config/app-config.service'; +import { FileStatus, ManualRedactionEntry, Rectangle } from '@redaction/red-ui-http'; +import WebViewer, { Annotations, WebViewerInstance } from '@pdftron/webviewer'; +import { TranslateService } from '@ngx-translate/core'; +import { ViewerSyncService } from '../service/viewer-sync.service'; +import { FileDownloadService } from '../service/file-download.service'; +import { FileType } from '../model/file-type'; @Component({ @@ -36,18 +34,15 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnDestroy { @Output() manualAnnotationRequested = new EventEmitter(); @Output() pageChanged = new EventEmitter(); - @ViewChild('viewer', {static: true}) viewer: ElementRef; + @ViewChild('viewer', { static: true }) viewer: ElementRef; wvInstance: WebViewerInstance; _fileData: Blob; - constructor( - private readonly _appStateService: AppStateService, - private readonly _viewerSyncService: ViewerSyncService, - private readonly _translateService: TranslateService, - private readonly _fileDownloadService: FileDownloadService, - private readonly _dialog: MatDialog, - private readonly _appConfigService: AppConfigService) { + constructor(private readonly _viewerSyncService: ViewerSyncService, + private readonly _translateService: TranslateService, + private readonly _fileDownloadService: FileDownloadService, + private readonly _appConfigService: AppConfigService) { } ngOnInit() { @@ -61,10 +56,10 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnDestroy { ngAfterViewInit(): void { this._fileDownloadService.loadFile(this.fileType, this.fileId, (data) => { - this._fileData = data + this._fileData = data; }, () => this._fileData).subscribe(() => { this._loadViewer(this._fileData); - }) + }); } private _loadViewer(pdfBlob: any) { @@ -98,7 +93,7 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnDestroy { }); instance.docViewer.on('documentLoaded', this.wvDocumentLoadedHandler); - instance.loadDocument(pdfBlob, {filename: this.fileStatus ? this.fileStatus.filename : 'document.pdf'}); + instance.loadDocument(pdfBlob, { filename: this.fileStatus ? this.fileStatus.filename : 'document.pdf' }); }); } @@ -128,7 +123,7 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnDestroy { onClick: () => { const selectedQuads = this.wvInstance.docViewer.getSelectedTextQuads(); const text = this.wvInstance.docViewer.getSelectedText(); - const entry: ManualRedactionEntry = {positions: []}; + const entry: ManualRedactionEntry = { positions: [] }; for (const key of Object.keys(selectedQuads)) { for (const quad of selectedQuads[key]) { entry.positions.push(this.toPosition(parseInt(key, 10), quad)); @@ -152,7 +147,7 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnDestroy { }, height: height, width: selectedQuad.x3 - selectedQuad.x4 - } + }; } private _configureHeader() { diff --git a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.html b/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.html index c90a8fc62..91f87587b 100644 --- a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.html +++ b/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.html @@ -69,7 +69,7 @@
- @@ -85,13 +85,13 @@ [matTooltip]="'project-listing.delete.action.label'|translate"> - - - - + + + - @@ -168,7 +177,7 @@
-
+
+ +
+
+ + {{ 'project-overview.legend.contains-hints.label' | translate }} +
+
+ + {{ 'project-overview.legend.contains-redactions.label' | translate }} +
+
+ + {{ 'project-overview.legend.contains-suggestions.label' | translate }} +
+
diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.scss b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.scss index 0a91f19ab..200c9f7f6 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.scss +++ b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.scss @@ -13,6 +13,35 @@ justify-content: flex-end; } +.select-all-container { + display: flex; + gap: 16px; + align-items: center; + + .select-oval { + margin-left: 0; + } +} + +.select-oval { + width: 20px; + height: 20px; + border-radius: 50%; + border: 1px solid $grey-5; + background-color: $white; + padding: 0; + margin-left: 16px; + cursor: pointer; + + &.active { + background-color: $primary; + } + + &.placeholder { + visibility: hidden; + } +} + .table-item { .needs-work { display: flex; @@ -41,3 +70,19 @@ gap: 5px; } } + +.legend { + display: flex; + flex-direction: column; + gap: 8px; + + > div { + display: flex; + gap: 8px; + align-items: center; + } +} + +.mt-32 { + margin-top: 32px; +} 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 ee6da7c66..bb45785a5 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 @@ -1,28 +1,17 @@ -import {ChangeDetectorRef, Component, OnDestroy, OnInit} from '@angular/core'; -import {ActivatedRoute, Router} from '@angular/router'; -import { - FileStatus, - FileUploadControllerService, - ProjectControllerService, - ReanalysisControllerService, - StatusControllerService -} from '@redaction/red-ui-http'; -import {NotificationService, NotificationType} from '../../notification/notification.service'; -import {TranslateService} from '@ngx-translate/core'; -import {ConfirmationDialogComponent} from '../../common/confirmation-dialog/confirmation-dialog.component'; -import {MatDialog} from '@angular/material/dialog'; -import {AppStateService} from '../../state/app-state.service'; -import {ProjectDetailsDialogComponent} from './project-details-dialog/project-details-dialog.component'; -import {FileDropOverlayService} from '../../upload/file-drop/service/file-drop-overlay.service'; -import {FileUploadModel} from '../../upload/model/file-upload.model'; -import {FileUploadService} from '../../upload/file-upload.service'; -import {UploadStatusOverlayService} from '../../upload/upload-status-dialog/service/upload-status-overlay.service'; -import {AddEditProjectDialogComponent} from '../project-listing-screen/add-edit-project-dialog/add-edit-project-dialog.component'; -import {UserService} from '../../user/user.service'; -import {SortingOption} from '../../utils/types'; -import {DoughnutChartConfig} from '../../components/simple-doughnut-chart/simple-doughnut-chart.component'; -import {groupBy} from '../../utils/functions'; -import {AssignOwnerDialogComponent} from "../../components/project-members-dialog/assign-owner-dialog.component"; +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { FileStatus, ReanalysisControllerService, StatusControllerService } from '@redaction/red-ui-http'; +import { NotificationService } from '../../notification/notification.service'; +import { AppStateService } from '../../state/app-state.service'; +import { FileDropOverlayService } from '../../upload/file-drop/service/file-drop-overlay.service'; +import { FileUploadModel } from '../../upload/model/file-upload.model'; +import { FileUploadService } from '../../upload/file-upload.service'; +import { UploadStatusOverlayService } from '../../upload/upload-status-dialog/service/upload-status-overlay.service'; +import { UserService } from '../../user/user.service'; +import { SortingOption } from '../../utils/types'; +import { DoughnutChartConfig } from '../../components/simple-doughnut-chart/simple-doughnut-chart.component'; +import { groupBy } from '../../utils/functions'; +import { DialogService } from '../../dialogs/dialog.service'; @Component({ @@ -32,31 +21,28 @@ import {AssignOwnerDialogComponent} from "../../components/project-members-dialo }) export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { private _fileStatusInterval; + private _selectedFileIds: string[] = []; public sortingOptions: SortingOption[] = [ - {label: 'project-overview.sorting.recent.label', order: 'desc', column: 'lastUpdated'}, - {label: 'project-overview.sorting.alphabetically.label', order: 'asc', column: 'filename'}, - {label: 'project-overview.sorting.number-of-pages.label', order: 'asc', column: 'numberOfPages'}, - {label: 'project-overview.sorting.number-of-analyses.label', order: 'desc', column: 'numberOfAnalyses'} + { label: 'project-overview.sorting.recent.label', order: 'desc', column: 'lastUpdated' }, + { label: 'project-overview.sorting.alphabetically.label', order: 'asc', column: 'filename' }, + { label: 'project-overview.sorting.number-of-pages.label', order: 'asc', column: 'numberOfPages' }, + { label: 'project-overview.sorting.number-of-analyses.label', order: 'desc', column: 'numberOfAnalyses' } ]; public sortingOption: SortingOption = this.sortingOptions[0]; public documentsChartData: DoughnutChartConfig[] = []; constructor(public readonly appStateService: AppStateService, - private readonly _changeDetectorRef: ChangeDetectorRef, private readonly _activatedRoute: ActivatedRoute, private readonly _statusControllerService: StatusControllerService, - private readonly _translateService: TranslateService, private readonly _notificationService: NotificationService, - private readonly _dialog: MatDialog, + private readonly _dialogService: DialogService, private readonly _fileUploadService: FileUploadService, private readonly _uploadStatusOverlayService: UploadStatusOverlayService, private readonly _reanalysisControllerService: ReanalysisControllerService, private readonly _router: Router, private readonly _userService: UserService, - private readonly _fileDropOverlayService: FileDropOverlayService, - private readonly _fileUploadControllerService: FileUploadControllerService, - private readonly _projectControllerService: ProjectControllerService) { + private readonly _fileDropOverlayService: FileDropOverlayService) { this._activatedRoute.params.subscribe(params => { this.appStateService.activateProject(params.projectId); }); @@ -91,7 +77,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { } public get displayMembers() { - return this.members.slice(0, 6) + return this.members.slice(0, 6); } public get overflowCount() { @@ -112,72 +98,66 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { const groups = groupBy(this.appStateService.activeProject.files, 'status'); this.documentsChartData = []; for (const key of Object.keys(groups)) { - this.documentsChartData.push({value: groups[key].length, color: key, label: key}); + this.documentsChartData.push({ value: groups[key].length, color: key, label: key }); } } - public openDeleteFileDialog($event: MouseEvent, fileStatus: FileStatus) { + public toggleFileSelected($event: MouseEvent, file: FileStatus) { $event.stopPropagation(); - const dialogRef = this._dialog.open(ConfirmationDialogComponent, { - width: '400px', - maxWidth: '90vw', - autoFocus: false - }); + const idx = this._selectedFileIds.indexOf(file.fileId); + if (idx === -1) { + this._selectedFileIds.push(file.fileId); + } else { + this._selectedFileIds.splice(idx, 1); + } + } - dialogRef.afterClosed().subscribe(result => { - if (result) { - this._fileUploadControllerService.deleteFile(fileStatus.projectId, fileStatus.fileId).subscribe(() => { - this._getFileStatus(); - }, () => { - this._notificationService.showToastNotification(this._translateService.instant('project-overview.delete-file-error.label', fileStatus), null, NotificationType.ERROR); - }); - } + public toggleSelectAll() { + if (this.areAllFilesSelected()) { + this._selectedFileIds = []; + } else { + this._selectedFileIds = this.appStateService.activeProject.files.map(file => file.fileId); + } + } + + public areAllFilesSelected() { + return this._selectedFileIds.length === this.appStateService.activeProject.files.length; + } + + public isFileSelected(file: FileStatus) { + return this._selectedFileIds.indexOf(file.fileId) !== -1; + } + + public openDeleteFileDialog($event: MouseEvent, fileStatus: FileStatus) { + this._dialogService.openDeleteFileDialog($event, fileStatus.projectId, fileStatus.fileId, () => { + this._calculateChartConfig(); }); } public openDetailsDialog($event: MouseEvent) { - $event.stopPropagation(); - this._dialog.open(ProjectDetailsDialogComponent, { - width: '600px', - maxWidth: '90vw', - data: this.appStateService.activeProject - }); + this._dialogService.openProjectDetailsDialog($event, this.appStateService.activeProject); } public openEditProjectDialog($event: MouseEvent) { - $event.stopPropagation(); - this._dialog.open(AddEditProjectDialogComponent, { - width: '400px', - maxWidth: '90vw', - data: this.appStateService.activeProject.project - }); + this._dialogService.openEditProjectDialog($event, this.appStateService.activeProject.project); } public openDeleteProjectDialog($event: MouseEvent) { - $event.stopPropagation(); - const dialogRef = this._dialog.open(ConfirmationDialogComponent, { - width: '400px', - maxWidth: '90vw' - }); - - dialogRef.afterClosed().subscribe(result => { - if (result) { - this.appStateService.deleteProject(this.appStateService.activeProject.project); - this.ngOnDestroy(); - this._router.navigate(['/ui/projects']); - } + this._dialogService.openDeleteProjectDialog($event, this.appStateService.activeProject.project, () => { + this._router.navigate(['/ui/projects']); }); } public openAssignProjectMembersDialog() { - this._dialog.open(AssignOwnerDialogComponent, { - width: '400px', - maxWidth: '90vw', - autoFocus: false, - data: {type: 'project', project: this.appStateService.activeProject.project } - }).afterClosed().subscribe( ()=>{ + this._dialogService.openAssignProjectMembersDialog(this.activeProject, () => { this._getFileStatus(); - }) + }); + } + + public openAssignFileOwnerDialog($event: MouseEvent, file: FileStatus) { + this._dialogService.openAssignFileOwnerDialog($event, file, () => { + this._getFileStatus(); + }); } public reanalyseFile($event: MouseEvent, fileStatus: FileStatus) { @@ -212,17 +192,6 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { return fileStatus === 'PROCESSING' || fileStatus === 'REVIEWED' || true; } - public assignPeopleToFile($event: MouseEvent, fileStatus: FileStatus) { - $event.stopPropagation(); - this._dialog.open(AssignOwnerDialogComponent, { - width: '400px', - maxWidth: '90vw', - data: {type: 'file', file: fileStatus} - }).afterClosed().subscribe( ()=>{ - this._getFileStatus(); - }) - } - public getFileOwnerUsername(fileStatus: FileStatus) { return this._userService.getNameForId(fileStatus.currentReviewer); } 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 f6b1a5f3e..eae08b4d1 100644 --- a/apps/red-ui/src/app/state/app-state.service.ts +++ b/apps/red-ui/src/app/state/app-state.service.ts @@ -109,6 +109,10 @@ export class AppStateService { return this.allProjects.find(project => project.project.projectId === id); } + public getFileById(projectId: string, fileId: string) { + return this.getProjectById(projectId).files.find(file => file.fileId === fileId); + } + async loadAllProjects() { const projects = await this._projectControllerService.getProjects().toPromise(); if (projects) { @@ -124,7 +128,7 @@ export class AppStateService { async getFiles(projectId: string) { const files = await this._statusControllerService.getProjectStatus(projectId).toPromise(); - const project = this._appState.projects.find(p => p.project.projectId === projectId); + const project = this.getProjectById(projectId); project.files = files; this._computeStats(); return files; @@ -152,7 +156,7 @@ export class AppStateService { } deleteProject(project: Project) { - this._projectControllerService.deleteProject(project.projectId).subscribe(() => { + return this._projectControllerService.deleteProject(project.projectId).toPromise().then(() => { const index = this._appState.projects.findIndex(p => p.project.projectId === project.projectId); this._appState.projects.splice(index, 1); this._appState.projects = [...this._appState.projects]; diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index f5eeaa360..f8eee0c5c 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -372,6 +372,17 @@ }, "no-project": { "label": "Requested project: {{projectId}} does not exist! Back to Project Listing. " + }, + "legend": { + "contains-hints": { + "label": "Contains hints " + }, + "contains-redactions": { + "label": "Contains redactions " + }, + "contains-suggestions": { + "label": "Contains suggestions for redaction " + } } }, "file-preview": { @@ -457,13 +468,12 @@ "label": "Unassigned" } }, - - "assign-file-owner":{ + "assign-file-owner": { "dialog": { "single-user": { "label": "Reviewer" }, - "title":{ + "title": { "label": "Manage File Reviewer" }, "save": { @@ -471,8 +481,7 @@ } } }, - - "assign-project-owner":{ + "assign-project-owner": { "dialog": { "single-user": { "label": "Owner" @@ -480,7 +489,7 @@ "multi-user": { "label": "Members" }, - "title":{ + "title": { "label": "Manage Project Owner and Members" }, "save": { @@ -488,7 +497,6 @@ } } }, - "unassigned": "Unassigned", "under-review": "Under review", "under-approval": "Under approval", diff --git a/apps/red-ui/src/assets/icons/general/info.svg b/apps/red-ui/src/assets/icons/general/info.svg index 2a5ae4e0a..731218b6a 100644 --- a/apps/red-ui/src/assets/icons/general/info.svg +++ b/apps/red-ui/src/assets/icons/general/info.svg @@ -1,14 +1,7 @@ - - - - - - - - - + + + info + + + + \ No newline at end of file diff --git a/apps/red-ui/src/assets/styles/red-page-layout.scss b/apps/red-ui/src/assets/styles/red-page-layout.scss index 0263fd26e..56b3d5d08 100644 --- a/apps/red-ui/src/assets/styles/red-page-layout.scss +++ b/apps/red-ui/src/assets/styles/red-page-layout.scss @@ -119,12 +119,6 @@ html, body { display: block; } -.details-button { - position: fixed !important; - bottom: 20px; - right: 20px; -} - .detail-row { opacity: 1; font-family: Inconsolata, monospace, monospace; @@ -165,6 +159,11 @@ html, body { .menu { display: flex; align-items: center; + overflow: hidden; + + &.right { + justify-content: flex-end; + } } } @@ -173,17 +172,15 @@ html, body { color: $accent; font-weight: 600; @include line-clamp(1); - max-width: 320px; - - mat-icon { - height: 10px; - width: 10px; - padding: 0 8px; - } + min-width: 25px; &:last-child { color: $primary; } + + .mat-icon { + vertical-align: middle; + } } .divider { diff --git a/apps/red-ui/src/assets/styles/red-tables.scss b/apps/red-ui/src/assets/styles/red-tables.scss index 6f147f25c..c977794f9 100644 --- a/apps/red-ui/src/assets/styles/red-tables.scss +++ b/apps/red-ui/src/assets/styles/red-tables.scss @@ -19,11 +19,11 @@ .table-col-names { display: flex; text-transform: uppercase; + border-bottom: 1px solid rgba(226, 228, 233, 0.9); > div { padding: 8px 16px; font-weight: 600; - border-bottom: 1px solid rgba(226, 228, 233, 0.9); } } diff --git a/apps/red-ui/src/assets/styles/red-text-styles.scss b/apps/red-ui/src/assets/styles/red-text-styles.scss index 57c14662a..bfe14535c 100644 --- a/apps/red-ui/src/assets/styles/red-text-styles.scss +++ b/apps/red-ui/src/assets/styles/red-text-styles.scss @@ -29,6 +29,13 @@ a { font-weight: 600; } +.page-title { + font-size: 13px; + font-weight: 600; + line-height: 18px; + text-align: center; +} + .all-caps-label { text-transform: uppercase; opacity: 0.7; diff --git a/apps/red-ui/src/assets/styles/red-toggle.scss b/apps/red-ui/src/assets/styles/red-toggle.scss index f8794b753..0d2150395 100644 --- a/apps/red-ui/src/assets/styles/red-toggle.scss +++ b/apps/red-ui/src/assets/styles/red-toggle.scss @@ -1,8 +1,4 @@ mat-slide-toggle { - display: flex !important; - flex-direction: row-reverse; - gap: 8px; - .mat-slide-toggle-bar { height: 16px !important; width: 30px !important;