File Details

This commit is contained in:
Timo 2021-04-27 16:16:33 +03:00
parent 1f0d33136e
commit 7575b01b7e
16 changed files with 96 additions and 230 deletions

View File

@ -1,9 +1,7 @@
import { AuthErrorComponent } from './components/auth-error/auth-error.component';
import { AuthGuard } from './modules/auth/auth.guard';
import { PdfViewerScreenComponent } from './components/pdf-viewer-screen/pdf-viewer-screen.component';
import { CompositeRouteGuard } from './guards/composite-route.guard';
import { RedRoleGuard } from './modules/auth/red-role.guard';
import { HtmlDebugScreenComponent } from './components/html-debug-screen/html-debug-screen.component';
import { BaseScreenComponent } from './components/base-screen/base-screen.component';
import { RouteReuseStrategy, RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
@ -23,22 +21,6 @@ const routes = [
component: AuthErrorComponent,
canActivate: [AuthGuard]
},
{
path: 'pdf-preview/:projectId/:fileId',
component: PdfViewerScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard]
}
},
{
path: 'html-debug/:projectId/:fileId',
component: HtmlDebugScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard]
}
},
{
path: 'main/my-profile',
component: BaseScreenComponent,

View File

@ -22,8 +22,6 @@ import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/materia
import { ToastComponent } from './components/toast/toast.component';
import { HttpCacheInterceptor } from '@redaction/red-cache';
import { NotificationsComponent } from './components/notifications/notifications.component';
import { PdfViewerScreenComponent } from './components/pdf-viewer-screen/pdf-viewer-screen.component';
import { HtmlDebugScreenComponent } from './components/html-debug-screen/html-debug-screen.component';
import { KeycloakService } from 'keycloak-angular';
import { DownloadsListScreenComponent } from './components/downloads-list-screen/downloads-list-screen.component';
import { AppRoutingModule } from './app-routing.module';
@ -49,7 +47,7 @@ function cleanupBaseUrl(baseUrl: string) {
}
}
const screens = [BaseScreenComponent, PdfViewerScreenComponent, HtmlDebugScreenComponent, DownloadsListScreenComponent, UserProfileScreenComponent];
const screens = [BaseScreenComponent, DownloadsListScreenComponent, UserProfileScreenComponent];
const components = [AppComponent, LogoComponent, AuthErrorComponent, ToastComponent, NotificationsComponent, ...screens];

View File

@ -1,2 +0,0 @@
<section [innerHTML]="htmlData"></section>
<redaction-full-page-loading-indicator [displayed]="loading"></redaction-full-page-loading-indicator>

View File

@ -1,7 +0,0 @@
section {
height: calc(100vh - 40px);
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
}

View File

@ -1,59 +0,0 @@
import { ChangeDetectorRef, Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { DebugControllerService, FileManagementControllerService } from '@redaction/red-ui-http';
import { FileStatusWrapper } from '../../models/file/file-status.wrapper';
import { mergeMap } from 'rxjs/operators';
@Component({
selector: 'redaction-html-debug-screen',
templateUrl: './html-debug-screen.component.html',
styleUrls: ['./html-debug-screen.component.scss']
})
export class HtmlDebugScreenComponent {
private _fileId: string;
private _projectId: string;
htmlData: any;
loading: boolean;
constructor(
private readonly _activatedRoute: ActivatedRoute,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _debugControllerService: DebugControllerService,
private readonly _fileManagementControllerService: FileManagementControllerService
) {
this._activatedRoute.params.subscribe((params) => {
this._fileId = params.fileId;
this._projectId = params.projectId;
this._loadDebugHTML();
});
}
private _loadDebugHTML() {
this.loading = true;
const fileStatus = new FileStatusWrapper(
{
projectId: this._projectId,
fileId: this._fileId,
lastProcessed: new Date().toISOString()
},
null
);
this._fileManagementControllerService
.downloadAnnotatedFile(fileStatus.projectId, fileStatus.fileId, true, fileStatus.lastUploaded, 'body')
.pipe(
mergeMap((fileData) => {
return this._debugControllerService.debugHtmlTablesForm(fileData, true);
})
)
.subscribe((data) => {
const reader = new FileReader();
reader.onload = () => {
this.htmlData = reader.result;
this.loading = false;
};
reader.readAsText(data);
});
}
}

View File

@ -1 +0,0 @@
<div #viewer class="viewer"></div>

View File

@ -1,4 +0,0 @@
.viewer {
width: 100vw;
height: 100vh;
}

View File

@ -1,87 +0,0 @@
import { ChangeDetectorRef, Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core';
import WebViewer, { WebViewerInstance } from '@pdftron/webviewer';
import { environment } from '../../../environments/environment';
import { ActivatedRoute } from '@angular/router';
import { FileStatusWrapper } from '../../models/file/file-status.wrapper';
import { FileManagementControllerService } from '@redaction/red-ui-http';
import { BASE_HREF } from '../../tokens';
@Component({
selector: 'redaction-pdf-viewer-screen',
templateUrl: './pdf-viewer-screen.component.html',
styleUrls: ['./pdf-viewer-screen.component.scss']
})
export class PdfViewerScreenComponent implements OnInit {
private _instance: WebViewerInstance;
@ViewChild('viewer', { static: true })
private _viewer: ElementRef;
private _fileId: string;
private _projectId: string;
private _fileData: any;
constructor(
@Inject(BASE_HREF) private readonly _baseHref: string,
private readonly _activatedRoute: ActivatedRoute,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _fileManagementControllerService: FileManagementControllerService
) {
this._activatedRoute.params.subscribe((params) => {
this._fileId = params.fileId;
this._projectId = params.projectId;
this._loadFile();
});
}
ngOnInit(): void {
this._loadViewer();
}
private _loadViewer() {
WebViewer(
{
licenseKey: environment.licenseKey ? atob(environment.licenseKey) : null,
isReadOnly: true,
path: this._baseHref + '/assets/wv-resources',
css: this._baseHref + '/assets/pdftron/stylesheet.css'
},
this._viewer.nativeElement
).then((instance) => {
this._instance = instance;
instance.docViewer.on('documentLoaded', () => {
this._changeDetectorRef.detectChanges();
});
if (this._fileData) {
this._loadDocumentIntoViewer();
}
});
}
private _loadFile() {
const fileStatus = new FileStatusWrapper(
{
projectId: this._projectId,
fileId: this._fileId,
lastProcessed: new Date().toISOString()
},
null
);
this._fileManagementControllerService
.downloadAnnotatedFile(fileStatus.projectId, fileStatus.fileId, true, fileStatus.lastUploaded, 'body')
.subscribe((data) => {
this._fileData = data;
this._loadDocumentIntoViewer();
});
}
private _loadDocumentIntoViewer() {
if (this._instance) {
this._instance.loadDocument(this._fileData, {
filename: 'document.pdf'
});
}
}
}

View File

@ -1,8 +1,27 @@
import { FileStatus } from '@redaction/red-ui-http';
import { FileAttributeConfig, FileStatus } from '@redaction/red-ui-http';
import { StatusSorter } from '../../utils/sorters/status-sorter';
export class FileStatusWrapper {
constructor(public fileStatus: FileStatus, public reviewerName: string) {}
primaryAttribute: string;
searchField: string;
constructor(public fileStatus: FileStatus, public reviewerName: string, fileAttributesConfig?: FileAttributeConfig[]) {
this.searchField = fileStatus.filename;
if (fileAttributesConfig) {
const primary = fileAttributesConfig.find((c) => c.primaryAttribute);
if (primary && fileStatus.fileAttributes?.attributeIdToValue) {
this.primaryAttribute = fileStatus.fileAttributes?.attributeIdToValue[primary.id];
this.searchField += ' ' + this.primaryAttribute;
}
if (!this.primaryAttribute) {
// Fallback here
this.primaryAttribute = '-';
}
}
}
get analysisDuration() {
return this.fileStatus.analysisDuration;
@ -52,6 +71,14 @@ export class FileStatusWrapper {
return this.fileStatus.filename;
}
get hasAnnotationComments() {
return this.fileStatus.hasAnnotationComments;
}
get ocrTime() {
return this.fileStatus.lastOCRTime;
}
get hasHints() {
return this.fileStatus.hasHints;
}

View File

@ -158,24 +158,6 @@
tooltip="file-preview.download-original-file"
tooltipPosition="below"
></redaction-circle-button>
<redaction-circle-button
*ngIf="userPreferenceService.areDevFeaturesEnabled"
(action)="openSSRFilePreview()"
icon="red:new-tab"
type="primary"
class="ml-8"
tooltip="file-preview.new-tab-ssr"
tooltipPosition="below"
></redaction-circle-button>
<redaction-circle-button
*ngIf="userPreferenceService.areDevFeaturesEnabled"
(action)="openHTMLDebug()"
icon="red:html-file"
type="primary"
class="ml-8"
tooltip="file-preview.html-debug"
tooltipPosition="below"
></redaction-circle-button>
<!-- End Dev Mode Features-->
<redaction-circle-button

View File

@ -181,6 +181,17 @@
</div>
<!-- <span *ngIf="permissionsService.fileRequiresReanalysis(fileStatus)" class="pill" translate="project-overview.new-rule.label"></span>-->
</div>
<div class="small-label stats-subtitle" *ngIf="fileStatus.primaryAttribute">
<div class="primary-attribute">
{{ fileStatus.primaryAttribute }}
</div>
</div>
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:ocr"></mat-icon>
{{ fileStatus.ocrTime ? (fileStatus.ocrTime | date: 'mediumDate') : ('project-overview.no-ocr' | translate) }}
</div>
</div>
</div>
<div>

View File

@ -121,3 +121,7 @@ cdk-virtual-scroll-viewport {
color: initial;
}
}
.primary-attribute {
padding-top: 6px;
}

View File

@ -34,7 +34,7 @@ import { OnAttach, OnDetach } from '../../../../utils/custom-route-reuse.strateg
styleUrls: ['./project-overview-screen.component.scss']
})
export class ProjectOverviewScreenComponent extends BaseListingComponent<FileStatusWrapper> implements OnInit, OnDestroy, OnDetach, OnAttach {
protected readonly _searchKey = 'filename';
protected readonly _searchKey = 'searchField';
protected readonly _selectionKey = 'fileId';
protected readonly _sortKey = 'project-overview';
@ -42,7 +42,6 @@ export class ProjectOverviewScreenComponent extends BaseListingComponent<FileSta
public peopleFilters: FilterModel[];
public needsWorkFilters: FilterModel[];
public collapsedDetails = false;
public searchForm: FormGroup;
detailsContainerFilters: {
needsWorkFilters: FilterModel[];

View File

@ -2,6 +2,7 @@ import { EventEmitter, Injectable } from '@angular/core';
import {
DictionaryControllerService,
FileAttributeConfig,
FileAttributesConfig,
FileAttributesControllerService,
FileStatus,
Project,
@ -17,8 +18,8 @@ import { NotificationService, NotificationType } from '../services/notification.
import { TranslateService } from '@ngx-translate/core';
import { Event, NavigationEnd, ResolveStart, Router } from '@angular/router';
import { UserService } from '../services/user.service';
import { forkJoin, Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { forkJoin, Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { humanize } from '../utils/functions';
import { FileStatusWrapper } from '../models/file/file-status.wrapper';
import { ProjectWrapper } from './model/project.wrapper';
@ -243,12 +244,17 @@ export class AppStateService {
});
const fileData = await this._statusControllerService.getFileStatusForProjects(mappedProjects.map((p) => p.projectId)).toPromise();
for (const projectId of Object.keys(fileData)) {
this._processFiles(
mappedProjects.find((p) => p.projectId === projectId),
fileData[projectId],
emitEvents
);
const project = mappedProjects.find((p) => p.projectId === projectId);
const fileAttributesConfig = await this._fileAttributesService
.getFileAttributesConfiguration(project.ruleSetId)
.pipe(catchError(() => of({})))
.toPromise();
console.log(fileAttributesConfig);
this._processFiles(project, fileData[projectId], emitEvents, fileAttributesConfig);
}
this._appState.projects = mappedProjects;
@ -266,7 +272,11 @@ export class AppStateService {
const oldProcessedDate = this.activeFile.lastProcessed;
const activeFile = await this._statusControllerService.getFileStatus(this.activeProjectId, this.activeFileId).toPromise();
const activeFileWrapper = new FileStatusWrapper(activeFile, this._userService.getNameForId(activeFile.currentReviewer));
const activeFileWrapper = new FileStatusWrapper(
activeFile,
this._userService.getNameForId(activeFile.currentReviewer),
this._appState.activeFileAttributesConfig
);
this.activeProject.files = this.activeProject.files.map((file) => (file.fileId === activeFileWrapper.fileId ? activeFileWrapper : file));
await this.updateDictionaryVersion();
@ -282,12 +292,17 @@ export class AppStateService {
if (!project) {
project = this.activeProject;
}
const files = await this._statusControllerService.getProjectStatus(project.project.projectId).toPromise();
const files = await this._statusControllerService.getProjectStatus(project.projectId).toPromise();
return this._processFiles(project, files, emitEvents);
const fileAttributesConfig = await this._fileAttributesService
.getFileAttributesConfiguration(project.ruleSetId)
.pipe(catchError(() => of({})))
.toPromise();
return this._processFiles(project, files, emitEvents, fileAttributesConfig);
}
private _processFiles(project: ProjectWrapper, files: FileStatus[], emitEvents: boolean = true) {
private _processFiles(project: ProjectWrapper, files: FileStatus[], emitEvents: boolean = true, fileAttributesConfig: FileAttributesConfig) {
const oldFiles = [...project.files];
const fileStatusChangedEvent = [];
@ -298,7 +313,11 @@ export class AppStateService {
for (const oldFile of oldFiles) {
if (oldFile.fileId === file.fileId) {
// emit when analysis count changed
const fileStatusWrapper = new FileStatusWrapper(file, this._userService.getNameForId(file.currentReviewer));
const fileStatusWrapper = new FileStatusWrapper(
file,
this._userService.getNameForId(file.currentReviewer),
fileAttributesConfig?.fileAttributeConfigs
);
if (JSON.stringify(oldFile) !== JSON.stringify(fileStatusWrapper)) {
fileStatusChangedEvent.push(fileStatusWrapper);
}
@ -311,12 +330,14 @@ export class AppStateService {
}
// emit for new file
if (!found) {
const fsw = new FileStatusWrapper(file, this._userService.getNameForId(file.currentReviewer));
const fsw = new FileStatusWrapper(file, this._userService.getNameForId(file.currentReviewer), fileAttributesConfig?.fileAttributeConfigs);
fileStatusChangedEvent.push(fsw);
}
}
project.files = files.map((f) => new FileStatusWrapper(f, this._userService.getNameForId(f.currentReviewer)));
project.files = files.map(
(f) => new FileStatusWrapper(f, this._userService.getNameForId(f.currentReviewer), fileAttributesConfig?.fileAttributeConfigs)
);
this._computeStats();
if (emitEvents) {
@ -343,12 +364,6 @@ export class AppStateService {
this._router.navigate(['/main/projects']);
return;
}
this._fileAttributesService
.getFileAttributesConfiguration(this.getProjectById(projectId).ruleSetId)
.toPromise()
.then((data) => {
this._appState.activeFileAttributesConfig = data.fileAttributeConfigs;
});
}
activateFile(projectId: string, fileId: string) {

View File

@ -192,6 +192,7 @@
"collapse": "Hide Details"
},
"project-overview": {
"no-ocr": "No OCR",
"no-data": {
"title": "There are no documents yet.",
"action": "Upload Document"

View File

@ -48,6 +48,10 @@ export interface FileStatus {
* The file's name.
*/
filename?: string;
/**
* Shows if this file has comments on annotations.
*/
hasAnnotationComments?: boolean;
/**
* Shows if any hints were found during the analysis.
*/
@ -68,6 +72,10 @@ export interface FileStatus {
* Shows if there is any change between the previous and current analysis.
*/
hasUpdates?: boolean;
/**
* Shows if this file has been OCRed by us. Last Time of OCR.
*/
lastOCRTime?: string;
/**
* Shows the last date of a successful analysis.
*/
@ -109,31 +117,30 @@ export interface FileStatus {
*/
uploader?: string;
}
export namespace FileStatus {
export type StatusEnum =
| 'UNPROCESSED'
| 'REPROCESS'
| 'FULLREPROCESS'
| 'PROCESSING'
| 'OCR_PROCESSING'
| 'ERROR'
| 'UNASSIGNED'
| 'UNDER_REVIEW'
| 'EXCLUDED'
| 'UNDER_APPROVAL'
| 'APPROVED';
| 'APPROVED'
| 'FULLREPROCESS'
| 'OCR_PROCESSING'
| 'EXCLUDED';
export const StatusEnum = {
UNPROCESSED: 'UNPROCESSED' as StatusEnum,
FULLREPROCESS: 'FULLREPROCESS' as StatusEnum,
REPROCESS: 'REPROCESS' as StatusEnum,
PROCESSING: 'PROCESSING' as StatusEnum,
OCR_PROCESSING: 'OCR_PROCESSING' as StatusEnum,
ERROR: 'ERROR' as StatusEnum,
UNASSIGNED: 'UNASSIGNED' as StatusEnum,
UNDERREVIEW: 'UNDER_REVIEW' as StatusEnum,
UNDERAPPROVAL: 'UNDER_APPROVAL' as StatusEnum,
APPROVED: 'APPROVED' as StatusEnum,
FULLREPROCESS: 'FULLREPROCESS' as StatusEnum,
OCR_PROCESSING: 'OCR_PROCESSING' as StatusEnum,
EXCLUDED: 'EXCLUDED' as StatusEnum
};
}