red-ui/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts

361 lines
13 KiB
TypeScript

import { ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import {
FileStatus,
FileUploadControllerService,
ReanalysisControllerService,
StatusControllerService
} from '@redaction/red-ui-http';
import { NotificationService, NotificationType } 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 { humanize } from '../../utils/functions';
import { DialogService } from '../../dialogs/dialog.service';
import { TranslateService } from '@ngx-translate/core';
import { FileActionService } from '../file/service/file-action.service';
import { FilterModel } from '../../common/filter/model/filter.model';
import * as moment from 'moment';
import { SortingComponent, SortingOption } from '../../components/sorting/sorting.component';
import { ProjectDetailsComponent } from './project-details/project-details.component';
import { FileStatusWrapper } from '../file/model/file-status.wrapper';
import {
annotationFilterChecker,
fileAddedFilterChecker,
getFilteredEntities,
keyChecker
} from '../../common/filter/utils/filter-utils';
@Component({
selector: 'redaction-project-overview-screen',
templateUrl: './project-overview-screen.component.html',
styleUrls: ['./project-overview-screen.component.scss']
})
export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
private _selectedFileIds: string[] = [];
public bulkSelectActive = false;
public statusFilters: FilterModel[];
public peopleFilters: FilterModel[];
// public addedDateFilters: FilterModel[];
public needsWorkFilters: FilterModel[];
public displayedFiles: FileStatusWrapper[] = [];
@ViewChild('projectDetailsComponent', { static: true })
private _projectDetailsComponent: ProjectDetailsComponent;
public sortingOption: SortingOption = { column: 'added', order: 'desc' };
constructor(
public readonly appStateService: AppStateService,
public readonly userService: UserService,
private readonly _activatedRoute: ActivatedRoute,
private readonly _fileUploadControllerService: FileUploadControllerService,
private readonly _statusControllerService: StatusControllerService,
private readonly _notificationService: NotificationService,
private readonly _dialogService: DialogService,
private readonly _fileActionService: FileActionService,
private readonly _fileUploadService: FileUploadService,
private readonly _uploadStatusOverlayService: UploadStatusOverlayService,
private readonly _reanalysisControllerService: ReanalysisControllerService,
private readonly _router: Router,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _translateService: TranslateService,
private readonly _fileDropOverlayService: FileDropOverlayService
) {
this._activatedRoute.params.subscribe((params) => {
this.appStateService.activateProject(params.projectId);
});
this.appStateService.fileStatusChanged.subscribe(() => {
this._calculateData();
});
}
ngOnInit(): void {
this._fileDropOverlayService.initFileDropHandling();
this._calculateData();
this._displayNewRuleToast();
}
ngOnDestroy(): void {
this._fileDropOverlayService.cleanupFileDropHandling();
}
public get user() {
return this.userService.user;
}
public isPending(fileStatusWrapper: FileStatusWrapper) {
return fileStatusWrapper.status === FileStatus.StatusEnum.UNPROCESSED;
}
public isError(fileStatusWrapper: FileStatusWrapper) {
return fileStatusWrapper.status === FileStatus.StatusEnum.ERROR;
}
public isProcessing(fileStatusWrapper: FileStatusWrapper) {
return [FileStatus.StatusEnum.REPROCESS, FileStatus.StatusEnum.PROCESSING].includes(
fileStatusWrapper.status
);
}
public toggleBulkSelect() {
this.bulkSelectActive = !this.bulkSelectActive;
}
private _displayNewRuleToast() {
// @ts-ignore
if (
!this.appStateService.activeProject.files.filter((file) =>
this.appStateService.fileNotUpToDateWithDictionary(file)
).length
) {
return;
}
this._notificationService.showToastNotification(
`${this._translateService.instant(
'project-overview.new-rule.toast.message-project'
)} <span class="pill">${this._translateService.instant(
'project-overview.new-rule.label'
)}</span>`,
null,
NotificationType.WARNING,
{
disableTimeOut: true,
positionClass: 'toast-top-left',
actions: [
{
title: this._translateService.instant(
'project-overview.new-rule.toast.actions.reanalyse-all'
),
action: () =>
this._reanalysisControllerService
.reanalyzeProject(
this.appStateService.activeProject.project.projectId
)
.toPromise()
.then(() => this.reloadProjects())
},
{
title: this._translateService.instant(
'project-overview.new-rule.toast.actions.later'
)
}
]
}
);
}
public reloadProjects() {
this.appStateService.getFiles().then(() => {
this._calculateData();
});
}
private _calculateData(): void {
this._computeAllFilters();
this._filterFiles();
this._projectDetailsComponent.calculateChartConfig();
this._changeDetectorRef.detectChanges();
}
public toggleFileSelected($event: MouseEvent, file: FileStatusWrapper) {
$event.stopPropagation();
const idx = this._selectedFileIds.indexOf(file.fileId);
if (idx === -1) {
this._selectedFileIds.push(file.fileId);
} else {
this._selectedFileIds.splice(idx, 1);
}
}
public toggleSelectAll() {
if (this.areAllFilesSelected()) {
this._selectedFileIds = [];
} else {
this._selectedFileIds = this.appStateService.activeProject.files.map(
(file) => file.fileId
);
}
}
public areAllFilesSelected() {
return (
this.appStateService.activeProject.files.length !== 0 &&
this._selectedFileIds.length === this.appStateService.activeProject.files.length
);
}
public isFileSelected(file: FileStatusWrapper) {
return this._selectedFileIds.indexOf(file.fileId) !== -1;
}
public openDeleteFileDialog($event: MouseEvent, fileStatusWrapper: FileStatusWrapper) {
this._dialogService.openDeleteFileDialog(
$event,
fileStatusWrapper.projectId,
fileStatusWrapper.fileId,
() => {
this._calculateData();
}
);
}
downloadFileRedactionReport($event: MouseEvent, file: FileStatusWrapper) {
$event.stopPropagation();
this.appStateService.downloadFileRedactionReport(file);
}
public assignReviewer($event: MouseEvent, file: FileStatusWrapper) {
$event.stopPropagation();
this._fileActionService.assignProjectReviewer(file, () => this._calculateData());
}
public reanalyseFile($event: MouseEvent, fileStatusWrapper: FileStatusWrapper) {
$event.stopPropagation();
this._reanalysisControllerService
.reanalyzeFile(
this.appStateService.activeProject.project.projectId,
fileStatusWrapper.fileId
)
.subscribe(() => {
this.reloadProjects();
});
}
public fileId(index, item) {
return item.fileId;
}
public uploadFiles(files: FileList | File[]) {
const uploadFiles: FileUploadModel[] = [];
for (let i = 0; i < files.length; i++) {
const file = files[i];
uploadFiles.push({
file: file,
progress: 0,
completed: false,
error: null
});
}
this._fileUploadService.uploadFiles(uploadFiles);
this._uploadStatusOverlayService.openStatusOverlay();
}
public canOpenFile(fileStatusWrapper: FileStatusWrapper): boolean {
return (
!this.isError(fileStatusWrapper) &&
!this.isProcessing(fileStatusWrapper) &&
this.appStateService.isReviewerOrOwner(fileStatusWrapper)
);
}
public sortingOptionChanged(option: SortingOption) {
this.sortingOption = option;
}
private _computeAllFilters() {
const allDistinctFileStatusWrapper = new Set<string>();
const allDistinctPeople = new Set<string>();
const allDistinctAddedDates = new Set<string>();
const allDistinctNeedsWork = new Set<string>();
// All people
this.appStateService.activeProject.project.memberIds.forEach((memberId) =>
allDistinctPeople.add(memberId)
);
// File statuses
this.appStateService.activeProject.files.forEach((file) =>
allDistinctFileStatusWrapper.add(file.status)
);
// Added dates
this.appStateService.activeProject.files.forEach((file) =>
allDistinctAddedDates.add(moment(file.added).format('DD/MM/YYYY'))
);
// Needs work
this.appStateService.activeProject.files.forEach((file) => {
if (file.hasHints) allDistinctNeedsWork.add('hints');
if (file.hasRedactions) allDistinctNeedsWork.add('redactions');
if (file.hasRequests) allDistinctNeedsWork.add('requests');
});
this.statusFilters = [];
allDistinctFileStatusWrapper.forEach((status) => {
this.statusFilters.push({
key: status,
label: humanize(status)
});
});
this.peopleFilters = [];
allDistinctPeople.forEach((userId) => {
this.peopleFilters.push({
key: userId,
label: this.userService.getNameForId(userId)
});
});
this.needsWorkFilters = [];
allDistinctNeedsWork.forEach((type) => {
this.needsWorkFilters.push({
key: type,
label: this._translateService.instant('filter.' + type)
});
});
}
filtersChanged() {
this._filterFiles();
}
private _filterFiles() {
const filters = [
{ values: this.statusFilters, checker: keyChecker('status') },
{ values: this.peopleFilters, checker: keyChecker('currentReviewer') },
{ values: this.needsWorkFilters, checker: annotationFilterChecker, matchAll: true }
];
this.displayedFiles = getFilteredEntities(
this.appStateService.activeProject.files,
filters
);
this._changeDetectorRef.detectChanges();
}
requestApprovalOrApproveFile($event: MouseEvent, fileStatusWrapper: FileStatusWrapper) {
$event.stopPropagation();
this._fileActionService.requestApprovalOrApproveFile(fileStatusWrapper).subscribe(() => {
this.reloadProjects();
});
}
undoApproveOrUnderApproval($event: MouseEvent, fileStatusWrapper: FileStatusWrapper) {
$event.stopPropagation();
this._fileActionService.undoApproveOrUnderApproval(fileStatusWrapper).subscribe(() => {
this.reloadProjects();
});
}
isApprovedOrUnderApproval(fileStatusWrapper: FileStatusWrapper) {
return (
fileStatusWrapper.status === 'APPROVED' || fileStatusWrapper.status === 'UNDER_APPROVAL'
);
}
canApprove(fileStatusWrapper: FileStatusWrapper) {
return (
fileStatusWrapper.status === 'UNDER_REVIEW' ||
fileStatusWrapper.status === 'UNDER_APPROVAL'
);
}
}