fixed stuff
This commit is contained in:
parent
45a8ba316c
commit
b8f072e20a
@ -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>
|
||||
|
||||
@ -26,10 +26,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.mat-checkbox {
|
||||
::ng-deep .filter-menu-checkbox {
|
||||
width: 100%;
|
||||
|
||||
label {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 }}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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> <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"
|
||||
|
||||
@ -115,3 +115,8 @@ redaction-pdf-viewer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-annotations {
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@ -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
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -116,7 +116,6 @@ export class BulkActionsComponent {
|
||||
|
||||
Promise.all(promises).then(() => {
|
||||
this.reload.emit();
|
||||
console.log('done');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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-->
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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": {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user