implemented post und put image;

This commit is contained in:
Marco Zeisler 2021-01-15 23:39:12 +01:00
parent bc12a086f5
commit a54fc5eb01
13 changed files with 290 additions and 97 deletions

View File

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

View File

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

View File

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

View File

@ -0,0 +1,3 @@
.margin-right {
margin-right: 1em;
}

View File

@ -0,0 +1,57 @@
<!--suppress ALL -->
<button type="button"
mat-raised-button
(click)="fileInput.click()"
class="margin-right"
>Choose File</button>
<input hidden
(change)="onFileSelected($event)"
#fileInput
type="file"
>
<mat-form-field class="margin-right">
<mat-label>Tag</mat-label>
<input matInput
placeholder="Add a tag"
[(ngModel)]="meta ? meta.tag : tag">
</mat-form-field>
<mat-form-field class="margin-right">
<mat-label>Name</mat-label>
<input matInput
placeholder="Name"
[(ngModel)]="meta ? meta.name : name">
</mat-form-field>
<mat-form-field *ngIf="mode==='post'"
class="margin-right"
>
<mat-label>Longitude</mat-label>
<input matInput
placeholder="Add longitude"
[(ngModel)]="meta ? meta.longitude : longitude">
</mat-form-field>
<mat-form-field *ngIf="mode==='post'"
class="margin-right"
>
<mat-label>Longitude</mat-label>
<input matInput
placeholder="Add latitude"
[(ngModel)]="meta ? meta.latitude : latitude">
</mat-form-field>
<button type="button"
mat-raised-button
class="margin-right"
(click)="postFile()"
[disabled]="!(file || mode !== 'post')"
>
<span *ngIf="mode==='post'; else updateImage">Upload Image</span>
<ng-template #updateImage>Update Image</ng-template>
</button>
<br>
<ng-container *ngIf="file">
<span *ngIf="currentFileName">{{currentFileName}}</span>
</ng-container>

View File

@ -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<string> = new EventEmitter<string>();
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');
}
}
}

View File

@ -8,7 +8,7 @@
<mat-expansion-panel
*ngFor="let image of images | slice:paginationGetStart():paginationGetEnd(); let i = index"
(opened)="loadImage(i, image.filename.split('.')[0])"
(opened)="loadImage(i + paginationGetStart(), image.filename.split('.')[0])"
>
<mat-expansion-panel-header (click)="$event.stopPropagation()">
<span style="width: 30em"
@ -24,19 +24,23 @@
<div style="width: 20em; height: 20em; border: solid; margin-bottom: 1em; display: flex">
<img *ngIf="image.image_b64, let b64; else loadingImage"
[src]="b64"
style="width: 100%; margin: auto" [alt]="image.filename"/>
style="width: 100%; margin: auto" [alt]="image.filename"/>
<ng-template #loadingImage>
<mat-spinner style="margin: auto;"></mat-spinner>
</ng-template>
</div>
<angular-file-uploader style="margin-left: 0 !important;"
[config]="getImageUpdateConfig(image.filename)">
</angular-file-uploader>
<app-file-uploader [mode]="'put'"
[filename]="image.filename.split('.')[0]"
[meta]=image
(reload)="loadImage(i + paginationGetStart(), $event.split('.')[0])"
></app-file-uploader>
<span style="float: right">
<button mat-raised-button color="warn" (click)="deleteImage(image)">Delete</button>
<button mat-raised-button color="warn" (click)="deleteImage(image)">
Delete
</button>
</span>
</ng-template>
</mat-expansion-panel>
@ -48,7 +52,7 @@
showFirstLastButtons
></mat-paginator>
<mat-divider style="margin-top: 2em"></mat-divider>
<h3>Upload New Image</h3>
<angular-file-uploader style="margin-left: 0 !important;"
[config]="imageUploadConfig" id="generalUpload">
</angular-file-uploader>
<app-file-uploader [mode]="'post'" (reload)="reload.emit()"></app-file-uploader>
<mat-divider style="margin-top: 2em"></mat-divider>

View File

@ -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<void> = new EventEmitter<void>();
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);
});

View File

@ -6,11 +6,12 @@
</span>
</h1>
<mat-tab-group *ngIf="images.length > 0">
<mat-tab-group *ngIf="images.length > 0; else noImages">
<mat-tab label="Images">
<app-images [images]="images"></app-images>
<app-images [images]="images" (reload)="loadImages()"></app-images>
</mat-tab>
<mat-tab label="Map">
<app-map [images]="images"></app-map>
</mat-tab>
</mat-tab-group>
<ng-template #noImages>No images found</ng-template>

View File

@ -1 +1,3 @@
agm-map { height: 50vh; /* height is required */ }
agm-map {
height: 50vh; /* height is required */
}

View File

@ -1,9 +1,9 @@
<h2>Map</h2>
<agm-map *ngIf="images"
[latitude]="latitude"
[longitude]="longitude"
[zoom]="2">
[latitude]="latitude"
[longitude]="longitude"
[zoom]="2">
<agm-marker
*ngFor="let image of images"
[latitude]="image.latitude"

View File

@ -19,10 +19,16 @@ export interface ImageMetadata {
sha512: string;
selected?: boolean;
image_b64?: string;
tag?: string;
}
export interface ImageResponse {
export interface GetImageResponse {
id: string;
image_data: string;
metadata: ImageMetadata;
}
export interface ImagePostResponse {
id: string;
filename: string;
}

View File

@ -4,7 +4,7 @@ import {NGXLogger} from 'ngx-logger';
import {environment} from '../../environments/environment';
import {Observable} from 'rxjs';
import {tap} from 'rxjs/operators';
import {ImageResponse, StorageHealth} from '../interfaces/interface';
import {GetImageResponse, ImagePostResponse, StorageHealth} from '../interfaces/interface';
@Injectable()
export class RestService {
@ -14,6 +14,7 @@ export class RestService {
private http: HttpClient
) {
}
private currentLocation = 'http://' + environment.location + ':' + environment.port;
private imageUrl = this.currentLocation + '/image';
private getImageUrl = this.imageUrl + '/get';
@ -36,8 +37,8 @@ export class RestService {
);
}
public getImage(id: string): Observable<ImageResponse> {
return this.http.get<ImageResponse>(this.getImageUrl + '/' + id).pipe(
public getImage(id: string): Observable<GetImageResponse> {
return this.http.get<GetImageResponse>(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<any> {
// return this.http.post<any>(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<ImagePostResponse> {
return this.http.post<ImagePostResponse>(this.postImageUrl, imageForm).pipe(
tap(next => this.logger.debug('postImage', imageForm, next))
);
}
// public updateImage(id: string, image: any): Observable<any> {
// return this.http.put<any>(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<any> {
return this.http.put<FormData>(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<StorageHealth> {
return this.http.get<StorageHealth>(this.healthCheckUrl).pipe(
tap(next => this.logger.debug('healthCheck', next))