From 962b1d39afe8a26dadfa356f33f7c3b101e34a52 Mon Sep 17 00:00:00 2001 From: Timo Date: Wed, 2 Dec 2020 19:22:16 +0200 Subject: [PATCH] min word length fixed to 2 --- .../dictionary-overview-screen.component.ts | 26 +++-- .../project-overview-screen.component.ts | 7 +- .../upload/file-drop/file-drop.component.html | 2 +- .../upload/file-drop/file-drop.component.ts | 9 +- .../service/file-drop-overlay.service.ts | 37 +++--- .../src/app/upload/file-upload.service.ts | 109 ++++++++++++++---- .../src/app/upload/model/file-upload.model.ts | 1 + .../upload-status-overlay.component.ts | 15 ++- apps/red-ui/src/assets/config/config.json | 2 +- yarn.lock | 8 +- 10 files changed, 158 insertions(+), 58 deletions(-) diff --git a/apps/red-ui/src/app/screens/admin/dictionary-overview-screen/dictionary-overview-screen.component.ts b/apps/red-ui/src/app/screens/admin/dictionary-overview-screen/dictionary-overview-screen.component.ts index b13bc4952..3c99c8f66 100644 --- a/apps/red-ui/src/app/screens/admin/dictionary-overview-screen/dictionary-overview-screen.component.ts +++ b/apps/red-ui/src/app/screens/admin/dictionary-overview-screen/dictionary-overview-screen.component.ts @@ -17,6 +17,8 @@ declare var ace; styleUrls: ['./dictionary-overview-screen.component.scss'] }) export class DictionaryOverviewScreenComponent { + static readonly MIN_WORD_LENGTH: number = 2; + @ViewChild('editorComponent') editorComponent: AceEditorComponent; @@ -136,12 +138,12 @@ export class DictionaryOverviewScreenComponent { const Range = ace.acequire('ace/range').Range; for (const i of this.changedLines) { - let entry = this.currentDictionaryEntries[i]; + const entry = this.currentDictionaryEntries[i]; if (entry?.trim().length > 0) { // only mark non-empty lines this.activeEditMarkers.push(this.editorComponent.getEditor().getSession().addMarker(new Range(i, 0, i, 1), 'changed-row-marker', 'fullLine')); } - if (entry?.trim().length > 0 && entry.trim().length < 3) { + if (entry?.trim().length > 0 && entry.trim().length < DictionaryOverviewScreenComponent.MIN_WORD_LENGTH) { // show lines that are to short this.activeEditMarkers.push(this.editorComponent.getEditor().getSession().addMarker(new Range(i, 0, i, 1), 'to-short-marker', 'fullLine')); } @@ -167,15 +169,21 @@ export class DictionaryOverviewScreenComponent { }); // remove empty lines - entriesToAdd = entriesToAdd.filter((e) => e && e.trim().length > 0); + entriesToAdd = entriesToAdd.filter((e) => e && e.trim().length > 0).map((e) => e.trim()); - if (entriesToAdd.length > 0) { - await this._dictionaryControllerService.addEntry(entriesToAdd, this.dictionary.type).toPromise(); + const invalidRowsExist = entriesToAdd.filter((e) => e.length < DictionaryOverviewScreenComponent.MIN_WORD_LENGTH); + + if (invalidRowsExist.length === 0) { + if (entriesToAdd.length > 0) { + await this._dictionaryControllerService.addEntry(entriesToAdd, this.dictionary.type).toPromise(); + } + if (entriesToDelete.length > 0) { + await this._dictionaryControllerService.deleteEntries(entriesToDelete, this.dictionary.type).toPromise(); + } + this._initialize(); + } else { + console.log('Invalid ROW', invalidRowsExist); } - if (entriesToDelete.length > 0) { - await this._dictionaryControllerService.deleteEntries(entriesToDelete, this.dictionary.type).toPromise(); - } - this._initialize(); // .ace_marker-layer .search-marker } diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts index 8ea6a0c19..da1225915 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts +++ b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { ChangeDetectorRef, Component, HostListener, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { NotificationService, NotificationType } from '../../notification/notification.service'; import { AppStateService } from '../../state/app-state.service'; @@ -38,6 +38,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { public needsWorkFilters: FilterModel[]; public collapsedDetails = false; public searchForm: FormGroup; + showFileDrop: boolean; displayedFiles: FileStatusWrapper[] = []; @@ -188,12 +189,14 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { file: file, progress: 0, completed: false, - error: null + error: null, + retryCount: 0 }); } this._fileUploadService.uploadFiles(uploadFiles); this._uploadStatusOverlayService.openStatusOverlay(); + this._changeDetectorRef.detectChanges(); } private _computeAllFilters() { 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 index d0a1e4f5c..6d9ef7a49 100644 --- 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 @@ -1,4 +1,4 @@ -
+
{{ 'project-overview.upload-files' | translate }} 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 index fef8b4f6e..e16a99dd5 100644 --- 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 @@ -1,4 +1,4 @@ -import { Component, HostListener, OnInit } from '@angular/core'; +import { ChangeDetectorRef, 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'; @@ -13,7 +13,8 @@ export class FileDropComponent implements OnInit { constructor( private readonly _dialogRef: OverlayRef, private readonly _fileUploadService: FileUploadService, - private _uploadStatusOverlayService: UploadStatusOverlayService + private readonly _changeDetectorRef: ChangeDetectorRef, + private readonly _uploadStatusOverlayService: UploadStatusOverlayService ) {} ngOnInit() {} @@ -35,12 +36,14 @@ export class FileDropComponent implements OnInit { file: file, progress: 0, completed: false, - error: null + error: null, + retryCount: 0 }); } this._fileUploadService.uploadFiles(uploadFiles); this._uploadStatusOverlayService.openStatusOverlay(); this._dialogRef.detach(); + this._changeDetectorRef.detectChanges(); } } 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 index c72466455..61db1e790 100644 --- 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 @@ -1,13 +1,13 @@ import { Injectable, Injector } from '@angular/core'; -import { Overlay } from '@angular/cdk/overlay'; +import { Overlay, OverlayRef } 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 _mouseIn: boolean = false; private readonly _dropOverlayRef: OverlayRef; constructor(private overlay: Overlay, private readonly _injector: Injector) { @@ -17,29 +17,40 @@ export class FileDropOverlayService { }); } - dragListener = () => { - this.openFileDropOverlay(); + dragListener = (e) => { + e.preventDefault(); + e.stopPropagation(); + if (this._mouseIn) { + this.openFileDropOverlay(); + } + return false; + }; + mouseIn = (e) => { + e.preventDefault(); + e.stopPropagation(); + this._mouseIn = true; + return false; }; mouseOut = (e) => { + e.preventDefault(); + e.stopPropagation(); if (e.toElement == null && e.relatedTarget == null) { + this._mouseIn = false; this.closeFileDropOverlay(); } + return false; }; initFileDropHandling() { - document - .getElementsByTagName('body')[0] - .addEventListener('dragenter', this.dragListener, false); + document.getElementsByTagName('body')[0].addEventListener('dragenter', this.dragListener, false); + document.getElementsByTagName('body')[0].addEventListener('mouseenter', this.mouseIn, 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); + document.getElementsByTagName('body')[0].removeEventListener('dragenter', this.dragListener, false); + document.getElementsByTagName('body')[0].removeEventListener('mouseenter', this.mouseIn, false); + document.getElementsByTagName('body')[0].removeEventListener('mouseout', this.mouseOut, false); } private _createInjector() { diff --git a/apps/red-ui/src/app/upload/file-upload.service.ts b/apps/red-ui/src/app/upload/file-upload.service.ts index 037d86c04..8705acdf4 100644 --- a/apps/red-ui/src/app/upload/file-upload.service.ts +++ b/apps/red-ui/src/app/upload/file-upload.service.ts @@ -3,43 +3,108 @@ import { FileUploadModel } from './model/file-upload.model'; import { AppStateService } from '../state/app-state.service'; import { HttpEventType } from '@angular/common/http'; import { FileManagementControllerService } from '@redaction/red-ui-http'; +import { interval, Subscription } from 'rxjs'; + +export interface UploadFile { + fileUploadModel: FileUploadModel; +} @Injectable({ providedIn: 'root' }) export class FileUploadService { + static readonly MAX_PARALLEL_UPLOADS = 5; files: FileUploadModel[] = []; - constructor(private readonly _appStateService: AppStateService, private readonly _fileManagementControllerService: FileManagementControllerService) {} + activeUploads: number = 0; + private _pendingUploads: UploadFile[] = []; + private _activeUploads: Subscription[] = []; + + constructor(private readonly _appStateService: AppStateService, private readonly _fileManagementControllerService: FileManagementControllerService) { + interval(2500).subscribe((val) => { + this._handleUploads(); + }); + } + + scheduleUpload(item: FileUploadModel) { + this._pendingUploads.push({ + fileUploadModel: item + }); + } uploadFiles(files: FileUploadModel[]) { this.files.push(...files); files.forEach((newFile) => { - this._fileManagementControllerService.uploadFileForm(newFile.file, this._appStateService.activeProject.project.projectId, 'events', true).subscribe( - async (event) => { - if (event.type === HttpEventType.UploadProgress) { - newFile.progress = Math.round((event.loaded / (event.total || event.loaded)) * 100); - } - if (event.type === HttpEventType.Response) { - if (event.status < 300) { - newFile.progress = 100; - newFile.completed = true; - } else { - newFile.completed = true; - newFile.error = event.body; - } - await this._appStateService.reloadActiveProjectFiles(); - } - }, - (error) => { - newFile.completed = true; - newFile.error = error; - } - ); + this.scheduleUpload(newFile); }); } stopAllUploads() { this.files = []; } + + private _handleUploads() { + if (this._activeUploads.length < FileUploadService.MAX_PARALLEL_UPLOADS && this._pendingUploads.length > 0) { + let cnt = FileUploadService.MAX_PARALLEL_UPLOADS - this._activeUploads.length; + while (cnt > 0) { + cnt--; + const popped = this._pendingUploads.pop(); + if (popped) { + const sub = this._createSubscription(popped); + this._activeUploads.push(sub); + } else { + return; + } + } + } + } + + private _createSubscription(uploadFile: UploadFile) { + this.activeUploads++; + const obs = this._fileManagementControllerService.uploadFileForm( + uploadFile.fileUploadModel.file, + this._appStateService.activeProject.project.projectId, + 'events', + true + ); + const subscription = obs.subscribe( + async (event) => { + if (event.type === HttpEventType.UploadProgress) { + uploadFile.fileUploadModel.progress = Math.round((event.loaded / (event.total || event.loaded)) * 100); + } + if (event.type === HttpEventType.Response) { + if (event.status < 300) { + uploadFile.fileUploadModel.progress = 100; + uploadFile.fileUploadModel.completed = true; + } else { + uploadFile.fileUploadModel.completed = true; + uploadFile.fileUploadModel.error = event.body; + } + this._removeUpload(subscription); + await this._appStateService.reloadActiveProjectFiles(); + } + }, + (error) => { + uploadFile.fileUploadModel.completed = true; + uploadFile.fileUploadModel.error = error; + this._removeUpload(subscription); + } + ); + return subscription; + } + + private _removeUpload(subscription: Subscription) { + const index = this._activeUploads.indexOf(subscription); + if (index > -1) { + this._activeUploads.splice(index, 1); + this.activeUploads--; + } + } + + removeFile(item: FileUploadModel) { + const index = this.files.indexOf(item); + if (index > -1) { + this.files.splice(index, 1); + } + } } 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 index 9bd8958c6..d46e2af92 100644 --- a/apps/red-ui/src/app/upload/model/file-upload.model.ts +++ b/apps/red-ui/src/app/upload/model/file-upload.model.ts @@ -3,4 +3,5 @@ export interface FileUploadModel { progress: number; completed: boolean; error: any; + retryCount: number; } 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 index 23d886df6..28fc05782 100644 --- 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 @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { FileUploadModel } from '../model/file-upload.model'; import { FileUploadService } from '../file-upload.service'; import { OverlayRef } from '@angular/cdk/overlay'; @@ -13,7 +13,11 @@ export class UploadStatusOverlay implements OnInit { uploadStatusInterval: number; - constructor(public readonly uploadService: FileUploadService, private readonly _overlayRef: OverlayRef) {} + constructor( + public readonly uploadService: FileUploadService, + private readonly _overlayRef: OverlayRef, + private readonly _changeDetectorRef: ChangeDetectorRef + ) {} ngOnInit() { this.uploadStatusInterval = setInterval(() => { @@ -27,11 +31,16 @@ export class UploadStatusOverlay implements OnInit { }, 2500); } - cancelItem(item: FileUploadModel) {} + cancelItem(item: FileUploadModel) { + this.uploadService.removeFile(item); + } uploadItem(item: FileUploadModel) { + item.progress = 0; item.completed = false; item.error = null; + this._changeDetectorRef.detectChanges(); + this.uploadService.scheduleUpload(item); } closeDialog() { diff --git a/apps/red-ui/src/assets/config/config.json b/apps/red-ui/src/assets/config/config.json index bfcb40971..d447e7e9e 100644 --- a/apps/red-ui/src/assets/config/config.json +++ b/apps/red-ui/src/assets/config/config.json @@ -1,5 +1,5 @@ { "OAUTH_URL": "https://redkc-staging.iqser.cloud/auth/realms/redaction", "OAUTH_CLIENT_ID": "redaction", - "API_URL": "https://timo-redaction-dev.iqser.cloud" + "API_URL": "https://redapi-staging.iqser.cloud" } diff --git a/yarn.lock b/yarn.lock index ed976c729..05d398df8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8476,10 +8476,10 @@ ngx-color-picker@^10.1.0: dependencies: tslib "^2.0.0" -ngx-dropzone@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/ngx-dropzone/-/ngx-dropzone-2.2.2.tgz#450609031efb909461181c6b693d7c5f793a278d" - integrity sha512-REuBcPNTY33OtcZD6dw8Fq/jFqwviiYydYaCn74yig8TvDDYhtJ/BPjtvbw7uEgF4BJr/UEHbKMvkdZIyCgsww== +ngx-dropzone@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/ngx-dropzone/-/ngx-dropzone-2.3.0.tgz#fb34bceb0d8ad3c00c314b957d095018d15c5fe9" + integrity sha512-Mhsu6MW1yJRxLPRPbwal7y18trokU9josTD8GDb8wFRmaCd/N+dJeMDXn0XsxtDhlZ7vWqbpEr9k0ej4Na3v8Q== dependencies: tslib "^1.9.0"