Rework UI

This commit is contained in:
Timo Bejan 2020-10-07 11:16:47 +03:00
parent 8da122c59f
commit e0c4f34b5c
19 changed files with 407 additions and 235 deletions

View File

@ -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,

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -7,5 +7,5 @@
.viewer {
width: 100%;
height: calc(100vh - (61px + 80px + 50px + 40px))
height: calc(100vh - 122px)
}

View File

@ -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);
}
}

View File

@ -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");
}
}

View File

@ -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>

View File

@ -1,2 +1,7 @@
@import "../../../assets/styles/red-mixins";
:host {
max-width: 1100px;
margin: 0 auto;
padding: 20px;
}

View File

@ -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;
}
});
}
}

View File

@ -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>

View File

@ -1,4 +1,6 @@
.listing {
position: relative;
height: calc(100vh - (61px + 80px + 70px + 80px));

View File

@ -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;
}

View File

@ -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>

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -11,7 +11,7 @@
"target": "es2015",
"module": "esnext",
"typeRoots": ["node_modules/@types"],
"lib": ["es2017", "dom"],
"lib": ["es2019", "dom"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"baseUrl": ".",