fixed stuff

This commit is contained in:
Timo Bejan 2020-11-14 02:19:35 +02:00
parent 45a8ba316c
commit b8f072e20a
25 changed files with 238 additions and 177 deletions

View File

@ -1,77 +1,49 @@
<div class="filter-root">
<button
color="accent"
mat-button
[class.arrow-button]="hasArrow"
[matMenuTriggerFor]="filterMenu"
[ngClass]="{ overlay: hasActiveFilters }"
>
<button color="accent" mat-button [class.arrow-button]="hasArrow" [matMenuTriggerFor]="filterMenu" [ngClass]="{ overlay: hasActiveFilters }">
<mat-icon [svgIcon]="icon" *ngIf="icon"></mat-icon>
<span [translate]="filterLabel"></span>
<mat-icon svgIcon="red:arrow-down" *ngIf="hasArrow"></mat-icon>
</button>
<div class="dot" *ngIf="hasActiveFilters"></div>
<mat-menu #filterMenu="matMenu" xPosition="before" (closed)="applyFilters()">
<div class="filter-menu-header">
<div class="all-caps-label" translate="filter-menu.filter-types"></div>
<div class="actions">
<div
class="all-caps-label primary pointer"
translate="filter-menu.all"
(click)="activateAllFilters(); $event.stopPropagation()"
></div>
<div
class="all-caps-label primary pointer"
translate="filter-menu.none"
(click)="deactivateAllFilters(); $event.stopPropagation()"
></div>
</div>
</div>
<div *ngFor="let filter of filters">
<div class="mat-menu-item flex" (click)="toggleFilterExpanded($event, filter)">
<div class="arrow-wrapper" *ngIf="filter.filters && filter.filters.length">
<mat-icon *ngIf="filter.expanded" svgIcon="red:arrow-down" color="accent">
</mat-icon>
<mat-icon *ngIf="!filter.expanded" color="accent" svgIcon="red:arrow-right">
</mat-icon>
<div (mouseleave)="filterMouseLeave()" (mouseenter)="filterMouseEnter()">
<div class="filter-menu-header">
<div class="all-caps-label" translate="filter-menu.filter-types"></div>
<div class="actions">
<div class="all-caps-label primary pointer" translate="filter-menu.all" (click)="activateAllFilters(); $event.stopPropagation()"></div>
<div class="all-caps-label primary pointer" translate="filter-menu.none" (click)="deactivateAllFilters(); $event.stopPropagation()"></div>
</div>
<mat-checkbox
[checked]="filter.checked"
[indeterminate]="filter.indeterminate"
(click)="$event.stopPropagation()"
(change)="filterCheckboxClicked($event, filter)"
color="primary"
>
<ng-template
*ngTemplateOutlet="
filterTemplate ? filterTemplate : defaultFilterTemplate;
context: { filter: filter }
"
>
</ng-template>
</mat-checkbox>
</div>
<div *ngIf="filter.filters && filter.expanded">
<div
*ngFor="let subFilter of filter.filters"
class="padding-left mat-menu-item"
(click)="$event.stopPropagation()"
>
<div *ngFor="let filter of filters">
<div class="mat-menu-item flex" (click)="toggleFilterExpanded($event, filter)">
<div class="arrow-wrapper" *ngIf="filter.filters && filter.filters.length">
<mat-icon *ngIf="filter.expanded" svgIcon="red:arrow-down" color="accent"> </mat-icon>
<mat-icon *ngIf="!filter.expanded" color="accent" svgIcon="red:arrow-right"> </mat-icon>
</div>
<mat-checkbox
[checked]="subFilter.checked"
[checked]="filter.checked"
[indeterminate]="filter.indeterminate"
(click)="$event.stopPropagation()"
(change)="filterCheckboxClicked($event, subFilter, filter)"
(change)="filterCheckboxClicked($event, filter)"
class="filter-menu-checkbox"
color="primary"
>
<ng-template
*ngTemplateOutlet="
filterTemplate ? filterTemplate : defaultFilterTemplate;
context: { filter: subFilter }
"
>
</ng-template>
<ng-template *ngTemplateOutlet="filterTemplate ? filterTemplate : defaultFilterTemplate; context: { filter: filter }"> </ng-template>
</mat-checkbox>
</div>
<div *ngIf="filter.filters && filter.expanded">
<div *ngFor="let subFilter of filter.filters" class="padding-left mat-menu-item" (click)="$event.stopPropagation()">
<mat-checkbox
[checked]="subFilter.checked"
(click)="$event.stopPropagation()"
(change)="filterCheckboxClicked($event, subFilter, filter)"
color="primary"
>
<ng-template *ngTemplateOutlet="filterTemplate ? filterTemplate : defaultFilterTemplate; context: { filter: subFilter }">
</ng-template>
</mat-checkbox>
</div>
</div>
</div>
</div>
</mat-menu>

View File

@ -26,10 +26,11 @@
}
}
.mat-checkbox {
::ng-deep .filter-menu-checkbox {
width: 100%;
label {
width: 100%;
height: 100%;
}
}

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -1,6 +1,6 @@
<redaction-annotation-icon *ngIf="filter.key === 'analysis'" type="square" label="A" color="#dd4d50"></redaction-annotation-icon>
<redaction-annotation-icon *ngIf="filter.key === 'redaction'" type="square" label="R" color="#283241"></redaction-annotation-icon>
<redaction-annotation-icon *ngIf="filter.key === 'hint'" type="round" label="H" color="#283241"></redaction-annotation-icon>
<redaction-annotation-icon *ngIf="filter.key === 'hint'" type="round" label="H" color="#9398a0"></redaction-annotation-icon>
<redaction-annotation-icon *ngIf="filter.key === 'suggestion'" type="rhombus" label="S" [color]="suggestionColor"></redaction-annotation-icon>
<redaction-annotation-icon *ngIf="filter.key === 'suggestion-remove'" type="rhombus" label="S" [color]="suggestionColor"></redaction-annotation-icon>
{{ filter.label | translate }}

View File

@ -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);

View File

@ -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;
}

View File

@ -11,7 +11,7 @@
<div class="red-input-group" *ngIf="!isDictionaryRequest">
<label translate="manual-annotation.dialog.content.reason"></label>
<mat-select formControlName="reason" class="full-width">
<mat-option *ngFor="let option of legalOptions" [value]="option.legalBasis">
<mat-option *ngFor="let option of legalOptions" [value]="option">
{{ option.label }}
</mat-option>
</mat-select>
@ -19,7 +19,7 @@
<div class="red-input-group" *ngIf="!isDictionaryRequest">
<label translate="manual-annotation.dialog.content.legalBasis"></label>
<input type="text" [value]="redactionForm.get('reason').value" disabled />
<input type="text" [value]="redactionForm.get('reason').value.legalBasis" disabled />
</div>
<div class="red-input-group">

View File

@ -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;

View File

@ -1,6 +1,6 @@
<div class="needs-work">
<redaction-annotation-icon *ngIf="reanalysisRequired()" type="square" label="A" color="#dd4d50"></redaction-annotation-icon>
<redaction-annotation-icon *ngIf="needsWorkInput.hasRedactions" type="square" label="R" color="#283241"></redaction-annotation-icon>
<redaction-annotation-icon *ngIf="needsWorkInput.hintsOnly" type="round" label="H" color="#283241"></redaction-annotation-icon>
<redaction-annotation-icon *ngIf="needsWorkInput.hintsOnly" type="round" label="H" color="#9398a0"></redaction-annotation-icon>
<redaction-annotation-icon *ngIf="needsWorkInput.hasRequests" type="rhombus" label="S" [color]="suggestionColor"></redaction-annotation-icon>
</div>

View File

@ -12,7 +12,6 @@
</div>
<div class="flex-1 filename page-title">
<!-- <span *ngIf="permissionsService.fileRequiresReanalysis()" class="pill" translate="project-overview.new-rule.label"></span>-->
<span *ngIf="!permissionsService.canPerformAnnotationActions()" class="pill" translate="readonly-pill"></span>&nbsp;<span>{{
appStateService.activeFile.filename
}}</span>
@ -77,6 +76,10 @@
<span class="all-caps-label"><span translate="page"></span> {{ activeViewerPage }}</span>
</div>
<div class="heading-l no-annotations" *ngIf="!displayedAnnotations[activeViewerPage]">
{{ 'file-preview.no-annotations-for-page' | translate }}
</div>
<div
(click)="selectAnnotation(annotation)"
*ngFor="let annotation of displayedAnnotations[activeViewerPage]?.annotations"

View File

@ -115,3 +115,8 @@ redaction-pdf-viewer {
}
}
}
.no-annotations {
padding: 24px;
text-align: center;
}

View File

@ -105,7 +105,7 @@ export class FilePreviewScreenComponent implements OnInit {
});
this.appStateService.fileReanalysed.subscribe((fileStatus: FileStatusWrapper) => {
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
);
}
});

View File

@ -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) {

View File

@ -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<FileDataModel> {
const annotatedObs = this.loadFile('ORIGINAL', this._appStateService.activeFile);
loadActiveFileData(): Observable<FileDataModel> {
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));
}

View File

@ -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();

View File

@ -116,7 +116,6 @@ export class BulkActionsComponent {
Promise.all(promises).then(() => {
this.reload.emit();
console.log('done');
});
}

View File

@ -67,7 +67,7 @@
</div>
<div>
<mat-icon svgIcon="red:user"></mat-icon>
<span>{{ 'project-overview.project-details.stats.people' | translate: { count: appStateService.activeProject.project.memberIds.length } }}</span>
<span>{{ 'project-overview.project-details.stats.people' | translate: { count: appStateService.activeProject.memberCount } }}</span>
</div>
<div>
<mat-icon svgIcon="red:pages"></mat-icon>

View File

@ -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);
});
}

View File

@ -68,7 +68,7 @@
{{ 'project-overview.table-header.title' | translate: { length: displayedFiles.length || 0 } }}
</span>
<redaction-bulk-actions [selectedFileIds]="selectedFileIds" (reload)="reloadProjects()"></redaction-bulk-actions>
<redaction-bulk-actions [selectedFileIds]="selectedFileIds" (reload)="bulkActionPerformed()"></redaction-bulk-actions>
</div>
<!-- Table column names-->

View File

@ -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();
}
}

View File

@ -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',

View File

@ -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;
}

View File

@ -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;
}
);
});
}

View File

@ -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": {