diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1d8c39d..e8ed7d7 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -3334,21 +3334,6 @@ "dev": true, "optional": true }, - "angular-file-uploader": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/angular-file-uploader/-/angular-file-uploader-7.0.3.tgz", - "integrity": "sha512-0wkr/3Vn3toG/2sTSiD5vJ5EK3HKYXwzyFd3M3hRegcHhQnagN34licn4r+CBWJQQED5aRWUVB9OWOCtuNwimw==", - "requires": { - "tslib": "^2.0.0" - }, - "dependencies": { - "tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" - } - } - }, "ansi-colors": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", diff --git a/frontend/package.json b/frontend/package.json index 84fee1d..a3bca3c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,7 +26,6 @@ "@angular/platform-browser-dynamic": "~9.1.9", "@angular/router": "~9.1.9", "@types/uuid": "8.3.0", - "angular-file-uploader": "^7.0.3", "bootstrap": "~4.3.1", "jquery": "3.5.1", "ngx-logger": "4.1.9", diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 8b4153a..ab15618 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -21,39 +21,40 @@ import {MatTabsModule} from '@angular/material/tabs'; import {ImagesComponent} from './component/images/images.component'; import {MapComponent} from './component/map/map.component'; import {AgmCoreModule} from '@agm/core'; -import {AngularFileUploaderModule} from 'angular-file-uploader'; import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'; +import { FileUploaderComponent } from './component/file-uploader/file-uploader.component'; +import {MatSnackBarModule} from '@angular/material/snack-bar'; @NgModule({ - declarations: [LandingComponent, ImagesComponent, MapComponent], - imports: [ - ReactiveFormsModule, - BrowserModule, - BrowserAnimationsModule, - LoggerModule.forRoot({level: environment.log_level, serverLogLevel: NgxLoggerLevel.ERROR}), - HttpClientModule, - MatFormFieldModule, - FormsModule, - MatButtonModule, - MatInputModule, - MatSlideToggleModule, - MatListModule, - MatExpansionModule, - MatCardModule, - MatPaginatorModule, - MatTableModule, - MatTabsModule, - // GoogleMapsModule, - AgmCoreModule.forRoot({ - apiKey: 'AIzaSyBDgLJ1sL48IQg7e5jrwmUyXkqeEfVnf78' - /* apiKey is required, unless you are a - premium customer, in which case you can - use clientId - */ - }), - AngularFileUploaderModule, - MatProgressSpinnerModule, - ], + declarations: [LandingComponent, ImagesComponent, MapComponent, FileUploaderComponent], + imports: [ + ReactiveFormsModule, + BrowserModule, + BrowserAnimationsModule, + LoggerModule.forRoot({level: environment.log_level, serverLogLevel: NgxLoggerLevel.ERROR}), + HttpClientModule, + MatFormFieldModule, + FormsModule, + MatButtonModule, + MatInputModule, + MatSlideToggleModule, + MatListModule, + MatExpansionModule, + MatCardModule, + MatPaginatorModule, + MatTableModule, + MatTabsModule, + MatSnackBarModule, + // GoogleMapsModule, + AgmCoreModule.forRoot({ + apiKey: 'AIzaSyBDgLJ1sL48IQg7e5jrwmUyXkqeEfVnf78' + /* apiKey is required, unless you are a + premium customer, in which case you can + use clientId + */ + }), + MatProgressSpinnerModule, + ], // enables injecting providers: [ RestService, diff --git a/frontend/src/app/component/file-uploader/file-uploader.component.css b/frontend/src/app/component/file-uploader/file-uploader.component.css new file mode 100644 index 0000000..b600584 --- /dev/null +++ b/frontend/src/app/component/file-uploader/file-uploader.component.css @@ -0,0 +1,3 @@ +.margin-right { + margin-right: 1em; +} diff --git a/frontend/src/app/component/file-uploader/file-uploader.component.html b/frontend/src/app/component/file-uploader/file-uploader.component.html new file mode 100644 index 0000000..e1318c8 --- /dev/null +++ b/frontend/src/app/component/file-uploader/file-uploader.component.html @@ -0,0 +1,57 @@ + + + + + + Tag + + + + + Name + + + + + Longitude + + + + + Longitude + + + + +
+ + {{currentFileName}} + diff --git a/frontend/src/app/component/file-uploader/file-uploader.component.ts b/frontend/src/app/component/file-uploader/file-uploader.component.ts new file mode 100644 index 0000000..44def82 --- /dev/null +++ b/frontend/src/app/component/file-uploader/file-uploader.component.ts @@ -0,0 +1,143 @@ +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {RestService} from '../../services/rest.service'; +import {NGXLogger} from 'ngx-logger'; +import {MatSnackBar} from '@angular/material/snack-bar'; +import {ImageMetadata} from '../../interfaces/interface'; + +@Component({ + selector: 'app-file-uploader', + templateUrl: './file-uploader.component.html', + styleUrls: ['./file-uploader.component.css'] +}) +export class FileUploaderComponent implements OnInit { + + constructor(private restService: RestService, private logger: NGXLogger, private snackBar: MatSnackBar) { + } + + @Input() + mode: string; // post, put + @Input() + filename: string; // if mode==put && without file ending + @Input() + meta: ImageMetadata; + + @Output() + reload: EventEmitter = new EventEmitter(); + + currentFileName: string; + + id: string; + + file: ArrayBuffer; + tag: string; + longitude: string; + latitude: string; + name: string; + + private buildForm(): FormData { + const mainForm: FormData = new FormData(); + const metaDict: {} = {}; + + if (this.filename) { + this.id = this.filename; + } else { + this.id = this.currentFileName; + } + + metaDict['filename'] = this.currentFileName; + + if (this.mode === 'post') { + if (this.longitude && this.latitude) { + metaDict['longitude'] = Number.parseFloat(this.longitude); + metaDict['latitude'] = Number.parseFloat(this.latitude); + } else if (this.meta.longitude && this.meta.latitude) { + metaDict['longitude'] = this.meta.longitude; + metaDict['latitude'] = this.meta.latitude; + } else { + throw new Error('Long/Lat necessary if new file!'); + } + } + + if (this.name) { + metaDict['name'] = this.name; + } else if (this.meta.name) { + metaDict['name'] = this.meta.name; + } else { + if (this.mode === 'post') { + throw new Error('Name necessary if new file'); + } + } + + if (this.tag) { + metaDict['tag'] = this.tag; + } else if (this.meta.tag) { + metaDict['tag'] = this.meta.tag; + } + + metaDict['datetime'] = new Date().toISOString(); + + mainForm.append('id', this.id.split('.')[0]); + mainForm.append('metadata', JSON.stringify(metaDict)); + + if (this.file) { + let binary = ''; + const bytes = new Uint8Array( this.file ); + const len = bytes.byteLength; + for (let i = 0; i < len; i++) { + binary += String.fromCharCode( bytes[ i ] ); + } + mainForm.append('image_data', window.btoa( binary )); + } else { + throw new Error('No file to upload!'); + } + + return mainForm; + } + + ngOnInit(): void { + } + + onFileSelected(fileEvent: Event) { + this.logger.debug('fileEvent', fileEvent); + const target: HTMLInputElement = fileEvent.target as HTMLInputElement; + + if (typeof (FileReader) !== 'undefined') { + const reader = new FileReader(); + const file: File = target.files[0]; + + reader.onload = (e: any) => { + this.file = e.target.result; + this.currentFileName = file.name; + this.logger.debug('Selected file', this.currentFileName, this.file); + }; + + reader.readAsArrayBuffer(file); + } + } + + async postFile() { + try { + const formData = this.buildForm(); + this.logger.debug('Build form data', formData); + + let response; + if (this.mode === 'post') { + response = await this.restService.postImage(formData).toPromise(); + } else if (this.mode === 'put') { + response = await this.restService.updateImage(this.id, formData).toPromise(); + } else { + // noinspection ExceptionCaughtLocallyJS + throw new Error('Unknown mode: ' + this.mode); + } + + this.logger.debug('Response', response); + + this.snackBar.open('Upload successful', 'Dismiss'); + this.reload.emit(this.currentFileName); + } catch (err) { + this.logger.error('error', err); + this.snackBar.open(err.error.Error, 'Dismiss'); + } + } + +} diff --git a/frontend/src/app/component/images/images.component.html b/frontend/src/app/component/images/images.component.html index 06b0789..e4f9976 100644 --- a/frontend/src/app/component/images/images.component.html +++ b/frontend/src/app/component/images/images.component.html @@ -8,7 +8,7 @@ + style="width: 100%; margin: auto" [alt]="image.filename"/> - - + - + @@ -48,7 +52,7 @@ showFirstLastButtons > +

Upload New Image

- - + + diff --git a/frontend/src/app/component/images/images.component.ts b/frontend/src/app/component/images/images.component.ts index 7ae33c2..55cd96d 100644 --- a/frontend/src/app/component/images/images.component.ts +++ b/frontend/src/app/component/images/images.component.ts @@ -1,9 +1,9 @@ -import {AfterViewInit, Component, Input, OnInit} from '@angular/core'; +import {AfterViewInit, Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; import {PageEvent} from '@angular/material/paginator'; import {ImageMetadata} from '../../interfaces/interface'; import {RestService} from '../../services/rest.service'; import {NGXLogger} from 'ngx-logger'; -import {AngularFileUploaderConfig} from 'angular-file-uploader'; +import {MatSnackBar} from '@angular/material/snack-bar'; @Component({ selector: 'app-images', @@ -14,19 +14,16 @@ export class ImagesComponent implements OnInit, AfterViewInit { @Input() images: ImageMetadata[]; + @Output() + reload: EventEmitter = new EventEmitter(); pageSizeOptions: number[] = [5, 10, 25, 100]; lastPageEvent: PageEvent; - imageUploadConfig: AngularFileUploaderConfig = { - uploadAPI: { - url: this.restService.getPostImageUrl() - } - }; - constructor( public restService: RestService, private logger: NGXLogger, + private snackbar: MatSnackBar ) { } @@ -46,24 +43,18 @@ export class ImagesComponent implements OnInit, AfterViewInit { return this.paginationGetStart() + this.lastPageEvent.pageSize; } - getImageUpdateConfig(filename: string): AngularFileUploaderConfig { - return { - uploadAPI: { - url: this.restService.getUpdateImageUrl(filename), - method: 'PUT' - } - }; - } - deleteImage(image: ImageMetadata) { - this.restService.deleteImage(image.filename).toPromise().then(() => { + this.restService.deleteImage(image.filename.split('.')[0]).toPromise().then(() => { this.images = this.images.filter(im => im !== image); + this.snackbar.open('Successfully deleted ' + image.filename, 'Dismiss'); + this.reload.emit(); }).catch(err => this.logger.error(err)); } loadImage(i: number, id: string): void { this.restService.getImage(id).toPromise().then(image => { this.images[i].image_b64 = 'data:image/jpg;base64,' + image.image_data; + this.logger.debug('image_b64', i, id, this.images[i].image_b64); }).catch(err => { this.logger.error('loadImage', i, id, err); }); diff --git a/frontend/src/app/component/landing/landing.component.html b/frontend/src/app/component/landing/landing.component.html index f665cae..a1f46d5 100644 --- a/frontend/src/app/component/landing/landing.component.html +++ b/frontend/src/app/component/landing/landing.component.html @@ -6,11 +6,12 @@ - + - + +No images found diff --git a/frontend/src/app/component/map/map.component.css b/frontend/src/app/component/map/map.component.css index 55199f0..d71a3d6 100644 --- a/frontend/src/app/component/map/map.component.css +++ b/frontend/src/app/component/map/map.component.css @@ -1 +1,3 @@ -agm-map { height: 50vh; /* height is required */ } +agm-map { + height: 50vh; /* height is required */ +} diff --git a/frontend/src/app/component/map/map.component.html b/frontend/src/app/component/map/map.component.html index cf00c2e..acce0be 100644 --- a/frontend/src/app/component/map/map.component.html +++ b/frontend/src/app/component/map/map.component.html @@ -1,9 +1,9 @@

Map

+ [latitude]="latitude" + [longitude]="longitude" + [zoom]="2"> { - return this.http.get(this.getImageUrl + '/' + id).pipe( + public getImage(id: string): Observable { + return this.http.get(this.getImageUrl + '/' + id).pipe( tap(next => this.logger.debug('getImage', id, next)) ); } @@ -61,26 +62,26 @@ export class RestService { ); } - // public postImage(id: string, image: any): Observable { - // return this.http.post(this.postImageUrl + '/' + id, image).pipe( - // tap(next => this.logger.debug('postImage', id, image, next)) - // ); - // } - - public getPostImageUrl(): string { - return this.postImageUrl; + public postImage(imageForm: FormData): Observable { + return this.http.post(this.postImageUrl, imageForm).pipe( + tap(next => this.logger.debug('postImage', imageForm, next)) + ); } - // public updateImage(id: string, image: any): Observable { - // return this.http.put(this.updateImageUrl + '/' + id, image).pipe( - // tap(next => this.logger.debug('updateImage', id, image, next)) - // ); + // public getPostImageUrl(): string { + // return this.postImageUrl; // } - public getUpdateImageUrl(filenameWithExtension: string): string { - return this.updateImageUrl + '/' + RestService.stripUploadFileName(filenameWithExtension); + public updateImage(id: string, imageForm: FormData): Observable { + return this.http.put(this.updateImageUrl + '/' + id, imageForm).pipe( + tap(next => this.logger.debug('updateImage', id, imageForm, next)) + ); } + // public getUpdateImageUrl(filenameWithExtension: string): string { + // return this.updateImageUrl + '/' + RestService.stripUploadFileName(filenameWithExtension); + // } + public healthCheck(): Observable { return this.http.get(this.healthCheckUrl).pipe( tap(next => this.logger.debug('healthCheck', next))