Improve processing indicator

This commit is contained in:
Dan Percic 2021-06-16 16:32:04 +03:00
parent 8090d4329f
commit 1cbb928778
12 changed files with 123 additions and 171 deletions

View File

@ -8,8 +8,3 @@
@include scroll-bar; @include scroll-bar;
overflow: auto; overflow: auto;
} }
.w-100 {
min-width: 100px;
width: 100px;
}

View File

@ -17,10 +17,16 @@
</div> </div>
</div> </div>
<div class="right-content"> <div class="right-content">
<span class="read-only flex-center" *ngIf="isReadOnly"> <div class="read-only d-flex" *ngIf="isReadOnly" [class.justify-center]="!isProcessing">
<mat-icon svgIcon="red:read-only" class="red-white"></mat-icon> <div class="flex-align-items-center" *ngIf="isProcessing">
<span class="read-only-text" translate="readonly-pill"></span> <span class="read-only-text" [translate]="fileData.fileStatus.status"></span>
</span> <mat-progress-bar class="w-100" [mode]="'indeterminate'"></mat-progress-bar>
</div>
<div class="flex-center">
<mat-icon svgIcon="red:read-only" class="red-white"></mat-icon>
<span class="read-only-text" translate="readonly"></span>
</div>
</div>
<div *ngIf="multiSelectActive" class="multi-select"> <div *ngIf="multiSelectActive" class="multi-select">
<div class="selected-wrapper"> <div class="selected-wrapper">
<redaction-round-checkbox <redaction-round-checkbox
@ -84,17 +90,13 @@
<div style="overflow: hidden; width: 100%"> <div style="overflow: hidden; width: 100%">
<div attr.anotation-page-header="{{ activeViewerPage }}" class="page-separator"> <div attr.anotation-page-header="{{ activeViewerPage }}" class="page-separator">
<span *ngIf="!!activeViewerPage" class="all-caps-label" <span *ngIf="!!activeViewerPage" class="all-caps-label">
><span translate="page"></span> {{ activeViewerPage }} - <span translate="page"></span> {{ activeViewerPage }} -
{{ displayedAnnotations[activeViewerPage]?.annotations?.length || 0 }} {{ activeAnnotationsLength || 0 }}
<span <span
[translate]=" [translate]="activeAnnotationsLength === 1 ? 'annotation' : 'annotations'"
displayedAnnotations[activeViewerPage]?.annotations?.length === 1 ></span>
? 'annotation' </span>
: 'annotations'
"
></span
></span>
<div *ngIf="multiSelectActive"> <div *ngIf="multiSelectActive">
<div (click)="selectAllOnActivePage()" class="all-caps-label primary pointer"> <div (click)="selectAllOnActivePage()" class="all-caps-label primary pointer">
@ -170,14 +172,9 @@
<div> <div>
<strong>{{ annotation.typeLabel | translate }}</strong> <strong>{{ annotation.typeLabel | translate }}</strong>
</div> </div>
<div <div *ngIf="annotation?.dictionary !== 'manual'">
*ngIf=" <strong>
annotation.dictionary && <span>{{ annotation.descriptor | translate }}</span
annotation.dictionary !== 'manual'
"
>
<strong
><span>{{ annotation.descriptor | translate }}</span
>: </strong >: </strong
>{{ annotation.dictionary | humanize: false }} >{{ annotation.dictionary | humanize: false }}
</div> </div>
@ -200,25 +197,15 @@
<div class="actions-wrapper"> <div class="actions-wrapper">
<div <div
(click)="toggleExpandComments(annotation, $event)" (click)="toggleExpandComments(annotation, $event)"
[matTooltip]=" [matTooltip]="commentsTooltip(annotation)"
(annotation.comments.length === 1
? 'comments.comment'
: 'comments.comments'
)
| translate
: {
count: annotation.comments.length
}
"
class="comments-counter" class="comments-counter"
matTooltipPosition="above" matTooltipPosition="above"
> >
<mat-icon svgIcon="red:comment"></mat-icon> <mat-icon svgIcon="red:comment"></mat-icon>
{{ annotation.comments.length }} {{ annotation.comments.length }}
</div> </div>
<div class="actions"> <div class="actions" *ngIf="!multiSelectActive">
<ng-container <ng-container
*ngIf="!multiSelectActive"
[ngTemplateOutletContext]="{ annotation: annotation }" [ngTemplateOutletContext]="{ annotation: annotation }"
[ngTemplateOutlet]="annotationActionsTemplate" [ngTemplateOutlet]="annotationActionsTemplate"
></ng-container> ></ng-container>

View File

@ -2,12 +2,12 @@
@import '../../../../../assets/styles/red-mixins'; @import '../../../../../assets/styles/red-mixins';
.read-only { .read-only {
padding: 13px 0; padding: 13px 16px;
background-color: $primary; background-color: $primary;
color: $white; color: $white;
justify-content: space-between;
.read-only-text { .read-only-text {
padding-left: 8px;
font-size: 11px; font-size: 11px;
font-weight: 600; font-weight: 600;
line-height: 14px; line-height: 14px;
@ -18,6 +18,18 @@
mat-icon { mat-icon {
height: 14px; height: 14px;
width: 14px; width: 14px;
&.red-white {
padding-right: 8px;
}
}
mat-progress-bar {
margin-left: 8px;
}
&.justify-center {
justify-content: center !important;
} }
} }

View File

@ -20,6 +20,7 @@ import { FileDataModel } from '@models/file/file-data.model';
import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model'; import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model';
import { CommentsComponent } from '../comments/comments.component'; import { CommentsComponent } from '../comments/comments.component';
import { PermissionsService } from '../../../../services/permissions.service'; import { PermissionsService } from '../../../../services/permissions.service';
import { TranslateService } from '@ngx-translate/core';
const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape']; const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape'];
const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown']; const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
@ -57,7 +58,8 @@ export class FileWorkloadComponent {
constructor( constructor(
private readonly _changeDetectorRef: ChangeDetectorRef, private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _annotationProcessingService: AnnotationProcessingService, private readonly _annotationProcessingService: AnnotationProcessingService,
private readonly _permissionsService: PermissionsService private readonly _permissionsService: PermissionsService,
private readonly _translateService: TranslateService
) {} ) {}
private _annotations: AnnotationWrapper[]; private _annotations: AnnotationWrapper[];
@ -69,6 +71,14 @@ export class FileWorkloadComponent {
private _multiSelectActive = false; private _multiSelectActive = false;
get isProcessing(): boolean {
return this.fileData.fileStatus.isProcessing;
}
get activeAnnotationsLength(): number | undefined {
return this.displayedAnnotations[this.activeViewerPage]?.annotations?.length;
}
get isReadOnly(): boolean { get isReadOnly(): boolean {
return !this._permissionsService.canPerformAnnotationActions(); return !this._permissionsService.canPerformAnnotationActions();
} }
@ -105,6 +115,11 @@ export class FileWorkloadComponent {
} }
} }
commentsTooltip({ comments }: AnnotationWrapper): string {
const i18nString = 'comments.' + (comments.length === 1 ? 'comment' : 'comments');
return this._translateService.instant(i18nString, { count: comments.length });
}
annotationIsSelected(annotation: AnnotationWrapper) { annotationIsSelected(annotation: AnnotationWrapper) {
return this.selectedAnnotations?.find(a => a?.id === annotation.id); return this.selectedAnnotations?.find(a => a?.id === annotation.id);
} }

View File

@ -209,7 +209,7 @@
<div> <div>
<div class="filename-wrapper"> <div class="filename-wrapper">
<div <div
[class.disabled]="fileStatus.isPending || fileStatus.isProcessing" [class.disabled]="fileStatus.isPending"
[class.error]="fileStatus.isError" [class.error]="fileStatus.isError"
class="table-item-title text-overflow" class="table-item-title text-overflow"
> >

View File

@ -31,13 +31,15 @@
<div *ngIf="viewReady" class="flex-1 actions-container"> <div *ngIf="viewReady" class="flex-1 actions-container">
<ng-container *ngIf="!appStateService.activeFile.isExcluded"> <ng-container *ngIf="!appStateService.activeFile.isExcluded">
<redaction-status-bar [config]="statusBarConfig" [small]="true"> <ng-container *ngIf="!appStateService.activeFile.isProcessing">
</redaction-status-bar> <redaction-status-bar [config]="statusBarConfig" [small]="true">
</redaction-status-bar>
<div class="all-caps-label mr-16 ml-8"> <div class="all-caps-label mr-16 ml-8">
{{ status | translate }} {{ status | translate }}
<span *ngIf="isUnderReviewOrApproval">{{ 'by' | translate }}:</span> <span *ngIf="isUnderReviewOrApproval">{{ 'by' | translate }}:</span>
</div> </div>
</ng-container>
<redaction-initials-avatar <redaction-initials-avatar
*ngIf="!editingReviewer" *ngIf="!editingReviewer"
@ -61,13 +63,7 @@
(cancel)="editingReviewer = false" (cancel)="editingReviewer = false"
></redaction-assign-user-dropdown> ></redaction-assign-user-dropdown>
<div <div *ngIf="canAssign" class="assign-actions-wrapper">
*ngIf="
!editingReviewer &&
(permissionsService.canAssignUser() || permissionsService.canAssignToSelf())
"
class="assign-actions-wrapper"
>
<redaction-circle-button <redaction-circle-button
(action)="editingReviewer = true" (action)="editingReviewer = true"
*ngIf="permissionsService.canAssignUser() && currentReviewer" *ngIf="permissionsService.canAssignUser() && currentReviewer"
@ -85,16 +81,11 @@
></redaction-circle-button> ></redaction-circle-button>
</div> </div>
<ng-container <ng-container *ngIf="permissionsService.isApprover() && !!lastReviewer">
*ngIf="
permissionsService.isApprover() &&
!!appStateService.activeFile.fileStatus.lastReviewer
"
>
<div class="vertical-line"></div> <div class="vertical-line"></div>
<div class="all-caps-label mr-16 ml-8" translate="file-preview.last-reviewer"></div> <div class="all-caps-label mr-16 ml-8" translate="file-preview.last-reviewer"></div>
<redaction-initials-avatar <redaction-initials-avatar
[userId]="appStateService.activeFile.fileStatus.lastReviewer" [userId]="lastReviewer"
[withName]="true" [withName]="true"
></redaction-initials-avatar> ></redaction-initials-avatar>
</ng-container> </ng-container>
@ -196,17 +187,7 @@
</div> </div>
</section> </section>
<redaction-full-page-loading-indicator <redaction-full-page-loading-indicator [displayed]="!viewReady" [showIndicator]="!viewReady">
[displayed]="!viewReady"
[showIndicator]="!this.loadingMessage"
>
<h1 *ngIf="loadingMessage" class="heading-l loading">{{ loadingMessage | translate }}</h1>
<div *ngIf="loadingMessage" class="analysis-progress">
<mat-progress-bar
[mode]="indeterminateMode ? 'indeterminate' : 'determinate'"
[value]="analysisProgress"
></mat-progress-bar>
</div>
</redaction-full-page-loading-indicator> </redaction-full-page-loading-indicator>
<ng-template #annotationActionsTemplate let-annotation="annotation"> <ng-template #annotationActionsTemplate let-annotation="annotation">

View File

@ -59,16 +59,12 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
fullScreen = false; fullScreen = false;
editingReviewer = false; editingReviewer = false;
shouldDeselectAnnotationsOnPageChange = true; shouldDeselectAnnotationsOnPageChange = true;
analysisProgressInSeconds = 0;
analysisProgress: number;
analysisInterval: number;
fileData: FileDataModel; fileData: FileDataModel;
annotationData: AnnotationData; annotationData: AnnotationData;
selectedAnnotations: AnnotationWrapper[]; selectedAnnotations: AnnotationWrapper[];
viewReady = false; viewReady = false;
primaryFilters: FilterModel[]; primaryFilters: FilterModel[];
secondaryFilters: FilterModel[]; secondaryFilters: FilterModel[];
loadingMessage: string;
canPerformAnnotationActions: boolean; canPerformAnnotationActions: boolean;
filesAutoUpdateTimer: Subscription; filesAutoUpdateTimer: Subscription;
fileReanalysedSubscription: Subscription; fileReanalysedSubscription: Subscription;
@ -141,24 +137,22 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
get canSwitchToDeltaView() { get canSwitchToDeltaView() {
return ( return (
this.fileData && this.fileData?.redactionChangeLog?.redactionLogEntry?.length > 0 &&
this.fileData.redactionChangeLog?.redactionLogEntry?.length > 0 &&
!this.fileData.fileStatus.isExcluded !this.fileData.fileStatus.isExcluded
); );
} }
get canAssign(): boolean {
return (
!this.editingReviewer &&
(this.permissionsService.canAssignUser() || this.permissionsService.canAssignToSelf())
);
}
get displayData() { get displayData() {
return this.fileData?.fileData; return this.fileData?.fileData;
} }
get indeterminateMode() {
return (
this.analysisProgress > 100 ||
this.appStateService.activeFile.analysisDuration < 3 * 1000
// it takes longer than usual - switch to indeterminate
); // on less than 3 seconds show indeterminate
}
get dossierId() { get dossierId() {
return this.appStateService.activeDossierId; return this.appStateService.activeDossierId;
} }
@ -171,6 +165,10 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
return !!this._workloadComponent?.multiSelectActive; return !!this._workloadComponent?.multiSelectActive;
} }
get lastReviewer(): string | undefined {
return this.appStateService.activeFile.fileStatus.lastReviewer;
}
updateViewMode() { updateViewMode() {
const annotations = this._getAnnotations(a => a.getCustomData('redacto-manager')); const annotations = this._getAnnotations(a => a.getCustomData('redacto-manager'));
const redactions = annotations.filter(a => a.getCustomData('redaction')); const redactions = annotations.filter(a => a.getCustomData('redaction'));
@ -239,6 +237,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
} catch (error) {} } catch (error) {}
this._subscribeToFileUpdates(); this._subscribeToFileUpdates();
this.viewReady = true;
} }
ngOnDestroy(): void { ngOnDestroy(): void {
@ -254,9 +253,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
imported: true, imported: true,
force: true force: true
}); });
} catch (error) { } catch (error) {}
// nothing
}
} }
console.log( console.log(
'[REDACTION] Delete previous annotations time: ' + '[REDACTION] Delete previous annotations time: ' +
@ -438,9 +435,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
case 'enable-analysis': case 'enable-analysis':
case 'reanalyse': case 'reanalyse':
this.viewReady = false;
this._startAnalysisTimer();
this.loadingMessage = 'file-preview.reanalyse-file';
await this._loadFileData(); await this._loadFileData();
this._updateCanPerformActions(); this._updateCanPerformActions();
await this.appStateService.reloadActiveDossierFiles(); await this.appStateService.reloadActiveDossierFiles();
@ -555,19 +549,13 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
private _subscribeToFileUpdates(): void { private _subscribeToFileUpdates(): void {
this.filesAutoUpdateTimer = timer(0, 5000) this.filesAutoUpdateTimer = timer(0, 5000)
.pipe( .pipe(tap(async () => await this.appStateService.reloadActiveFile()))
tap(async () => {
await this.appStateService.reloadActiveFile();
})
)
.subscribe(); .subscribe();
this.fileReanalysedSubscription = this.appStateService.fileReanalysed.subscribe( this.fileReanalysedSubscription = this.appStateService.fileReanalysed.subscribe(
async (fileStatus: FileStatusWrapper) => { async (fileStatus: FileStatusWrapper) => {
if (fileStatus.fileId === this.fileId) { if (fileStatus.fileId === this.fileId) {
await this._loadFileData(true); await this._loadFileData(true);
this.viewReady = true; this.viewReady = true;
this.loadingMessage = null;
this._stopAnalysisTimer();
this._updateCanPerformActions(); this._updateCanPerformActions();
this._cleanupAndRedrawManualAnnotations(); this._cleanupAndRedrawManualAnnotations();
} }
@ -585,32 +573,26 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
this.permissionsService.canPerformAnnotationActions() && this.viewMode === 'STANDARD'; this.permissionsService.canPerformAnnotationActions() && this.viewMode === 'STANDARD';
} }
private _loadFileData(performUpdate: boolean = false) { private async _loadFileData(performUpdate: boolean = false): Promise<void> {
return this._fileDownloadService const fileData = await this._fileDownloadService.loadActiveFileData().toPromise();
.loadActiveFileData() if (!fileData.fileStatus.isPending && !fileData.fileStatus.isError) {
.pipe( if (performUpdate) {
tap(fileDataModel => { this.fileData.redactionLog = fileData.redactionLog;
if (fileDataModel.fileStatus.isWorkable) { this.fileData.redactionChangeLog = fileData.redactionChangeLog;
if (performUpdate) { this.fileData.fileStatus = fileData.fileStatus;
this.fileData.redactionLog = fileDataModel.redactionLog; this.fileData.manualRedactions = fileData.manualRedactions;
this.fileData.redactionChangeLog = fileDataModel.redactionChangeLog; this.rebuildFilters(true);
this.fileData.fileStatus = fileDataModel.fileStatus; } else {
this.fileData.manualRedactions = fileDataModel.manualRedactions; this.fileData = fileData;
this.rebuildFilters(true); this.rebuildFilters();
} else { }
this.fileData = fileDataModel;
this.rebuildFilters(); return;
} }
} else {
if (fileDataModel.fileStatus.isError) { if (fileData.fileStatus.isError) {
this._router.navigate(['/main/dossiers/' + this.dossierId]); await this._router.navigate(['/main/dossiers/' + this.dossierId]);
} else { }
this.loadingMessage = 'file-preview.reanalyse-file';
}
}
})
)
.toPromise();
} }
@debounce() @debounce()
@ -665,17 +647,20 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
const hasAnyFilterSet = const hasAnyFilterSet =
this.primaryFilters.find(f => f.checked || f.indeterminate) || this.primaryFilters.find(f => f.checked || f.indeterminate) ||
this.secondaryFilters.find(f => f.checked || f.indeterminate); this.secondaryFilters.find(f => f.checked || f.indeterminate);
if (hasAnyFilterSet) {
const oldPageSpecificFilters = if (!hasAnyFilterSet) {
this._annotationProcessingService.getAnnotationFilter(currentPageAnnotations); return;
const newPageSpecificFilters =
this._annotationProcessingService.getAnnotationFilter(newPageAnnotations);
handleFilterDelta(oldPageSpecificFilters, newPageSpecificFilters, this.primaryFilters);
this._workloadComponent.filtersChanged({
primary: this.primaryFilters,
secondary: this.secondaryFilters
});
} }
const oldPageSpecificFilters =
this._annotationProcessingService.getAnnotationFilter(currentPageAnnotations);
const newPageSpecificFilters =
this._annotationProcessingService.getAnnotationFilter(newPageAnnotations);
handleFilterDelta(oldPageSpecificFilters, newPageSpecificFilters, this.primaryFilters);
this._workloadComponent.filtersChanged({
primary: this.primaryFilters,
secondary: this.secondaryFilters
});
} }
private _findAndDeleteAnnotation(id: string) { private _findAndDeleteAnnotation(id: string) {
@ -688,33 +673,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
} }
} }
private _startAnalysisTimer() {
this._stopAnalysisTimer();
if (this.appStateService.activeFile.analysisDuration > 0) {
this.analysisProgress = 0;
this.analysisProgressInSeconds = 0;
this.analysisInterval = setInterval(() => {
this.analysisProgressInSeconds += 1;
this.analysisProgress =
(this.analysisProgressInSeconds * 100) /
(this.appStateService.activeFile.analysisDuration / 1000);
}, 1000);
} else {
this.analysisInterval = 0;
this.analysisProgress = 0;
this.analysisProgressInSeconds = 0;
}
}
private _stopAnalysisTimer() {
if (this.analysisInterval) {
clearInterval(this.analysisInterval);
this.analysisInterval = 0;
}
}
/* View in fullscreen */ /* View in fullscreen */
private _openFullScreen() { private _openFullScreen() {
const documentElement = document.documentElement; const documentElement = document.documentElement;

View File

@ -1,11 +1,12 @@
import { Component, Input, ViewEncapsulation } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input, ViewEncapsulation } from '@angular/core';
import { Color } from '@utils/types'; import { Color } from '@utils/types';
@Component({ @Component({
selector: 'redaction-status-bar', selector: 'redaction-status-bar',
templateUrl: './status-bar.component.html', templateUrl: './status-bar.component.html',
styleUrls: ['./status-bar.component.scss'], styleUrls: ['./status-bar.component.scss'],
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class StatusBarComponent { export class StatusBarComponent {
@Input() @Input()
@ -18,6 +19,4 @@ export class StatusBarComponent {
@Input() @Input()
small = false; small = false;
constructor() {}
} }

View File

@ -160,7 +160,7 @@ export class PermissionsService {
} }
canOpenFile(fileStatus = this._activeFile): boolean { canOpenFile(fileStatus = this._activeFile): boolean {
return !fileStatus?.isError && !fileStatus?.isProcessing && !fileStatus?.isPending; return !fileStatus?.isError && !fileStatus?.isPending;
} }
canUndoApproval(fileStatus = this._activeFile): boolean { canUndoApproval(fileStatus = this._activeFile): boolean {

View File

@ -519,7 +519,7 @@
"number-of-analyses": "Anzahl der Analysen", "number-of-analyses": "Anzahl der Analysen",
"custom": "Benutzerdefiniert" "custom": "Benutzerdefiniert"
}, },
"readonly-pill": "Schreibgeschützt", "readonly": "Schreibgeschützt",
"group": { "group": {
"redactions": "Redaktionswörterbücher", "redactions": "Redaktionswörterbücher",
"hints": "Tipp Wörterbücher" "hints": "Tipp Wörterbücher"

View File

@ -578,7 +578,7 @@
"number-of-analyses": "Number of analyses", "number-of-analyses": "Number of analyses",
"custom": "Custom" "custom": "Custom"
}, },
"readonly-pill": "Read-only", "readonly": "Read only",
"group": { "group": {
"redactions": "Redaction Dictionaries", "redactions": "Redaction Dictionaries",
"hints": "Hint Dictionaries" "hints": "Hint Dictionaries"

View File

@ -265,6 +265,11 @@ section.settings {
padding-bottom: 32px; padding-bottom: 32px;
} }
.w-100 {
min-width: 100px !important;
width: 100px !important;
}
.break-20 { .break-20 {
height: 20px; height: 20px;
background: transparent; background: transparent;