From 6eed5efedb33f2aeebadf31e6703b70d59792095 Mon Sep 17 00:00:00 2001 From: Timo Bejan Date: Sun, 4 Oct 2020 20:46:47 +0300 Subject: [PATCH] Upload and OAUTH finished --- apps/red-ui/proxy.conf.json | 12 +-- .../src/app/app-config/app-config.service.ts | 1 + apps/red-ui/src/app/app.component.ts | 1 + apps/red-ui/src/app/app.module.ts | 3 +- apps/red-ui/src/app/auth/auth.guard.ts | 11 ++- apps/red-ui/src/app/icons/icons.module.ts | 25 +++++ .../file/pdf-viewer/pdf-viewer.component.ts | 30 ++++-- .../project-overview-screen.component.html | 10 +- .../project-overview-screen.component.scss | 17 ---- .../project-overview-screen.component.ts | 28 ++---- .../upload/file-drop/file-drop.component.html | 11 +++ .../upload/file-drop/file-drop.component.scss | 26 ++++++ .../upload/file-drop/file-drop.component.ts | 46 ++++++++++ .../service/file-drop-overlay.service.ts | 69 ++++++++++++++ .../src/app/upload/file-upload.module.ts | 32 +++++++ .../src/app/upload/file-upload.service.ts | 47 ++++++++++ .../src/app/upload/model/file-upload.model.ts | 8 ++ .../service/upload-status-overlay.service.ts | 41 +++++++++ .../upload-status-overlay.component.html | 77 ++++++++++++++++ .../upload-status-overlay.component.scss | 91 +++++++++++++++++++ .../upload-status-overlay.component.ts | 39 ++++++++ apps/red-ui/src/app/utils/dialog-helper.ts | 80 ++++++++++++++++ apps/red-ui/src/assets/config/config.json | 3 +- apps/red-ui/src/assets/i18n/en.json | 24 +++++ apps/red-ui/src/assets/icons/general/add.svg | 1 + .../src/assets/icons/general/arrow_down.svg | 10 ++ .../src/assets/icons/general/arrow_up.svg | 10 ++ .../src/assets/icons/general/check_icon.svg | 10 ++ .../src/assets/icons/general/error_icon.svg | 10 ++ .../src/assets/keycloak/silent-check-sso.html | 7 -- .../src/assets/oauth/silent-refresh.html | 7 ++ .../src/assets/styles/red-material-theme.scss | 12 +-- docker/red-ui/docker-entrypoint.sh | 12 +-- package.json | 5 +- yarn.lock | 15 ++- 35 files changed, 745 insertions(+), 86 deletions(-) create mode 100644 apps/red-ui/src/app/upload/file-drop/file-drop.component.html create mode 100644 apps/red-ui/src/app/upload/file-drop/file-drop.component.scss create mode 100644 apps/red-ui/src/app/upload/file-drop/file-drop.component.ts create mode 100644 apps/red-ui/src/app/upload/file-drop/service/file-drop-overlay.service.ts create mode 100644 apps/red-ui/src/app/upload/file-upload.module.ts create mode 100644 apps/red-ui/src/app/upload/file-upload.service.ts create mode 100644 apps/red-ui/src/app/upload/model/file-upload.model.ts create mode 100644 apps/red-ui/src/app/upload/upload-status-dialog/service/upload-status-overlay.service.ts create mode 100644 apps/red-ui/src/app/upload/upload-status-dialog/upload-status-overlay.component.html create mode 100644 apps/red-ui/src/app/upload/upload-status-dialog/upload-status-overlay.component.scss create mode 100644 apps/red-ui/src/app/upload/upload-status-dialog/upload-status-overlay.component.ts create mode 100644 apps/red-ui/src/app/utils/dialog-helper.ts create mode 100644 apps/red-ui/src/assets/icons/general/add.svg create mode 100755 apps/red-ui/src/assets/icons/general/arrow_down.svg create mode 100644 apps/red-ui/src/assets/icons/general/arrow_up.svg create mode 100755 apps/red-ui/src/assets/icons/general/check_icon.svg create mode 100755 apps/red-ui/src/assets/icons/general/error_icon.svg delete mode 100644 apps/red-ui/src/assets/keycloak/silent-check-sso.html create mode 100644 apps/red-ui/src/assets/oauth/silent-refresh.html diff --git a/apps/red-ui/proxy.conf.json b/apps/red-ui/proxy.conf.json index 06a2a01e8..89abfa3cf 100644 --- a/apps/red-ui/proxy.conf.json +++ b/apps/red-ui/proxy.conf.json @@ -1,36 +1,36 @@ { "/project": { - "target": "http://ingress.redaction-timo-dev.178.63.47.73.xip.io", + "target": "http://ingress.redaction.178.63.47.73.xip.io", "secure": false, "changeOrigin": true, "logLevel": "debug" }, "/reanalyse": { - "target": "http://ingress.redaction-timo-dev.178.63.47.73.xip.io", + "target": "http://ingress.redaction.178.63.47.73.xip.io", "secure": false, "changeOrigin": true, "logLevel": "debug" }, "/upload": { - "target": "http://ingress.redaction-timo-dev.178.63.47.73.xip.io", + "target": "http://ingress.redaction.178.63.47.73.xip.io", "secure": false, "changeOrigin": true, "logLevel": "debug" }, "/download": { - "target": "http://ingress.redaction-timo-dev.178.63.47.73.xip.io", + "target": "http://ingress.redaction.178.63.47.73.xip.io", "secure": false, "changeOrigin": true, "logLevel": "debug" }, "/delete": { - "target": "http://ingress.redaction-timo-dev.178.63.47.73.xip.io", + "target": "http://ingress.redaction.178.63.47.73.xip.io", "secure": false, "changeOrigin": true, "logLevel": "debug" }, "/status": { - "target": "http://ingress.redaction-timo-dev.178.63.47.73.xip.io", + "target": "http://ingress.redaction.178.63.47.73.xip.io", "secure": false, "changeOrigin": true, "logLevel": "debug" diff --git a/apps/red-ui/src/app/app-config/app-config.service.ts b/apps/red-ui/src/app/app-config/app-config.service.ts index e9243b2ee..f91da2f9d 100644 --- a/apps/red-ui/src/app/app-config/app-config.service.ts +++ b/apps/red-ui/src/app/app-config/app-config.service.ts @@ -8,6 +8,7 @@ export enum AppConfigKey { OAUTH_URL = "OAUTH_URL", OAUTH_CLIENT_ID = "OAUTH_CLIENT_ID", API_URL = "API_URL", + PDFTRON_LICENSE ="PDFTRON_LICENSE" } @Injectable({ diff --git a/apps/red-ui/src/app/app.component.ts b/apps/red-ui/src/app/app.component.ts index 828555022..ad9861ea0 100644 --- a/apps/red-ui/src/app/app.component.ts +++ b/apps/red-ui/src/app/app.component.ts @@ -7,4 +7,5 @@ import {Component} from '@angular/core'; }) export class AppComponent { + } diff --git a/apps/red-ui/src/app/app.module.ts b/apps/red-ui/src/app/app.module.ts index f8c970b5b..f0fad84c8 100644 --- a/apps/red-ui/src/app/app.module.ts +++ b/apps/red-ui/src/app/app.module.ts @@ -41,6 +41,7 @@ import {environment} from '../environments/environment'; import {ProjectDetailsDialogComponent} from './screens/project-overview-screen/project-details-dialog/project-details-dialog.component'; import {AuthModule} from "./auth/auth.module"; import {AuthGuard} from "./auth/auth.guard"; +import {FileUploadModule} from "./upload/file-upload.module"; export function HttpLoaderFactory(httpClient: HttpClient) { return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json'); @@ -105,8 +106,8 @@ export function HttpLoaderFactory(httpClient: HttpClient) { MatFormFieldModule, ToastrModule.forRoot(), MatSelectModule, - NgxDropzoneModule, MatSidenavModule, + FileUploadModule, ServiceWorkerModule.register('ngsw-worker.js', {enabled: environment.production}) ], providers: [ { diff --git a/apps/red-ui/src/app/auth/auth.guard.ts b/apps/red-ui/src/app/auth/auth.guard.ts index 4688da573..bf6c07c2b 100644 --- a/apps/red-ui/src/app/auth/auth.guard.ts +++ b/apps/red-ui/src/app/auth/auth.guard.ts @@ -4,6 +4,7 @@ import {Observable} from "rxjs"; import {AuthConfig, OAuthService} from "angular-oauth2-oidc"; import {AppConfigKey, AppConfigService} from "../app-config/app-config.service"; import {map} from "rxjs/operators"; +import {JwksValidationHandler} from "angular-oauth2-oidc-jwks"; @Injectable() @@ -18,6 +19,8 @@ export class AuthGuard implements CanActivate { this._configured = true; const authConfig = await this._createConfiguration().toPromise(); this._oauthService.configure(authConfig); + this._oauthService.tokenValidationHandler = new JwksValidationHandler(); + this._oauthService.setupAutomaticSilentRefresh(); return this._oauthService.loadDiscoveryDocumentAndTryLogin(); } @@ -25,11 +28,13 @@ export class AuthGuard implements CanActivate { if (!this._configured) { return this._configure().then(() => this._checkToken()); } + return this._checkToken(); } private _checkToken() { - if (!this._oauthService.getAccessToken()) { + const expired = this._oauthService.getAccessTokenExpiration() - new Date().getTime() < 0; + if (!this._oauthService.getAccessToken() || expired) { this._oauthService.initLoginFlow(); return false; } @@ -42,8 +47,10 @@ export class AuthGuard implements CanActivate { issuer: config[AppConfigKey.OAUTH_URL], redirectUri: window.location.origin, clientId: config[AppConfigKey.OAUTH_CLIENT_ID], - responseType: 'code', + scope: 'openid', showDebugInformation: true, + silentRefreshRedirectUri: window.location.origin + '/assets/oauth/silent-refresh.html', + useSilentRefresh: true, } })); } diff --git a/apps/red-ui/src/app/icons/icons.module.ts b/apps/red-ui/src/app/icons/icons.module.ts index 563918c07..b0f331769 100644 --- a/apps/red-ui/src/app/icons/icons.module.ts +++ b/apps/red-ui/src/app/icons/icons.module.ts @@ -74,6 +74,31 @@ export class IconsModule { sanitizer.bypassSecurityTrustResourceUrl('/assets/icons/general/refresh.svg') ); + iconRegistry.addSvgIconInNamespace( + 'red', + 'arrow-down', + sanitizer.bypassSecurityTrustResourceUrl('/assets/icons/general/arrow_down.svg') + ); + iconRegistry.addSvgIconInNamespace( + 'red', + 'arrow-up', + sanitizer.bypassSecurityTrustResourceUrl('/assets/icons/general/arrow_up.svg') + ); + iconRegistry.addSvgIconInNamespace( + 'red', + 'error', + sanitizer.bypassSecurityTrustResourceUrl( + '/assets/icons/general/error_icon.svg' + ) + ); + iconRegistry.addSvgIconInNamespace( + 'red', + 'check', + sanitizer.bypassSecurityTrustResourceUrl( + '/assets/icons/general/check_icon.svg' + ) + ); + } diff --git a/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts b/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts index c7569000d..33a05b9bf 100644 --- a/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts +++ b/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts @@ -1,10 +1,10 @@ import {AfterViewInit, Component, ElementRef, Input, OnChanges, OnInit, SimpleChanges, ViewChild} from '@angular/core'; -import {KeycloakService} from "keycloak-angular"; -import {AppConfigService} from "../../../app-config/app-config.service"; +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"; export enum FileType { @@ -32,9 +32,10 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges { _redactedFileData: Blob; - constructor(private readonly _keycloak: KeycloakService, - private readonly _fileUploadControllerService: FileUploadControllerService, - private readonly _appConfigService: AppConfigService) { + constructor( + private readonly _translateService: TranslateService, + private readonly _fileUploadControllerService: FileUploadControllerService, + private readonly _appConfigService: AppConfigService) { } ngOnInit() { @@ -43,7 +44,7 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges { wvDocumentLoadedHandler(): void { this.wvInstance.setFitMode('FitWidth'); - if(this.fileType === FileType.ANNOTATED){ + if (this.fileType === FileType.ANNOTATED) { this.wvInstance.toggleElement('notesPanel') } } @@ -63,10 +64,13 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges { } private _loadViewer(pdfBlob: any) { + const license = this._appConfigService.getConfig(AppConfigKey.PDFTRON_LICENSE); WebViewer({ + licenseKey: license, path: '/assets/wv-resources', }, this.viewer.nativeElement).then(instance => { this.wvInstance = instance; + this._configureTextPopup(); instance.docViewer.on('documentLoaded', this.wvDocumentLoadedHandler) instance.loadDocument(pdfBlob, {filename: this.fileStatus.filename}); }) @@ -95,4 +99,18 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges { } return fileObs$; } + + private _configureTextPopup() { + this.wvInstance.textPopup.add({ + type: 'actionButton', + 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); + } + }); + } } diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html index 72f80aee3..530419e28 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html +++ b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html @@ -7,7 +7,7 @@
{{appStateService.activeProject.projectName}}
-
@@ -23,13 +23,7 @@
-
- - {{"project-overview.upload-files.label"| translate}} - +
{ @@ -62,23 +65,12 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { } ngOnInit(): void { + this._fileDropOverlayService.initFileDropHandling(); this._fileStatusInterval = setInterval(() => { this._getFileStatus(); }, 5000); } - handleFileInput(files: FileList | File[]) { - this.dragActive = false; - for (let i = 0; i < files.length; i++) { - const file = files[i]; - this._fileUploadControllerService.uploadFileForm(file, this.projectId, 'response').subscribe(() => { - this._getFileStatus(); - }, () => { - this._notificationService.showToastNotification(this._translateService.instant('project-overview.upload-error.label', file), null, NotificationType.ERROR); - }) - } - } - deleteFile($event: MouseEvent, fileStatus: FileStatus) { $event.stopPropagation(); const dialogRef = this._dialog.open(ConfirmationDialogComponent, { @@ -102,13 +94,6 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { this.sorting = $event; } - dragEnter($event: DragEvent) { - this.dragActive = true; - } - - dragLeave($event: any) { - this.dragActive = false; - } showDetailsDialog($event: MouseEvent) { $event.stopPropagation(); @@ -126,6 +111,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { } ngOnDestroy(): void { + this._fileDropOverlayService.cleanupFileDropHandling(); if (this._fileStatusInterval) { this._fileStatusInterval = null; clearInterval(this._fileStatusInterval) @@ -152,4 +138,8 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { fileId(index, item){ return item.fileId; } + + uploadFiles(files: any) { + + } } diff --git a/apps/red-ui/src/app/upload/file-drop/file-drop.component.html b/apps/red-ui/src/app/upload/file-drop/file-drop.component.html new file mode 100644 index 000000000..9b69a83bd --- /dev/null +++ b/apps/red-ui/src/app/upload/file-drop/file-drop.component.html @@ -0,0 +1,11 @@ +
+ + {{"project-overview.upload-files.label"| translate}} + + + + +
diff --git a/apps/red-ui/src/app/upload/file-drop/file-drop.component.scss b/apps/red-ui/src/app/upload/file-drop/file-drop.component.scss new file mode 100644 index 000000000..8c5394302 --- /dev/null +++ b/apps/red-ui/src/app/upload/file-drop/file-drop.component.scss @@ -0,0 +1,26 @@ +section { + position: fixed; + top: 0; + bottom: 0; + right: 0; + left: 0; + width: 100vw; + height: 100vh; + z-index: 1000; + padding: 12px; + background: white; +} + +.file-drop-zone { + background: white; + height: calc(100% - 26px); + width: calc(100% - 26px); +} + + +.close-icon { + position: absolute; + z-index: 1100; + top: 20px; + right: 40px; +} diff --git a/apps/red-ui/src/app/upload/file-drop/file-drop.component.ts b/apps/red-ui/src/app/upload/file-drop/file-drop.component.ts new file mode 100644 index 000000000..0aea912fe --- /dev/null +++ b/apps/red-ui/src/app/upload/file-drop/file-drop.component.ts @@ -0,0 +1,46 @@ +import {Component, HostListener, OnInit} from '@angular/core'; +import {FileUploadService} from "../file-upload.service"; +import {FileUploadModel} from "../model/file-upload.model"; +import {OverlayRef} from "@angular/cdk/overlay"; +import {UploadStatusOverlayService} from "../upload-status-dialog/service/upload-status-overlay.service"; + +@Component({ + selector: 'redaction-file-drop', + templateUrl: './file-drop.component.html', + styleUrls: ['./file-drop.component.scss'] +}) +export class FileDropComponent implements OnInit { + + constructor(private readonly _dialogRef: OverlayRef, private readonly _fileUploadService: FileUploadService, private _uploadStatusOverlayService: UploadStatusOverlayService) { + } + + ngOnInit() { + } + + close() { + this._dialogRef.detach(); + } + + @HostListener('document:keydown.escape', ['$event']) + onKeydownHandler(evt: KeyboardEvent) { + this.close(); + } + + handleFileInput(files: FileList | File[]) { + + const uploadFiles: FileUploadModel[] = []; + for (let i = 0; i < files.length; i++) { + const file = files[i]; + uploadFiles.push({ + file: file, + progress: 0, + completed: false, + error: null + }) + } + + this._fileUploadService.uploadFiles(uploadFiles); + this._uploadStatusOverlayService.openStatusOverlay(); + this._dialogRef.detach(); + } +} diff --git a/apps/red-ui/src/app/upload/file-drop/service/file-drop-overlay.service.ts b/apps/red-ui/src/app/upload/file-drop/service/file-drop-overlay.service.ts new file mode 100644 index 000000000..8bee1601c --- /dev/null +++ b/apps/red-ui/src/app/upload/file-drop/service/file-drop-overlay.service.ts @@ -0,0 +1,69 @@ +import {Injectable, Injector} from "@angular/core"; +import {Overlay} from "@angular/cdk/overlay"; +import {FileDropComponent} from "../file-drop.component"; +import {ComponentPortal} from "@angular/cdk/portal"; +import {OverlayRef} from "@angular/cdk/overlay"; + +@Injectable({ + providedIn: 'root' +}) +export class FileDropOverlayService { + + private readonly _dropOverlayRef: OverlayRef; + + constructor(private overlay: Overlay, private readonly _injector: Injector) { + this._dropOverlayRef = this.overlay.create({ + height: '100vh', + width: '100vw', + }); + } + + dragListener = () => { + this.openFileDropOverlay(); + }; + mouseOut = e => { + if (e.toElement == null && e.relatedTarget == null) { + this.closeFileDropOverlay(); + } + }; + + initFileDropHandling() { + document + .getElementsByTagName('body')[0] + .addEventListener('dragenter', this.dragListener, false); + document.getElementsByTagName('body')[0].addEventListener('mouseout', this.mouseOut, false); + } + + cleanupFileDropHandling() { + document + .getElementsByTagName('body')[0] + .removeEventListener('dragenter', this.dragListener, false); + document + .getElementsByTagName('body')[0] + .removeEventListener('mouseout', this.mouseOut, false); + } + + + private _createInjector() { + return Injector.create({ + providers: [{provide: OverlayRef, useValue: this._dropOverlayRef}], + parent: this._injector, + }) + } + + openFileDropOverlay() { + const component = new ComponentPortal(FileDropComponent, null, this._createInjector()); + if (!this._dropOverlayRef.hasAttached()) { + this._dropOverlayRef.attach(component); + } + } + + closeFileDropOverlay() { + if (this._dropOverlayRef) { + this._dropOverlayRef.detach(); + } + } + + + +} diff --git a/apps/red-ui/src/app/upload/file-upload.module.ts b/apps/red-ui/src/app/upload/file-upload.module.ts new file mode 100644 index 000000000..4d6171c8f --- /dev/null +++ b/apps/red-ui/src/app/upload/file-upload.module.ts @@ -0,0 +1,32 @@ +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {MatIconModule} from '@angular/material/icon'; +import {MatListModule} from '@angular/material/list'; +import {MatProgressBarModule} from '@angular/material/progress-bar'; +import {MatTooltipModule} from '@angular/material/tooltip'; +import {FileDropComponent} from './file-drop/file-drop.component'; +import {OverlayModule} from '@angular/cdk/overlay'; +import {UploadStatusOverlay} from './upload-status-dialog/upload-status-overlay.component'; +import {NgxDropzoneModule} from "ngx-dropzone"; +import {TranslateModule} from "@ngx-translate/core"; +import {MatButtonModule} from "@angular/material/button"; + +@NgModule({ + imports: [ + CommonModule, + MatIconModule, + MatTooltipModule, + TranslateModule, + MatListModule, + NgxDropzoneModule, + MatProgressBarModule, + OverlayModule, + MatButtonModule, + ], + declarations: [FileDropComponent, UploadStatusOverlay], + providers: [], + entryComponents: [FileDropComponent, UploadStatusOverlay], + exports: [FileDropComponent, UploadStatusOverlay] +}) +export class FileUploadModule { +} diff --git a/apps/red-ui/src/app/upload/file-upload.service.ts b/apps/red-ui/src/app/upload/file-upload.service.ts new file mode 100644 index 000000000..e041b38a0 --- /dev/null +++ b/apps/red-ui/src/app/upload/file-upload.service.ts @@ -0,0 +1,47 @@ +import {Injectable} from "@angular/core"; +import {FileUploadModel} from "./model/file-upload.model"; +import {FileUploadControllerService} from "@redaction/red-ui-http"; +import {AppStateService} from "../state/app-state.service"; +import {HttpEventType} from "@angular/common/http"; + +@Injectable({ + providedIn: 'root' +}) +export class FileUploadService { + + files: FileUploadModel[] = []; + + constructor( + private readonly _appStateService: AppStateService, + private readonly _fileUploadControllerService: FileUploadControllerService, + ) { + } + + + uploadFiles(files: FileUploadModel[]) { + this.files.push(...files); + files.forEach(newFile => { + this._fileUploadControllerService.uploadFileForm(newFile.file, this._appStateService.activeProject.projectId, 'events', true).subscribe((event) => { + if (event.type === HttpEventType.UploadProgress) { + newFile.progress = (event.loaded / (event.total || event.loaded) * 100) | 0; + } + if (event.type === HttpEventType.Response) { + if (event.status < 300) { + newFile.progress = 100; + newFile.completed = true; + } else { + newFile.completed = true; + newFile.error = event.body; + } + } + }, (error) => { + newFile.completed = true; + newFile.error = error; + }); + }); + } + + stopAllUploads() { + + } +} diff --git a/apps/red-ui/src/app/upload/model/file-upload.model.ts b/apps/red-ui/src/app/upload/model/file-upload.model.ts new file mode 100644 index 000000000..da4775fc1 --- /dev/null +++ b/apps/red-ui/src/app/upload/model/file-upload.model.ts @@ -0,0 +1,8 @@ +export interface FileUploadModel { + + file: File; + progress: number; + completed: boolean; + error: any; + +} diff --git a/apps/red-ui/src/app/upload/upload-status-dialog/service/upload-status-overlay.service.ts b/apps/red-ui/src/app/upload/upload-status-dialog/service/upload-status-overlay.service.ts new file mode 100644 index 000000000..0c2f0455c --- /dev/null +++ b/apps/red-ui/src/app/upload/upload-status-dialog/service/upload-status-overlay.service.ts @@ -0,0 +1,41 @@ +import {Injectable, Injector} from "@angular/core"; +import {Overlay, OverlayRef} from "@angular/cdk/overlay"; +import {ComponentPortal} from "@angular/cdk/portal"; +import {UploadStatusOverlay} from "../upload-status-overlay.component"; + +@Injectable({ + providedIn: 'root' +}) +export class UploadStatusOverlayService { + + private readonly _statusOverlayRef: OverlayRef; + + constructor(private overlay: Overlay, private readonly _injector: Injector) { + this._statusOverlayRef = this.overlay.create({ + height: '500px', + width: '300px', + }); + } + + private _createInjector() { + return Injector.create({ + providers: [{provide: OverlayRef, useValue: this._statusOverlayRef}], + parent: this._injector, + }) + } + + openStatusOverlay() { + const component = new ComponentPortal(UploadStatusOverlay, null, this._createInjector()); + if (!this._statusOverlayRef.hasAttached()) { + this._statusOverlayRef.attach(component); + } + } + + closeStatusOverlay() { + if (this._statusOverlayRef) { + this._statusOverlayRef.detach(); + } + } + + +} diff --git a/apps/red-ui/src/app/upload/upload-status-dialog/upload-status-overlay.component.html b/apps/red-ui/src/app/upload/upload-status-dialog/upload-status-overlay.component.html new file mode 100644 index 000000000..4e197c996 --- /dev/null +++ b/apps/red-ui/src/app/upload/upload-status-dialog/upload-status-overlay.component.html @@ -0,0 +1,77 @@ +
+
+
+ {{ 'upload-status.dialog.title.label' | translate: {p1: uploadService.files.length} }} +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+ {{ model.file?.name }} +
+
+ {{ model.progress }}% +
+
+ +
+
+ +
+
+
+
+ {{ model.error.message }} +
+ +
+ +
+ key +
+
+ +
+
+
+
+ +
+
+
+
+
+ diff --git a/apps/red-ui/src/app/upload/upload-status-dialog/upload-status-overlay.component.scss b/apps/red-ui/src/app/upload/upload-status-dialog/upload-status-overlay.component.scss new file mode 100644 index 000000000..f236c08da --- /dev/null +++ b/apps/red-ui/src/app/upload/upload-status-dialog/upload-status-overlay.component.scss @@ -0,0 +1,91 @@ +@import "../../../assets/styles/red-variables"; + +section { + background: white; + position: fixed; + bottom: 10px; + right: 10px; + border: 2px solid $grey-1; +} + + +.upload-list { + max-height: 400px; + max-width: 400px; + overflow: auto; +} + +.red-upload-header { + display: flex; + flex-direction: row; + position: relative; + padding: 10px; + background: $grey-1; + color: $white; + width: 380px; + cursor: pointer; + + mat-icon { + color: $white; + } +} + +.collapse-icon { + padding-left: 8px; + + mat-icon { + width: 16px; + } +} + +.close-icon { + position: absolute; + right: 10px; + color: $white; +} + +.upload-list-item { + padding: 8px; + + mat-icon { + width: 16px; + height: 16px; + } + + .upload-line { + display: flex !important; + height: 20px; + position: relative; + justify-content: flex-end; + + .upload-file-name { + text-overflow: ellipsis; + overflow: hidden; + display: block; + white-space: nowrap; + padding-right: 50px; + + &.error { + color: $red-1; + padding-right: 60px; + } + } + + .upload-progress { + position: absolute; + right: 0; + width: 50px; + display: flex; + justify-content: space-evenly; + + &.error { + color: $red-1; + } + + &.ok { + color: $blue-1; + } + } + } + +} diff --git a/apps/red-ui/src/app/upload/upload-status-dialog/upload-status-overlay.component.ts b/apps/red-ui/src/app/upload/upload-status-dialog/upload-status-overlay.component.ts new file mode 100644 index 000000000..3613c0eaa --- /dev/null +++ b/apps/red-ui/src/app/upload/upload-status-dialog/upload-status-overlay.component.ts @@ -0,0 +1,39 @@ +import {Component, OnInit} from '@angular/core'; +import {FileUploadModel} from "../model/file-upload.model"; +import {FileUploadService} from "../file-upload.service"; +import {OverlayRef} from "@angular/cdk/overlay"; + +@Component({ + selector: 'redaction-upload-status-overlay', + templateUrl: './upload-status-overlay.component.html', + styleUrls: ['./upload-status-overlay.component.scss'] +}) +export class UploadStatusOverlay implements OnInit { + + collapsed: boolean = false; + + + constructor(public readonly uploadService: FileUploadService, private readonly _overlayRef: OverlayRef) { + } + + + ngOnInit() { + + } + + cancelItem(item: FileUploadModel) { + + } + + + uploadItem(item: FileUploadModel) { + item.completed = false; + item.error = null; + + } + + closeDialog() { + this.uploadService.stopAllUploads(); + this._overlayRef.detach(); + } +} diff --git a/apps/red-ui/src/app/utils/dialog-helper.ts b/apps/red-ui/src/app/utils/dialog-helper.ts new file mode 100644 index 000000000..8b3025220 --- /dev/null +++ b/apps/red-ui/src/app/utils/dialog-helper.ts @@ -0,0 +1,80 @@ +import { MatDialogConfig } from '@angular/material/dialog'; +import { Overlay, OverlayConfig } from '@angular/cdk/overlay'; + +export class DialogHelper { + public static _MARGIN = '16px'; + + public static generateDialogConfig(injectionData?: any | null): MatDialogConfig { + const viewPortWidth = window.innerWidth; + if (viewPortWidth < 600) { + return DialogHelper.generateFullScreenDialogConfig(injectionData); + } + + const conf = new MatDialogConfig(); + conf.position = { top: '100px' }; + conf.maxWidth = '90vw'; + conf.maxHeight = '88vh'; + if (injectionData) { + conf.data = injectionData; + } + return conf; + } + + public static generateMediumDialogConfig(injectionData?: any | null): MatDialogConfig { + const conf = new MatDialogConfig(); + conf.width = '60vw'; + conf.maxHeight = '88vh'; + conf.maxWidth = '90vw'; + if (injectionData) { + conf.data = injectionData; + } + return conf; + } + + public static generateLargeDialogConfig(injectionData?: any | null): MatDialogConfig { + const conf = new MatDialogConfig(); + conf.height = '90vh'; + conf.width = '90vw'; + conf.maxWidth = '90vw'; + if (injectionData) { + conf.data = injectionData; + } + return conf; + } + + public static generateFullScreenDialogConfig(injectionData?: any | null): MatDialogConfig { + const conf = new MatDialogConfig(); + conf.height = '100vh'; + conf.width = '100vw'; + conf.maxWidth = '100vw'; + if (injectionData) { + conf.data = injectionData; + } + return conf; + } + + public static generateFileDropConfig(overlay: Overlay): OverlayConfig { + const config = new OverlayConfig(); + config.hasBackdrop = true; + config.backdropClass = 'dark-backdrop'; + config.panelClass = 'gin-file-upload-dialog-panel'; + config.scrollStrategy = overlay.scrollStrategies.block(); + config.positionStrategy = overlay + .position() + .global() + .centerHorizontally() + .centerVertically(); + return config; + } + + public static generateUploadStatusConfig(overlay: Overlay): OverlayConfig { + const config = new OverlayConfig(); + config.hasBackdrop = false; + config.positionStrategy = overlay + .position() + .global() + .bottom(DialogHelper._MARGIN) + .right(DialogHelper._MARGIN); + return config; + } +} diff --git a/apps/red-ui/src/assets/config/config.json b/apps/red-ui/src/assets/config/config.json index 8fd33daf8..65a9f055a 100644 --- a/apps/red-ui/src/assets/config/config.json +++ b/apps/red-ui/src/assets/config/config.json @@ -1,5 +1,6 @@ { "OAUTH_URL": "https://keycloak-dev.iqser.cloud/auth/realms/dev", "OAUTH_CLIENT_ID": "gin-client", - "API_URL": "" + "API_URL": "", + "PDFTRON_LICENSE": "" } diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index 70d58431a..1a9223b18 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -1,4 +1,28 @@ { + "upload-status": { + "dialog": { + "title": { + "label": "File Upload" + }, + "actions": { + "re-upload": { + "label": "Retry Upload" + }, + "cancel": { + "label": "Cancel Upload" + } + } + } + }, + "pdf-viewer": { + "text-popup": { + "actions": { + "suggestion-redaction": { + "label": "Suggest Redaction" + } + } + } + }, "common": { "dialog": { "close": { diff --git a/apps/red-ui/src/assets/icons/general/add.svg b/apps/red-ui/src/assets/icons/general/add.svg new file mode 100644 index 000000000..b38938947 --- /dev/null +++ b/apps/red-ui/src/assets/icons/general/add.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/red-ui/src/assets/icons/general/arrow_down.svg b/apps/red-ui/src/assets/icons/general/arrow_down.svg new file mode 100755 index 000000000..815c3b0c6 --- /dev/null +++ b/apps/red-ui/src/assets/icons/general/arrow_down.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/apps/red-ui/src/assets/icons/general/arrow_up.svg b/apps/red-ui/src/assets/icons/general/arrow_up.svg new file mode 100644 index 000000000..27d76d583 --- /dev/null +++ b/apps/red-ui/src/assets/icons/general/arrow_up.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/apps/red-ui/src/assets/icons/general/check_icon.svg b/apps/red-ui/src/assets/icons/general/check_icon.svg new file mode 100755 index 000000000..37da65aad --- /dev/null +++ b/apps/red-ui/src/assets/icons/general/check_icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/apps/red-ui/src/assets/icons/general/error_icon.svg b/apps/red-ui/src/assets/icons/general/error_icon.svg new file mode 100755 index 000000000..98251e489 --- /dev/null +++ b/apps/red-ui/src/assets/icons/general/error_icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/apps/red-ui/src/assets/keycloak/silent-check-sso.html b/apps/red-ui/src/assets/keycloak/silent-check-sso.html deleted file mode 100644 index 9de338b30..000000000 --- a/apps/red-ui/src/assets/keycloak/silent-check-sso.html +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/apps/red-ui/src/assets/oauth/silent-refresh.html b/apps/red-ui/src/assets/oauth/silent-refresh.html new file mode 100644 index 000000000..7c0ea26f4 --- /dev/null +++ b/apps/red-ui/src/assets/oauth/silent-refresh.html @@ -0,0 +1,7 @@ + + + + + diff --git a/apps/red-ui/src/assets/styles/red-material-theme.scss b/apps/red-ui/src/assets/styles/red-material-theme.scss index edace0304..042d3e34a 100644 --- a/apps/red-ui/src/assets/styles/red-material-theme.scss +++ b/apps/red-ui/src/assets/styles/red-material-theme.scss @@ -5,8 +5,8 @@ $grey-1-palette: ( default: $grey-1, - lighter: lighten($grey-1, 15%), - darker: darken($grey-1, 15%), + lighter: lighten($grey-1, 30%), + darker: darken($grey-1, 30%), text: $grey-1, contrast: ( default: $white, @@ -17,8 +17,8 @@ $grey-1-palette: ( $grey-2-palette: ( default: $grey-2, - lighter: lighten($grey-2, 15%), - darker: darken($grey-2, 15%), + lighter: lighten($grey-2, 30%), + darker: darken($grey-2, 30%), text: $grey-2, contrast: ( default: $grey-1, @@ -29,8 +29,8 @@ $grey-2-palette: ( $red-palette: ( default: $red-1, - lighter: lighten($red-1, 15%), - darker: darken($red-1, 15%), + lighter: lighten($red-1, 30%), + darker: darken($red-1, 30%), text: $red-1, contrast: ( default: $white, diff --git a/docker/red-ui/docker-entrypoint.sh b/docker/red-ui/docker-entrypoint.sh index 1414c7130..6c55912cd 100755 --- a/docker/red-ui/docker-entrypoint.sh +++ b/docker/red-ui/docker-entrypoint.sh @@ -1,14 +1,14 @@ #!/bin/sh -API_CLIENT="${API_CLIENT:-gin-client}" -KEYCLOAK_URL="${KEYCLOAK_URL:-https://keycloak-dev.iqser.cloud/auth}" -KEYCLOAK_REALM="${KEYCLOAK_REALM:-dev}" +OAUTH_CLIENT_ID="${OAUTH_CLIENT_ID:-gin-client}" +OAUTH_URL="${OAUTH_URL:-https://keycloak-dev.iqser.cloud/auth/realms/dev}" API_URL="${API_URL:-}" +PDFTRON_LICENSE="${API_URL:-}" echo '{ - "API_CLIENT":"'"$API_CLIENT"'", - "KEYCLOAK_URL":"'"$KEYCLOAK_URL"'", - "KEYCLOAK_REALM":"'"$KEYCLOAK_REALM"'", + "OAUTH_CLIENT_ID":"'"$OAUTH_CLIENT_ID"'", + "OAUTH_URL":"'"$OAUTH_URL"'", + "PDFTRON_LICENSE":"'"$PDFTRON_LICENSE"'", "API_URL":"'"$API_URL"'" }' > /usr/share/nginx/html/assets/config/config.json diff --git a/package.json b/package.json index ac3027d6d..af64b783c 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "private": true, "dependencies": { "@angular/animations": "^10.0.0", - "@angular/cdk": "^10.2.1", + "@angular/cdk": "^10.2.3", "@angular/common": "^10.0.0", "@angular/core": "^10.0.0", "@angular/forms": "^10.0.0", @@ -45,8 +45,7 @@ "angular-oauth2-oidc": "^10.0.3", "angular-oauth2-oidc-jwks": "^9.0.0", "file-saver": "^2.0.2", - "keycloak-angular": "^8.0.1", - "keycloak-js": "10.0.2", + "ng2-file-upload": "^1.4.0", "ngp-sort-pipe": "^0.0.4", "ngx-dropzone": "^2.2.2", "ngx-toastr": "^13.0.0", diff --git a/yarn.lock b/yarn.lock index 71a4847fb..9019d5655 100644 --- a/yarn.lock +++ b/yarn.lock @@ -182,10 +182,10 @@ dependencies: tslib "^2.0.0" -"@angular/cdk@^10.2.1": - version "10.2.1" - resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-10.2.1.tgz#7ca0fe7220ca97bdf8e73a40105212749427a88f" - integrity sha512-67nPfteerlGPlwBeKt+EEOgEo2zm3+U6FzwhXVqwEeBxCZ7PWelMDiartHC7zZnzIKkcNEO0Uptq7Bzl2gdU0w== +"@angular/cdk@^10.2.3": + version "10.2.3" + resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-10.2.3.tgz#49eb09ae68f91f1ecec388592091a0f8f1dd5dce" + integrity sha512-ne3uSnWLQyUfYQ32zTvDauudyPONRPPBSbdOzFSsvFQuPxUcMQ3mFHJuq2OXei47TfSatmmyuKSuw9EtmTRbQw== dependencies: tslib "^2.0.0" optionalDependencies: @@ -7714,6 +7714,13 @@ ng-packagr@^10.1.2: stylus "^0.54.7" terser "^5.0.0" +ng2-file-upload@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ng2-file-upload/-/ng2-file-upload-1.4.0.tgz#8dea28d573234c52af474ad2a4001b335271e5c4" + integrity sha512-3J/KPU/tyh/ad6TFeUbrxX+SihUj0iOEt2Zsg4EX7mB3GFiQscXOfcUOxCkBtPWWWaqt3azrYbVGzsYa3/7NzQ== + dependencies: + tslib "^1.9.0" + ngp-sort-pipe@^0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/ngp-sort-pipe/-/ngp-sort-pipe-0.0.4.tgz#9d70caff0ee34f32f2ea1df16c7333b72df72b56"