Rework UI
This commit is contained in:
parent
8da122c59f
commit
e0c4f34b5c
@ -42,74 +42,77 @@ import {ProjectDetailsDialogComponent} from './screens/project-overview-screen/p
|
||||
import {AuthModule} from "./auth/auth.module";
|
||||
import {AuthGuard} from "./auth/auth.guard";
|
||||
import {FileUploadModule} from "./upload/file-upload.module";
|
||||
import { FullPageLoadingIndicatorComponent } from './utils/full-page-loading-indicator/full-page-loading-indicator.component';
|
||||
import {MatProgressSpinnerModule} from "@angular/material/progress-spinner";
|
||||
|
||||
export function HttpLoaderFactory(httpClient: HttpClient) {
|
||||
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent, BaseScreenComponent, ProjectListingScreenComponent, ProjectOverviewScreenComponent, AddEditProjectDialogComponent, ConfirmationDialogComponent, FilePreviewScreenComponent, PdfViewerComponent, FileDetailsDialogComponent, ProjectDetailsDialogComponent],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
ReactiveFormsModule,
|
||||
HttpClientModule,
|
||||
AuthModule,
|
||||
IconsModule,
|
||||
ApiModule,
|
||||
MatDialogModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useFactory: HttpLoaderFactory,
|
||||
deps: [HttpClient]
|
||||
}
|
||||
}),
|
||||
RouterModule.forRoot([
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'ui/projects',
|
||||
pathMatch: 'full',
|
||||
},
|
||||
{
|
||||
path: 'ui',
|
||||
component: BaseScreenComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'projects',
|
||||
component: ProjectListingScreenComponent,
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'projects/:projectId',
|
||||
component: ProjectOverviewScreenComponent,
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'projects/:projectId/file/:fileId',
|
||||
component: FilePreviewScreenComponent,
|
||||
canActivate: [AuthGuard]
|
||||
}
|
||||
]
|
||||
}
|
||||
declarations: [AppComponent, BaseScreenComponent, ProjectListingScreenComponent, ProjectOverviewScreenComponent, AddEditProjectDialogComponent, ConfirmationDialogComponent, FilePreviewScreenComponent, PdfViewerComponent, FileDetailsDialogComponent, ProjectDetailsDialogComponent, FullPageLoadingIndicatorComponent],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
ReactiveFormsModule,
|
||||
HttpClientModule,
|
||||
AuthModule,
|
||||
IconsModule,
|
||||
ApiModule,
|
||||
MatDialogModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useFactory: HttpLoaderFactory,
|
||||
deps: [HttpClient]
|
||||
}
|
||||
}),
|
||||
RouterModule.forRoot([
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'ui/projects',
|
||||
pathMatch: 'full',
|
||||
},
|
||||
{
|
||||
path: 'ui',
|
||||
component: BaseScreenComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'projects',
|
||||
component: ProjectListingScreenComponent,
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'projects/:projectId',
|
||||
component: ProjectOverviewScreenComponent,
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'projects/:projectId/file/:fileId',
|
||||
component: FilePreviewScreenComponent,
|
||||
canActivate: [AuthGuard]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
]),
|
||||
NgpSortModule,
|
||||
MatToolbarModule,
|
||||
MatButtonModule,
|
||||
MatMenuModule,
|
||||
MatIconModule,
|
||||
MatTooltipModule,
|
||||
MatSnackBarModule,
|
||||
MatTabsModule,
|
||||
MatButtonToggleModule,
|
||||
MatFormFieldModule,
|
||||
ToastrModule.forRoot(),
|
||||
MatSelectModule,
|
||||
MatSidenavModule,
|
||||
FileUploadModule,
|
||||
ServiceWorkerModule.register('ngsw-worker.js', {enabled: environment.production})
|
||||
],
|
||||
]),
|
||||
NgpSortModule,
|
||||
MatToolbarModule,
|
||||
MatButtonModule,
|
||||
MatMenuModule,
|
||||
MatIconModule,
|
||||
MatTooltipModule,
|
||||
MatSnackBarModule,
|
||||
MatTabsModule,
|
||||
MatButtonToggleModule,
|
||||
MatFormFieldModule,
|
||||
ToastrModule.forRoot(),
|
||||
MatSelectModule,
|
||||
MatSidenavModule,
|
||||
FileUploadModule,
|
||||
ServiceWorkerModule.register('ngsw-worker.js', {enabled: environment.production}),
|
||||
MatProgressSpinnerModule
|
||||
],
|
||||
providers: [ {
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
multi: true,
|
||||
|
||||
@ -1,52 +1 @@
|
||||
@import "../../../assets/styles/red-variables";
|
||||
|
||||
.red-top-bar {
|
||||
height: 61px;
|
||||
width: 100%;
|
||||
max-height: 61px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.top-bar-row {
|
||||
height: 60px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-left: 60px;
|
||||
padding-right: 60px;
|
||||
|
||||
.menu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
color: $yellow-1;
|
||||
|
||||
mat-icon {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
opacity: 0.15;
|
||||
background-color: $grey-1;
|
||||
}
|
||||
}
|
||||
|
||||
.red-content {
|
||||
width: 100vw;
|
||||
height: calc(100vh - 61px);
|
||||
overflow: auto;
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
|
||||
.red-content-inner {
|
||||
padding: 40px 40px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +1,29 @@
|
||||
<div class="page-header">
|
||||
<div class="heading-l clamp-2">{{appStateService.activeFile?.filename}}</div>
|
||||
<div class="toggle-buttons">
|
||||
<mat-button-toggle-group #group="matButtonToggleGroup" name="type" value="ANNOTATED">
|
||||
<mat-button-toggle value="ORIGINAL">Original</mat-button-toggle>
|
||||
<mat-button-toggle value="ANNOTATED">Annotated</mat-button-toggle>
|
||||
<mat-button-toggle value="REDACTED">Redacted</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
<section>
|
||||
<div class="red-top-bar">
|
||||
<div class="top-bar-row">
|
||||
<div class="menu left">
|
||||
<mat-button-toggle-group #group="matButtonToggleGroup" name="type" value="ANNOTATED"
|
||||
(change)="viewerChanged(group.value)">
|
||||
<mat-button-toggle value="ANNOTATED">Annotated</mat-button-toggle>
|
||||
<mat-button-toggle value="REDACTED">Redacted</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
</div>
|
||||
<div class="menu right ">
|
||||
<div class="heading-l clamp-1">
|
||||
{{appStateService.activeFile?.filename}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<redaction-pdf-viewer [fileId]="fileId" [fileType]="group.value" [fileStatus]="appStateService.activeFile"></redaction-pdf-viewer>
|
||||
|
||||
<button (click)="showDetailsDialog($event)" aria-label="details" class="details-button" color="primary" mat-fab>
|
||||
<mat-icon svgIcon="red:info"></mat-icon>
|
||||
</button>
|
||||
<div class="view-container">
|
||||
<redaction-pdf-viewer [class.visible]="group.value === 'ANNOTATED'" [fileId]="fileId" fileType="ANNOTATED"
|
||||
[fileStatus]="appStateService.activeFile"></redaction-pdf-viewer>
|
||||
<redaction-pdf-viewer [class.visible]="group.value === 'REDACTED'" [fileId]="fileId" fileType="REDACTED"
|
||||
[fileStatus]="appStateService.activeFile"></redaction-pdf-viewer>
|
||||
</div>
|
||||
|
||||
<button (click)="showDetailsDialog($event)" aria-label="details" class="details-button" color="primary" mat-fab>
|
||||
<mat-icon svgIcon="red:info"></mat-icon>
|
||||
</button>
|
||||
</section>
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
|
||||
redaction-pdf-viewer {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
|
||||
&.visible {
|
||||
z-index: 100;
|
||||
}
|
||||
}
|
||||
|
||||
.view-container{
|
||||
width: 100%;
|
||||
height: calc(100vh - 122px);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import {NotificationService} from "../../../notification/notification.service";
|
||||
import {MatDialog} from "@angular/material/dialog";
|
||||
import {AppStateService} from "../../../state/app-state.service";
|
||||
import {FileDetailsDialogComponent} from "./file-details-dialog/file-details-dialog.component";
|
||||
import {ViewerSyncService} from "../service/viwer-sync.service";
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-file-preview-screen',
|
||||
@ -23,7 +24,8 @@ export class FilePreviewScreenComponent implements OnInit {
|
||||
private readonly _statusControllerService: StatusControllerService,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _dialog: MatDialog,
|
||||
private readonly _viewerSyncService: ViewerSyncService,
|
||||
private readonly _dialog: MatDialog,
|
||||
private readonly _router: Router,
|
||||
private readonly _fileUploadControllerService: FileUploadControllerService,
|
||||
private readonly _projectControllerService: ProjectControllerService) {
|
||||
@ -36,6 +38,7 @@ export class FilePreviewScreenComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._viewerSyncService.activateViewer('ANNOTATED');
|
||||
}
|
||||
|
||||
showDetailsDialog($event: MouseEvent) {
|
||||
@ -46,4 +49,8 @@ export class FilePreviewScreenComponent implements OnInit {
|
||||
data: this.appStateService.activeFile
|
||||
});
|
||||
}
|
||||
|
||||
viewerChanged(value: any) {
|
||||
this._viewerSyncService.activateViewer(value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,5 +7,5 @@
|
||||
|
||||
.viewer {
|
||||
width: 100%;
|
||||
height: calc(100vh - (61px + 80px + 50px + 40px))
|
||||
height: calc(100vh - 122px)
|
||||
}
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import {AfterViewInit, Component, ElementRef, Input, OnChanges, OnInit, SimpleChanges, ViewChild} from '@angular/core';
|
||||
import {AfterViewInit, Component, ElementRef, Input, OnDestroy, OnInit, ViewChild} from '@angular/core';
|
||||
import {AppConfigKey, AppConfigService} from "../../../app-config/app-config.service";
|
||||
import {FileStatus, FileUploadControllerService} from "@redaction/red-ui-http";
|
||||
import {Observable, of} from "rxjs";
|
||||
import {tap} from "rxjs/operators";
|
||||
import WebViewer, {WebViewerInstance} from "@pdftron/webviewer";
|
||||
import {TranslateService} from "@ngx-translate/core";
|
||||
import {ViewerSyncService} from "../service/viwer-sync.service";
|
||||
|
||||
|
||||
export enum FileType {
|
||||
@ -18,7 +19,7 @@ export enum FileType {
|
||||
templateUrl: './pdf-viewer.component.html',
|
||||
styleUrls: ['./pdf-viewer.component.scss']
|
||||
})
|
||||
export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
|
||||
export class PdfViewerComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
@Input() fileId: string;
|
||||
@Input() fileType: FileType;
|
||||
@ -33,6 +34,7 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
|
||||
|
||||
|
||||
constructor(
|
||||
private readonly _viewerSyncService: ViewerSyncService,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _fileUploadControllerService: FileUploadControllerService,
|
||||
private readonly _appConfigService: AppConfigService) {
|
||||
@ -44,6 +46,9 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
|
||||
|
||||
wvDocumentLoadedHandler(): void {
|
||||
this.wvInstance.setFitMode('FitWidth');
|
||||
const displayMode = this.wvInstance.docViewer.getDisplayModeManager().getDisplayMode();
|
||||
displayMode.mode = "Continuous";
|
||||
this.wvInstance.docViewer.getDisplayModeManager().setDisplayMode(displayMode);
|
||||
if (this.fileType === FileType.ANNOTATED) {
|
||||
this.wvInstance.toggleElement('notesPanel')
|
||||
}
|
||||
@ -55,24 +60,19 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
|
||||
});
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (this.wvInstance) {
|
||||
this._loadFile().subscribe(data => {
|
||||
this.wvInstance.loadDocument(data, {filename: this.fileStatus.filename});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _loadViewer(pdfBlob: any) {
|
||||
const license = this._appConfigService.getConfig(AppConfigKey.PDFTRON_LICENSE);
|
||||
WebViewer({
|
||||
licenseKey: license,
|
||||
isReadOnly: true,
|
||||
path: '/assets/wv-resources',
|
||||
}, this.viewer.nativeElement).then(instance => {
|
||||
this.wvInstance = instance;
|
||||
this._viewerSyncService.registerViewer(this.fileType, this.wvInstance);
|
||||
this._configureTextPopup();
|
||||
instance.docViewer.on('documentLoaded', this.wvDocumentLoadedHandler)
|
||||
instance.loadDocument(pdfBlob, {filename: this.fileStatus.filename});
|
||||
instance.loadDocument(pdfBlob, {filename: this.fileStatus ? this.fileStatus.filename : 'file.pdf'});
|
||||
})
|
||||
}
|
||||
|
||||
@ -106,11 +106,14 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
|
||||
img: '/assets/icons/general/add.svg',
|
||||
title: this._translateService.instant('pdf-viewer.text-popup.actions.suggestion-redaction.label'),
|
||||
onClick: () => {
|
||||
const selectedQuads = this.wvInstance.docViewer.getSelectedTextQuads();
|
||||
|
||||
|
||||
console.log(selectedQuads);
|
||||
const selectedQuads = this.wvInstance.docViewer.getSelectedTextQuads();
|
||||
console.log(selectedQuads);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this._viewerSyncService.deregisterInstance(this.fileType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,59 @@
|
||||
import {Injectable} from "@angular/core";
|
||||
import {WebViewerInstance} from "@pdftron/webviewer";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ViewerSyncService {
|
||||
|
||||
private _activeViewer: string;
|
||||
|
||||
private _viewers: { [key: string]: WebViewerInstance } = {};
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
|
||||
syncViewers() {
|
||||
if (this._activeViewer) {
|
||||
const lastScrolledViewer = this._viewers[this._activeViewer];
|
||||
if (lastScrolledViewer) {
|
||||
const lastScrolledViewerScrollElement = lastScrolledViewer.docViewer.getScrollViewElement();
|
||||
const lastViewerScrollHeight = lastScrolledViewerScrollElement.scrollHeight;
|
||||
const lastViewerScrollTop = lastScrolledViewerScrollElement.scrollTop;
|
||||
for (const key of Object.keys(this._viewers)) {
|
||||
if (key !== this._activeViewer) {
|
||||
const viewerScrollElement = this._viewers[key].docViewer.getScrollViewElement();
|
||||
const viewerScrollHeight = viewerScrollElement.scrollHeight;
|
||||
if (viewerScrollHeight === lastViewerScrollHeight) {
|
||||
viewerScrollElement.scrollTo(viewerScrollElement.scrollLeft, lastViewerScrollTop);
|
||||
} else if (viewerScrollHeight > lastViewerScrollHeight) {
|
||||
let delta = viewerScrollHeight / lastViewerScrollHeight;
|
||||
delta = this._roundToTwo(delta);
|
||||
viewerScrollElement.scrollTo(viewerScrollElement.scrollLeft, delta * lastViewerScrollTop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
deregisterInstance(key: string) {
|
||||
delete this._viewers[key];
|
||||
}
|
||||
|
||||
registerViewer(key: string, instance: WebViewerInstance) {
|
||||
this._viewers[key] = instance;
|
||||
}
|
||||
|
||||
activateViewer(key: string) {
|
||||
this.syncViewers();
|
||||
this._activeViewer = key;
|
||||
}
|
||||
|
||||
|
||||
private _roundToTwo(num) {
|
||||
return +(Math.round(Number(num + "e+2")) + "e-2");
|
||||
}
|
||||
}
|
||||
@ -1,35 +1,36 @@
|
||||
<div class="page-header">
|
||||
<div class="heading-xl" translate="projects.header.label"></div>
|
||||
<button (click)="openAddProjectDialog()" color="accent" mat-flat-button translate="projects.add-new.label"></button>
|
||||
</div>
|
||||
<section class="center-section" *ngIf="viewReady">
|
||||
<div class="page-header">
|
||||
<div class="heading-xl" translate="projects.header.label"></div>
|
||||
<button (click)="openAddProjectDialog()" color="accent" mat-flat-button translate="projects.add-new.label"></button>
|
||||
</div>
|
||||
|
||||
<div *ngIf="appStateService.allProjects?.length === 0 " translate="projects.no-projects.label"></div>
|
||||
<div class="listing">
|
||||
<div *ngFor="let project of appStateService.allProjects" [routerLink]="'/ui/projects/'+project.projectId"
|
||||
class="list-entry clickable">
|
||||
<div class="list-entry-content">
|
||||
<div class="listing-title">
|
||||
{{project.projectName}}
|
||||
<div *ngIf="appStateService.allProjects?.length === 0 " translate="projects.no-projects.label"></div>
|
||||
<div class="listing">
|
||||
<div *ngFor="let project of appStateService.allProjects" [routerLink]="'/ui/projects/'+project.projectId"
|
||||
class="list-entry clickable">
|
||||
<div class="list-entry-content">
|
||||
<div class="listing-title">
|
||||
{{project.projectName}}
|
||||
</div>
|
||||
<div class="listing-subtitle">
|
||||
{{project.description}}
|
||||
</div>
|
||||
<div class="listing-subtitle">
|
||||
{{project.date | date:'short'}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="listing-subtitle">
|
||||
{{project.description}}
|
||||
<div class="list-entry-actions">
|
||||
<button (click)="editProject($event,project)" mat-icon-button
|
||||
[matTooltip]="'projects.edit.action.label'|translate">
|
||||
<mat-icon svgIcon="red:edit"></mat-icon>
|
||||
</button>
|
||||
<button (click)="deleteProject($event,project)" color="warn" mat-icon-button
|
||||
[matTooltip]="'projects.delete.action.label'|translate">
|
||||
<mat-icon svgIcon="red:delete"></mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="listing-subtitle">
|
||||
{{project.date | date:'short'}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-entry-actions">
|
||||
<button (click)="editProject($event,project)" mat-icon-button
|
||||
[matTooltip]="'projects.edit.action.label'|translate">
|
||||
<mat-icon svgIcon="red:edit"></mat-icon>
|
||||
</button>
|
||||
<button (click)="deleteProject($event,project)" color="warn" mat-icon-button
|
||||
[matTooltip]="'projects.delete.action.label'|translate">
|
||||
<mat-icon svgIcon="red:delete"></mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
<redaction-full-page-loading-indicator [displayed]="!viewReady"></redaction-full-page-loading-indicator>
|
||||
|
||||
@ -1,2 +1,7 @@
|
||||
@import "../../../assets/styles/red-mixins";
|
||||
|
||||
:host {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ import {AppStateService} from "../../state/app-state.service";
|
||||
})
|
||||
export class ProjectListingScreenComponent implements OnInit {
|
||||
|
||||
viewReady = false;
|
||||
|
||||
constructor(
|
||||
public readonly appStateService: AppStateService,
|
||||
@ -24,7 +25,7 @@ export class ProjectListingScreenComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._reloadProjects();
|
||||
this._reloadProjects(true);
|
||||
}
|
||||
|
||||
openAddProjectDialog(project?: Project): void {
|
||||
@ -62,9 +63,12 @@ export class ProjectListingScreenComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
private _reloadProjects() {
|
||||
private _reloadProjects(initial: boolean = false) {
|
||||
this.appStateService.reset();
|
||||
this.appStateService.loadAllProjects().subscribe(() => {
|
||||
if (initial) {
|
||||
this.viewReady = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,67 +1,70 @@
|
||||
<div *ngIf="!appStateService.activeProject"
|
||||
[innerHTML]="'project-overview.no-project.label' | translate:{projectId: projectId}"
|
||||
class="heading-l"></div>
|
||||
<section class="center-section" *ngIf="viewReady">
|
||||
<div *ngIf="!appStateService.activeProject"
|
||||
[innerHTML]="'project-overview.no-project.label' | translate:{projectId: projectId}"
|
||||
class="heading-l"></div>
|
||||
|
||||
|
||||
<div *ngIf="appStateService.activeProject" class="page-header">
|
||||
<div class="heading-xl clamp-1">{{appStateService.activeProject.projectName}}</div>
|
||||
<button (click)="fileInput.click()" color="accent" mat-flat-button
|
||||
translate="project-overview.upload-files.label"></button>
|
||||
<input #fileInput (change)="uploadFiles($event.target.files)" class="file-upload-input" multiple="true"
|
||||
type="file">
|
||||
</div>
|
||||
<div class="flex-row">
|
||||
<div class="heading-l clamp-2">{{appStateService.activeProject?.description}}</div>
|
||||
<mat-form-field *ngIf="appStateService.projectFiles && appStateService.projectFiles.length > 0">
|
||||
<mat-label>{{'project-overview.sorting.label' | translate}}</mat-label>
|
||||
<mat-select (valueChange)="sortingChanged($event)" [value]="sorting" color="primary">
|
||||
<mat-option *ngFor="let option of sortOptions" [value]="option.value">
|
||||
<mat-icon [svgIcon]="option.icon"></mat-icon>
|
||||
{{option.label | translate}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="break-20"></div>
|
||||
<div class="listing">
|
||||
<div *ngFor="let fileStatus of appStateService.projectFiles | sortBy: sorting.order:sorting.name; trackBy:fileId"
|
||||
[class.clickable]="fileStatus.status === 'PROCESSED'"
|
||||
[routerLink]="fileStatus.status === 'PROCESSED' ? ['/ui/projects/'+projectId+'/file/'+fileStatus.fileId] : []"
|
||||
class="list-entry xl">
|
||||
<div class="list-entry-content">
|
||||
<div class="listing-title one-line slim break-all">
|
||||
{{fileStatus.filename}}
|
||||
<div *ngIf="appStateService.activeProject" class="page-header">
|
||||
<div class="heading-xl clamp-1">{{appStateService.activeProject.projectName}}</div>
|
||||
<button (click)="fileInput.click()" color="accent" mat-flat-button
|
||||
translate="project-overview.upload-files.label"></button>
|
||||
<input #fileInput (change)="uploadFiles($event.target.files)" class="file-upload-input" multiple="true"
|
||||
type="file">
|
||||
</div>
|
||||
<div class="flex-row">
|
||||
<div class="heading-l clamp-2">{{appStateService.activeProject?.description}}</div>
|
||||
<mat-form-field *ngIf="appStateService.projectFiles && appStateService.projectFiles.length > 0">
|
||||
<mat-label>{{'project-overview.sorting.label' | translate}}</mat-label>
|
||||
<mat-select (valueChange)="sortingChanged($event)" [value]="sorting" color="primary">
|
||||
<mat-option *ngFor="let option of sortOptions" [value]="option.value">
|
||||
<mat-icon [svgIcon]="option.icon"></mat-icon>
|
||||
{{option.label | translate}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="break-20"></div>
|
||||
<div class="listing">
|
||||
<div *ngFor="let fileStatus of appStateService.projectFiles | sortBy: sorting.order:sorting.name; trackBy:fileId"
|
||||
[class.clickable]="fileStatus.status === 'PROCESSED'"
|
||||
[routerLink]="fileStatus.status === 'PROCESSED' ? ['/ui/projects/'+projectId+'/file/'+fileStatus.fileId] : []"
|
||||
class="list-entry xl">
|
||||
<div class="list-entry-content">
|
||||
<div class="listing-title one-line slim break-all">
|
||||
{{fileStatus.filename}}
|
||||
</div>
|
||||
<div class="listing-subtitle">
|
||||
{{'project-overview.file-listing.file-entry.status.label'| translate:fileStatus}}
|
||||
</div>
|
||||
<div class="listing-subtitle">
|
||||
{{'project-overview.file-listing.file-entry.number-of-pages.label'| translate:fileStatus}}
|
||||
</div>
|
||||
<div class="listing-subtitle">
|
||||
{{'project-overview.file-listing.file-entry.number-of-analyses.label'| translate:fileStatus}}
|
||||
</div>
|
||||
<div class="listing-subtitle">
|
||||
{{'project-overview.file-listing.file-entry.added.label'| translate:{added: fileStatus.added | date:'short'} }}
|
||||
</div>
|
||||
<div *ngIf="fileStatus.lastUpdated" class="listing-subtitle">
|
||||
{{'project-overview.file-listing.file-entry.last-updated.label'| translate:{lastUpdated: fileStatus.lastUpdated | date:'short'} }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="listing-subtitle">
|
||||
{{'project-overview.file-listing.file-entry.status.label'| translate:fileStatus}}
|
||||
<div class="list-entry-actions">
|
||||
<button (click)="deleteFile($event,fileStatus)" color="warn" mat-icon-button
|
||||
[matTooltip]="'project-overview.delete.action.label'|translate">
|
||||
<mat-icon svgIcon="red:delete"></mat-icon>
|
||||
</button>
|
||||
<button (click)="reanalyseFile($event,fileStatus)" color="primary" mat-icon-button
|
||||
[matTooltip]="'project-overview.reanalyse.action.label'|translate">
|
||||
<mat-icon svgIcon="red:refresh"></mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="listing-subtitle">
|
||||
{{'project-overview.file-listing.file-entry.number-of-pages.label'| translate:fileStatus}}
|
||||
</div>
|
||||
<div class="listing-subtitle">
|
||||
{{'project-overview.file-listing.file-entry.number-of-analyses.label'| translate:fileStatus}}
|
||||
</div>
|
||||
<div class="listing-subtitle">
|
||||
{{'project-overview.file-listing.file-entry.added.label'| translate:{added: fileStatus.added | date:'short'} }}
|
||||
</div>
|
||||
<div *ngIf="fileStatus.lastUpdated" class="listing-subtitle">
|
||||
{{'project-overview.file-listing.file-entry.last-updated.label'| translate:{lastUpdated: fileStatus.lastUpdated | date:'short'} }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-entry-actions">
|
||||
<button (click)="deleteFile($event,fileStatus)" color="warn" mat-icon-button
|
||||
[matTooltip]="'project-overview.delete.action.label'|translate">
|
||||
<mat-icon svgIcon="red:delete"></mat-icon>
|
||||
</button>
|
||||
<button (click)="reanalyseFile($event,fileStatus)" color="primary" mat-icon-button
|
||||
[matTooltip]="'project-overview.reanalyse.action.label'|translate">
|
||||
<mat-icon svgIcon="red:refresh"></mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<button (click)="showDetailsDialog($event)" aria-label="details" class="details-button" color="primary" mat-fab>
|
||||
<mat-icon svgIcon="red:info"></mat-icon>
|
||||
</button>
|
||||
<button (click)="showDetailsDialog($event)" aria-label="details" class="details-button" color="primary" mat-fab>
|
||||
<mat-icon svgIcon="red:info"></mat-icon>
|
||||
</button>
|
||||
</section>
|
||||
<redaction-full-page-loading-indicator [displayed]="!viewReady"></redaction-full-page-loading-indicator>
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
|
||||
|
||||
|
||||
.listing {
|
||||
position: relative;
|
||||
height: calc(100vh - (61px + 80px + 70px + 80px));
|
||||
|
||||
@ -23,6 +23,8 @@ import {FileDropOverlayService} from "../../upload/file-drop/service/file-drop-o
|
||||
})
|
||||
export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
|
||||
|
||||
viewReady = false;
|
||||
|
||||
@ViewChild('dropzoneComponent', {static: true}) dropZoneComponent;
|
||||
|
||||
dragActive = false;
|
||||
@ -60,7 +62,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
|
||||
private readonly _projectControllerService: ProjectControllerService) {
|
||||
this._activatedRoute.params.subscribe(params => {
|
||||
this.projectId = params.projectId;
|
||||
this._loadProject();
|
||||
this._loadProject(true);
|
||||
});
|
||||
}
|
||||
|
||||
@ -125,8 +127,11 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
private _loadProject() {
|
||||
private _loadProject(initial: boolean = false) {
|
||||
this.appStateService.activateProject(this.projectId).subscribe(() => {
|
||||
if (initial) {
|
||||
this.viewReady = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -135,7 +140,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
fileId(index, item){
|
||||
fileId(index, item) {
|
||||
return item.fileId;
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
<section class="full-page-load-section" *ngIf="displayed">
|
||||
</section>
|
||||
<section class="full-page-load-spinner" *ngIf="displayed">
|
||||
<mat-spinner diameter="40"></mat-spinner>
|
||||
</section>
|
||||
@ -0,0 +1,24 @@
|
||||
@import "../../../assets/styles/red-variables";
|
||||
|
||||
|
||||
.full-page-load-section, .full-page-load-spinner {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.full-page-load-section {
|
||||
opacity: 0.7;
|
||||
background: $white;
|
||||
z-index: 900;
|
||||
|
||||
}
|
||||
|
||||
.full-page-load-spinner {
|
||||
z-index: 1000;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-full-page-loading-indicator',
|
||||
templateUrl: './full-page-loading-indicator.component.html',
|
||||
styleUrls: ['./full-page-loading-indicator.component.scss']
|
||||
})
|
||||
export class FullPageLoadingIndicatorComponent {
|
||||
|
||||
@Input() displayed = false;
|
||||
|
||||
}
|
||||
@ -22,6 +22,10 @@ html, body {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: 20px;
|
||||
|
||||
&.slim {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -166,3 +170,57 @@ html, body {
|
||||
line-height: 14px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.center-section {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.red-top-bar {
|
||||
height: 61px;
|
||||
width: 100%;
|
||||
max-height: 61px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.top-bar-row {
|
||||
height: 60px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-left: 60px;
|
||||
padding-right: 60px;
|
||||
|
||||
.menu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
color: $yellow-1;
|
||||
|
||||
mat-icon {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
opacity: 0.15;
|
||||
background-color: $grey-1;
|
||||
}
|
||||
}
|
||||
|
||||
.red-content {
|
||||
width: 100vw;
|
||||
height: calc(100vh - 61px);
|
||||
overflow: auto;
|
||||
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
"target": "es2015",
|
||||
"module": "esnext",
|
||||
"typeRoots": ["node_modules/@types"],
|
||||
"lib": ["es2017", "dom"],
|
||||
"lib": ["es2019", "dom"],
|
||||
"skipLibCheck": true,
|
||||
"skipDefaultLibCheck": true,
|
||||
"baseUrl": ".",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user