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;
overflow: auto;
}
.w-100 {
min-width: 100px;
width: 100px;
}

View File

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

View File

@ -2,12 +2,12 @@
@import '../../../../../assets/styles/red-mixins';
.read-only {
padding: 13px 0;
padding: 13px 16px;
background-color: $primary;
color: $white;
justify-content: space-between;
.read-only-text {
padding-left: 8px;
font-size: 11px;
font-weight: 600;
line-height: 14px;
@ -18,6 +18,18 @@
mat-icon {
height: 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 { CommentsComponent } from '../comments/comments.component';
import { PermissionsService } from '../../../../services/permissions.service';
import { TranslateService } from '@ngx-translate/core';
const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape'];
const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
@ -57,7 +58,8 @@ export class FileWorkloadComponent {
constructor(
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _annotationProcessingService: AnnotationProcessingService,
private readonly _permissionsService: PermissionsService
private readonly _permissionsService: PermissionsService,
private readonly _translateService: TranslateService
) {}
private _annotations: AnnotationWrapper[];
@ -69,6 +71,14 @@ export class FileWorkloadComponent {
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 {
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) {
return this.selectedAnnotations?.find(a => a?.id === annotation.id);
}

View File

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

View File

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

View File

@ -59,16 +59,12 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
fullScreen = false;
editingReviewer = false;
shouldDeselectAnnotationsOnPageChange = true;
analysisProgressInSeconds = 0;
analysisProgress: number;
analysisInterval: number;
fileData: FileDataModel;
annotationData: AnnotationData;
selectedAnnotations: AnnotationWrapper[];
viewReady = false;
primaryFilters: FilterModel[];
secondaryFilters: FilterModel[];
loadingMessage: string;
canPerformAnnotationActions: boolean;
filesAutoUpdateTimer: Subscription;
fileReanalysedSubscription: Subscription;
@ -141,24 +137,22 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
get canSwitchToDeltaView() {
return (
this.fileData &&
this.fileData.redactionChangeLog?.redactionLogEntry?.length > 0 &&
this.fileData?.redactionChangeLog?.redactionLogEntry?.length > 0 &&
!this.fileData.fileStatus.isExcluded
);
}
get canAssign(): boolean {
return (
!this.editingReviewer &&
(this.permissionsService.canAssignUser() || this.permissionsService.canAssignToSelf())
);
}
get displayData() {
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() {
return this.appStateService.activeDossierId;
}
@ -171,6 +165,10 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
return !!this._workloadComponent?.multiSelectActive;
}
get lastReviewer(): string | undefined {
return this.appStateService.activeFile.fileStatus.lastReviewer;
}
updateViewMode() {
const annotations = this._getAnnotations(a => a.getCustomData('redacto-manager'));
const redactions = annotations.filter(a => a.getCustomData('redaction'));
@ -239,6 +237,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
} catch (error) {}
this._subscribeToFileUpdates();
this.viewReady = true;
}
ngOnDestroy(): void {
@ -254,9 +253,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
imported: true,
force: true
});
} catch (error) {
// nothing
}
} catch (error) {}
}
console.log(
'[REDACTION] Delete previous annotations time: ' +
@ -438,9 +435,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
case 'enable-analysis':
case 'reanalyse':
this.viewReady = false;
this._startAnalysisTimer();
this.loadingMessage = 'file-preview.reanalyse-file';
await this._loadFileData();
this._updateCanPerformActions();
await this.appStateService.reloadActiveDossierFiles();
@ -555,19 +549,13 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
private _subscribeToFileUpdates(): void {
this.filesAutoUpdateTimer = timer(0, 5000)
.pipe(
tap(async () => {
await this.appStateService.reloadActiveFile();
})
)
.pipe(tap(async () => await this.appStateService.reloadActiveFile()))
.subscribe();
this.fileReanalysedSubscription = this.appStateService.fileReanalysed.subscribe(
async (fileStatus: FileStatusWrapper) => {
if (fileStatus.fileId === this.fileId) {
await this._loadFileData(true);
this.viewReady = true;
this.loadingMessage = null;
this._stopAnalysisTimer();
this._updateCanPerformActions();
this._cleanupAndRedrawManualAnnotations();
}
@ -585,32 +573,26 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
this.permissionsService.canPerformAnnotationActions() && this.viewMode === 'STANDARD';
}
private _loadFileData(performUpdate: boolean = false) {
return this._fileDownloadService
.loadActiveFileData()
.pipe(
tap(fileDataModel => {
if (fileDataModel.fileStatus.isWorkable) {
if (performUpdate) {
this.fileData.redactionLog = fileDataModel.redactionLog;
this.fileData.redactionChangeLog = fileDataModel.redactionChangeLog;
this.fileData.fileStatus = fileDataModel.fileStatus;
this.fileData.manualRedactions = fileDataModel.manualRedactions;
this.rebuildFilters(true);
} else {
this.fileData = fileDataModel;
this.rebuildFilters();
}
} else {
if (fileDataModel.fileStatus.isError) {
this._router.navigate(['/main/dossiers/' + this.dossierId]);
} else {
this.loadingMessage = 'file-preview.reanalyse-file';
}
}
})
)
.toPromise();
private async _loadFileData(performUpdate: boolean = false): Promise<void> {
const fileData = await this._fileDownloadService.loadActiveFileData().toPromise();
if (!fileData.fileStatus.isPending && !fileData.fileStatus.isError) {
if (performUpdate) {
this.fileData.redactionLog = fileData.redactionLog;
this.fileData.redactionChangeLog = fileData.redactionChangeLog;
this.fileData.fileStatus = fileData.fileStatus;
this.fileData.manualRedactions = fileData.manualRedactions;
this.rebuildFilters(true);
} else {
this.fileData = fileData;
this.rebuildFilters();
}
return;
}
if (fileData.fileStatus.isError) {
await this._router.navigate(['/main/dossiers/' + this.dossierId]);
}
}
@debounce()
@ -665,17 +647,20 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
const hasAnyFilterSet =
this.primaryFilters.find(f => f.checked || f.indeterminate) ||
this.secondaryFilters.find(f => f.checked || f.indeterminate);
if (hasAnyFilterSet) {
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
});
if (!hasAnyFilterSet) {
return;
}
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) {
@ -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 */
private _openFullScreen() {
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';
@Component({
selector: 'redaction-status-bar',
templateUrl: './status-bar.component.html',
styleUrls: ['./status-bar.component.scss'],
encapsulation: ViewEncapsulation.None
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class StatusBarComponent {
@Input()
@ -18,6 +19,4 @@ export class StatusBarComponent {
@Input()
small = false;
constructor() {}
}

View File

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

View File

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

View File

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

View File

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