mirror of
https://github.com/CaramelFur/Picsur.git
synced 2026-05-06 08:47:20 +02:00
add support for upload status report
This commit is contained in:
@@ -84,6 +84,7 @@ export class ImageManageController {
|
||||
for await (const file of multipart) {
|
||||
const buffer = await file.toBuffer();
|
||||
const filename = file.filename;
|
||||
console.log(filename);
|
||||
|
||||
// const id = ThrowIfFailed(
|
||||
// await this.ingressDB.uploadFile(filename, buffer),
|
||||
|
||||
@@ -1,6 +1,26 @@
|
||||
<div class="content-border">
|
||||
<div class="centered">
|
||||
<h1>Processing</h1>
|
||||
<mat-spinner color="accent"></mat-spinner>
|
||||
<ng-container *ngIf="state === 'idle'">
|
||||
<h1>Loading</h1>
|
||||
<mat-spinner color="accent"></mat-spinner>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="state === 'uploading'">
|
||||
<h1>Uploading</h1>
|
||||
<p>{{ (progress * 100).toFixed(0) }}% complete</p>
|
||||
<mat-progress-bar
|
||||
[mode]="progress === 0 ? 'indeterminate' : 'determinate'"
|
||||
[value]="progress * 100"
|
||||
color="accent"
|
||||
></mat-progress-bar>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="state === 'processing'">
|
||||
<h1>Processing</h1>
|
||||
<p>{{ (progress * 100).toFixed(0) }}% complete</p>
|
||||
<mat-progress-bar
|
||||
[mode]="progress === 0 ? 'indeterminate' : 'determinate'"
|
||||
[value]="progress * 100"
|
||||
color="accent"
|
||||
></mat-progress-bar>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
mat-progress-bar {
|
||||
width: 80%;
|
||||
}
|
||||
@@ -1,18 +1,28 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Fail, FT, HasFailed } from 'picsur-shared/dist/types';
|
||||
import { Fail, FT } from 'picsur-shared/dist/types';
|
||||
import { ProcessingViewMeta } from 'src/app/models/dto/processing-view-meta.dto';
|
||||
import { ApiService } from 'src/app/services/api/api.service';
|
||||
import { ImageService } from 'src/app/services/api/image.service';
|
||||
import { Logger } from 'src/app/services/logger/logger.service';
|
||||
import { ErrorService } from 'src/app/util/error-manager/error.service';
|
||||
|
||||
enum ProcessingState {
|
||||
Idle = 'idle',
|
||||
Uploading = 'uploading',
|
||||
Processing = 'processing',
|
||||
}
|
||||
|
||||
@Component({
|
||||
templateUrl: './processing.component.html',
|
||||
styleUrls: ['./processing.component.scss'],
|
||||
})
|
||||
export class ProcessingComponent implements OnInit {
|
||||
export class ProcessingComponent implements OnInit, OnDestroy {
|
||||
private readonly logger = new Logger(ProcessingComponent.name);
|
||||
|
||||
public state = ProcessingState.Idle;
|
||||
public progress = 0;
|
||||
|
||||
constructor(
|
||||
private readonly router: Router,
|
||||
private readonly imageService: ImageService,
|
||||
@@ -31,10 +41,24 @@ export class ProcessingComponent implements OnInit {
|
||||
|
||||
history.replaceState(null, '');
|
||||
|
||||
const id = await this.imageService.UploadImage(state.imageFiles[0]);
|
||||
const request = this.imageService.UploadImages(state.imageFiles);
|
||||
request.progress.subscribe((progress) => {
|
||||
this.progress = progress;
|
||||
});
|
||||
this.state = ProcessingState.Uploading;
|
||||
|
||||
if (HasFailed(id)) return this.errorService.quitFailure(id, this.logger);
|
||||
await request.result;
|
||||
|
||||
this.router.navigate([`/view/`, id], { replaceUrl: true });
|
||||
this.logger.debug('Upload finished');
|
||||
|
||||
// if (HasFailed(id)) return this.errorService.quitFailure(id, this.logger);
|
||||
|
||||
// this.router.navigate([`/view/`, id], { replaceUrl: true });
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.state === ProcessingState.Idle) return;
|
||||
|
||||
this.errorService.info('Upload continued in background');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { ErrorManagerModule } from 'src/app/util/error-manager/error-manager.module';
|
||||
import { ProcessingComponent } from './processing.component';
|
||||
@@ -12,6 +13,7 @@ import { ProcessingRoutingModule } from './processing.routing.module';
|
||||
ErrorManagerModule,
|
||||
ProcessingRoutingModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatProgressBarModule,
|
||||
],
|
||||
})
|
||||
export default class ProcessingRouteModule {}
|
||||
|
||||
@@ -237,10 +237,12 @@ export class ApiService {
|
||||
const result = await axios.request({
|
||||
url,
|
||||
onDownloadProgress: (e) => {
|
||||
downloadProgress.next(e.loaded / e.total * 100);
|
||||
if (e.total === 0) return downloadProgress.next(0);
|
||||
downloadProgress.next(e.loaded / e.total);
|
||||
},
|
||||
onUploadProgress: (e) => {
|
||||
uploadProgress.next(e.loaded / e.total * 100);
|
||||
if (e.total === 0) return uploadProgress.next(0);
|
||||
uploadProgress.next(e.loaded / e.total);
|
||||
},
|
||||
signal: abortController.signal,
|
||||
...options,
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
HasSuccess,
|
||||
Open
|
||||
} from 'picsur-shared/dist/types/failable';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { ImagesUploadRequest } from 'src/app/models/dto/images-upload-request.dto';
|
||||
import { ImageUploadRequest } from '../../models/dto/image-upload-request.dto';
|
||||
import { ApiService } from './api.service';
|
||||
@@ -49,19 +50,55 @@ export class ImageService {
|
||||
return Open(result, 'id');
|
||||
}
|
||||
|
||||
public async UploadImages(images: File[]): AsyncFailable<string[]> {
|
||||
public UploadImages(images: File[]): {
|
||||
progress: Observable<number>;
|
||||
result: AsyncFailable<string>;
|
||||
cancel: () => void;
|
||||
} {
|
||||
console.log('Uploading images', images);
|
||||
|
||||
// Split into chunks of 20
|
||||
const groups = this.chunks(images, 20);
|
||||
const totalBytes = images.reduce((acc, cur) => acc + cur.size, 0);
|
||||
const groups = this.chunks(images, 20).map((chunk) => {
|
||||
const groupSize = chunk.reduce((acc, cur) => acc + cur.size, 0);
|
||||
return {
|
||||
images: chunk,
|
||||
groupSize,
|
||||
};
|
||||
});
|
||||
const progress = new Subject<number>();
|
||||
const aborter = new AbortController();
|
||||
|
||||
const result = await this.api.postForm(
|
||||
ImageUploadResponse,
|
||||
'/api/image/upload/bulk',
|
||||
new ImagesUploadRequest(images),
|
||||
);
|
||||
const result = (async () => {
|
||||
let processedBytes = 0;
|
||||
for (const group of groups) {
|
||||
const request = await this.api.postForm(
|
||||
ImageUploadResponse,
|
||||
'/api/image/upload/bulk',
|
||||
new ImagesUploadRequest(group.images),
|
||||
);
|
||||
|
||||
return [];
|
||||
request.uploadProgress.subscribe((p) => {
|
||||
progress.next((processedBytes + p * group.groupSize) / totalBytes);
|
||||
});
|
||||
|
||||
aborter.signal.addEventListener('abort', () => {
|
||||
request.cancel();
|
||||
});
|
||||
|
||||
await request.result;
|
||||
|
||||
progress.next((processedBytes += group.groupSize) / totalBytes);
|
||||
}
|
||||
|
||||
return '';
|
||||
})();
|
||||
|
||||
return {
|
||||
progress: progress.asObservable(),
|
||||
result: result,
|
||||
cancel: () => aborter.abort(),
|
||||
};
|
||||
}
|
||||
|
||||
public async GetImageMeta(image: string): AsyncFailable<ImageMetaResponse> {
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<div class="dialog-text">
|
||||
<h2>Downloading {{ data.name }}...</h2>
|
||||
<mat-progress-bar mode="determinate" [value]="progress | async" color="accent"></mat-progress-bar>
|
||||
<mat-progress-bar
|
||||
[mode]="((progress | async) ?? 0) === 0 ? 'indeterminate' : 'determinate'"
|
||||
[value]="((progress | async) ?? 0) * 100"
|
||||
color="accent"
|
||||
></mat-progress-bar>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user