Feature: Temporary Manager Chunksize and Callback (#2432)
Allow to set a chunk size and batch up uploads Co-authored-by: Nikolaj Brask-Nielsen <nikolajgive@gmail.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
export * from './temporary-file.repository.js';
|
||||
export * from './components/temporary-file-badge.element.js';
|
||||
export * from './temporary-file-manager.class.js';
|
||||
export * from './types.js';
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
import { UmbTemporaryFileRepository } from './temporary-file.repository.js';
|
||||
import {
|
||||
TemporaryFileStatus,
|
||||
type UmbQueueHandlerCallback,
|
||||
type UmbTemporaryFileModel,
|
||||
type UmbUploadOptions,
|
||||
} from './types.js';
|
||||
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
|
||||
|
||||
///export type TemporaryFileStatus = 'success' | 'waiting' | 'error';
|
||||
|
||||
export enum TemporaryFileStatus {
|
||||
SUCCESS = 'success',
|
||||
WAITING = 'waiting',
|
||||
ERROR = 'error',
|
||||
}
|
||||
|
||||
export interface UmbTemporaryFileModel {
|
||||
file: File;
|
||||
temporaryUnique: string;
|
||||
status?: TemporaryFileStatus;
|
||||
}
|
||||
|
||||
export class UmbTemporaryFileManager<
|
||||
UploadableItem extends UmbTemporaryFileModel = UmbTemporaryFileModel,
|
||||
> extends UmbControllerBase {
|
||||
@@ -24,7 +16,7 @@ export class UmbTemporaryFileManager<
|
||||
readonly #queue = new UmbArrayState<UploadableItem>([], (item) => item.temporaryUnique);
|
||||
public readonly queue = this.#queue.asObservable();
|
||||
|
||||
async uploadOne(uploadableItem: UploadableItem): Promise<UploadableItem> {
|
||||
async uploadOne(uploadableItem: UploadableItem, options?: UmbUploadOptions<UploadableItem>): Promise<UploadableItem> {
|
||||
this.#queue.setValue([]);
|
||||
|
||||
const item: UploadableItem = {
|
||||
@@ -33,15 +25,18 @@ export class UmbTemporaryFileManager<
|
||||
};
|
||||
|
||||
this.#queue.appendOne(item);
|
||||
return (await this.#handleQueue())[0];
|
||||
return (await this.#handleQueue({ ...options, chunkSize: 1 }))[0];
|
||||
}
|
||||
|
||||
async upload(queueItems: Array<UploadableItem>): Promise<Array<UploadableItem>> {
|
||||
async upload(
|
||||
queueItems: Array<UploadableItem>,
|
||||
options?: UmbUploadOptions<UploadableItem>,
|
||||
): Promise<Array<UploadableItem>> {
|
||||
this.#queue.setValue([]);
|
||||
|
||||
const items = queueItems.map((item): UploadableItem => ({ status: TemporaryFileStatus.WAITING, ...item }));
|
||||
this.#queue.append(items);
|
||||
return this.#handleQueue();
|
||||
return this.#handleQueue({ ...options });
|
||||
}
|
||||
|
||||
removeOne(unique: string) {
|
||||
@@ -52,30 +47,44 @@ export class UmbTemporaryFileManager<
|
||||
this.#queue.remove(uniques);
|
||||
}
|
||||
|
||||
async #handleQueue() {
|
||||
async #handleQueue(options?: UmbUploadOptions<UploadableItem>): Promise<Array<UploadableItem>> {
|
||||
const filesCompleted: Array<UploadableItem> = [];
|
||||
const queue = this.#queue.getValue();
|
||||
|
||||
if (!queue.length) return filesCompleted;
|
||||
|
||||
for (const item of queue) {
|
||||
if (!item.temporaryUnique) throw new Error(`Unique is missing for item ${item}`);
|
||||
const handler: UmbQueueHandlerCallback<UploadableItem> = async (item) => {
|
||||
const completedUpload = await this.#handleUpload(item);
|
||||
filesCompleted.push(completedUpload);
|
||||
|
||||
const { error } = await this.#temporaryFileRepository.upload(item.temporaryUnique, item.file);
|
||||
//await new Promise((resolve) => setTimeout(resolve, (Math.random() + 0.5) * 1000)); // simulate small delay so that the upload badge is properly shown
|
||||
if (options?.callback) await options.callback(completedUpload);
|
||||
};
|
||||
|
||||
let status: TemporaryFileStatus;
|
||||
if (error) {
|
||||
status = TemporaryFileStatus.ERROR;
|
||||
this.#queue.updateOne(item.temporaryUnique, { ...item, status });
|
||||
} else {
|
||||
status = TemporaryFileStatus.SUCCESS;
|
||||
this.#queue.updateOne(item.temporaryUnique, { ...item, status });
|
||||
}
|
||||
const chunkSize = options?.chunkSize ?? 5;
|
||||
const chunks = Math.ceil(queue.length / chunkSize);
|
||||
|
||||
filesCompleted.push({ ...item, status });
|
||||
for (let i = 0; i < chunks; i++) {
|
||||
const chunk = queue.slice(i * chunkSize, i * chunkSize + chunkSize);
|
||||
await Promise.all(chunk.map(handler));
|
||||
}
|
||||
|
||||
return filesCompleted;
|
||||
}
|
||||
|
||||
async #handleUpload(item: UploadableItem) {
|
||||
if (!item.temporaryUnique) throw new Error(`Unique is missing for item ${item}`);
|
||||
|
||||
const { error } = await this.#temporaryFileRepository.upload(item.temporaryUnique, item.file);
|
||||
|
||||
let status: TemporaryFileStatus;
|
||||
if (error) {
|
||||
status = TemporaryFileStatus.ERROR;
|
||||
this.#queue.updateOne(item.temporaryUnique, { ...item, status });
|
||||
} else {
|
||||
status = TemporaryFileStatus.SUCCESS;
|
||||
this.#queue.updateOne(item.temporaryUnique, { ...item, status });
|
||||
}
|
||||
|
||||
return { ...item, status };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
export enum TemporaryFileStatus {
|
||||
SUCCESS = 'success',
|
||||
WAITING = 'waiting',
|
||||
ERROR = 'error',
|
||||
}
|
||||
|
||||
export interface UmbTemporaryFileModel {
|
||||
file: File;
|
||||
temporaryUnique: string;
|
||||
status?: TemporaryFileStatus;
|
||||
}
|
||||
|
||||
export type UmbQueueHandlerCallback<TItem extends UmbTemporaryFileModel> = (item: TItem) => Promise<void>;
|
||||
|
||||
export type UmbUploadOptions<TItem extends UmbTemporaryFileModel> = {
|
||||
chunkSize?: number;
|
||||
callback?: UmbQueueHandlerCallback<TItem>;
|
||||
};
|
||||
Reference in New Issue
Block a user