tempfilemanager

This commit is contained in:
Lone Iversen
2023-11-28 14:26:56 +01:00
parent 129f0ffc44
commit 9e46e7c685
5 changed files with 158 additions and 25 deletions

View File

@@ -1,4 +1,5 @@
import { css, html, nothing, until, customElement, property } from '@umbraco-cms/backoffice/external/lit';
import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app';
import { html, until, customElement, property } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
type FileItem = {
@@ -8,12 +9,15 @@ type FileItem = {
@customElement('umb-input-upload-field-file')
export class UmbInputUploadFieldFileElement extends UmbLitElement {
@property({ type: String })
path = '';
/**
* @description The file to be rendered.
* @type {File}
* @required
*/
@property({ type: File, attribute: false })
@property({ attribute: false })
set file(value: File) {
this.#fileItem = new Promise((resolve) => {
/**
@@ -40,15 +44,35 @@ export class UmbInputUploadFieldFileElement extends UmbLitElement {
}
#fileItem!: Promise<FileItem>;
#serverUrl = '';
render = () => until(this.#renderFileItem(), html`<uui-loader></uui-loader>`);
constructor() {
super();
this.consumeContext(UMB_APP_CONTEXT, (instance) => {
this.#serverUrl = instance.getServerUrl();
});
}
// TODO Better way to do this....
render = () => {
if (this.path) {
return html`<uui-symbol-file-thumbnail
src=${this.#serverUrl + this.path}
title=${this.path}
alt=${this.path}></uui-symbol-file-thumbnail>`;
} else {
return until(this.#renderFileItem(), html`<uui-loader></uui-loader>`);
}
};
// render = () => until(this.#renderFileItem(), html`<uui-loader></uui-loader>`);
async #renderFileItem() {
const fileItem = await this.#fileItem;
return html`<uui-symbol-file-thumbnail
src=${fileItem.src}
title=${fileItem.name}
alt=${fileItem.name}></uui-symbol-file-thumbnail>`;
alt=${fileItem.name}></uui-symbol-file-thumbnail> `;
}
}

View File

@@ -1,13 +1,15 @@
import { UmbId } from '../../index.js';
import { TemporaryFileQueueItem, UmbTemporaryFileManager } from '../../temporary-file/temporary-file-manager.class.js';
import {
css,
html,
nothing,
map,
ifDefined,
customElement,
property,
query,
state,
repeat,
} from '@umbraco-cms/backoffice/external/lit';
import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui';
import type { UUIFileDropzoneElement, UUIFileDropzoneEvent } from '@umbraco-cms/backoffice/external/uui';
@@ -50,7 +52,7 @@ export class UmbInputUploadFieldElement extends FormControlMixin(UmbLitElement)
multiple = false;
@state()
_currentFiles: File[] = [];
_currentFiles: Array<TemporaryFileQueueItem> = [];
@state()
extensions?: string[];
@@ -58,10 +60,20 @@ export class UmbInputUploadFieldElement extends FormControlMixin(UmbLitElement)
@query('#dropzone')
private _dropzone?: UUIFileDropzoneElement;
#manager;
protected getFormElement() {
return undefined;
}
constructor() {
super();
this.#manager = new UmbTemporaryFileManager(this);
this.observe(this.#manager.isReady, (value) => (this.error = !value));
this.observe(this.#manager.items, (value) => (this._currentFiles = value));
}
connectedCallback(): void {
super.connectedCallback();
this.#setExtensions();
@@ -85,12 +97,19 @@ export class UmbInputUploadFieldElement extends FormControlMixin(UmbLitElement)
}
#setFiles(files: File[]) {
this._currentFiles = [...this._currentFiles, ...files];
const items = files.map(
(file): TemporaryFileQueueItem => ({
id: UmbId.new(),
file,
status: 'waiting',
}),
);
this.#manager.upload(items);
//TODO: set keys when possible, not names
this.keys = this._currentFiles.map((file) => file.name);
this.dispatchEvent(new CustomEvent('change', { bubbles: true }));
this.keys = items.map((item) => item.id);
this.value = this.keys.join(',');
this.dispatchEvent(new CustomEvent('change', { bubbles: true }));
}
#handleBrowse() {
@@ -99,9 +118,11 @@ export class UmbInputUploadFieldElement extends FormControlMixin(UmbLitElement)
}
render() {
return html`${this.#renderFiles()} ${this.#renderDropzone()}`;
return html`${this.#renderUploadedFiles()} ${this.#renderDropzone()}${this.#renderButtonRemove()}`;
}
//TODO When the property editor gets saved, it seems that the property editor gets the file path from the server rather than key/id.
// Image/files needs to be displayed from a previous save (not just when it just got uploaded).
#renderDropzone() {
if (!this.multiple && this._currentFiles.length) return nothing;
return html`
@@ -115,22 +136,33 @@ export class UmbInputUploadFieldElement extends FormControlMixin(UmbLitElement)
</uui-file-dropzone>
`;
}
#renderFiles() {
#renderUploadedFiles() {
if (!this._currentFiles.length) return nothing;
return html` <div id="wrapper">
${map(this._currentFiles, (file) => {
return html`<umb-input-upload-field-file .file=${file}></umb-input-upload-field-file>`;
})}
</div>
<uui-button compact @click=${this.#handleRemove} label="Remove files">
<uui-icon name="icon-trash"></uui-icon> Remove file(s)
</uui-button>`;
return html`<div id="wrapper">
${repeat(
this._currentFiles,
(item) => item.id + item.status,
(item) =>
html`<div style="position:relative;">
<umb-input-upload-field-file .file=${item.file as any}></umb-input-upload-field-file>
${item.status === 'waiting' ? html`<umb-temporary-file-badge></umb-temporary-file-badge>` : nothing}
</div>`,
)}
</div>`;
}
#renderButtonRemove() {
if (!this._currentFiles.length) return;
return html`<uui-button compact @click=${this.#handleRemove} label="Remove files">
<uui-icon name="icon-trash"></uui-icon> Remove file(s)
</uui-button>`;
}
#handleRemove() {
// Remove via endpoint?
this._currentFiles = [];
const ids = this._currentFiles.map((item) => item.id) as string[];
this.#manager.remove(ids);
this.dispatchEvent(new CustomEvent('change', { bubbles: true }));
}
static styles = [

View File

@@ -1,6 +1,6 @@
import { UmbInputUploadFieldElement } from '../../../components/input-upload-field/input-upload-field.element.js';
import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from "@umbraco-cms/backoffice/style";
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
@@ -34,7 +34,8 @@ export class UmbPropertyEditorUIUploadFieldElement extends UmbLitElement impleme
return html`<umb-input-upload-field
@change="${this._onChange}"
?multiple="${this._multiple}"
.fileExtensions="${this._fileExtensions}"></umb-input-upload-field>`;
.fileExtensions="${this._fileExtensions}"
.value=${this.value}></umb-input-upload-field>`;
}
static styles = [UmbTextStyles];

View File

@@ -69,3 +69,5 @@ declare global {
'umb-temporary-file-badge': UmbTemporaryFileBadgeElement;
}
}
export default UmbTemporaryFileBadgeElement;

View File

@@ -0,0 +1,74 @@
import { UmbTemporaryFileRepository } from './temporary-file.repository.js';
import { UmbArrayState, UmbBooleanState } from '@umbraco-cms/backoffice/observable-api';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
export type TemporaryFileStatus = 'complete' | 'waiting' | 'error';
export interface TemporaryFileQueueItem {
id: string;
file: File;
status?: TemporaryFileStatus;
//type?: string;
}
export class UmbTemporaryFileManager {
#temporaryFileRepository;
#items = new UmbArrayState<TemporaryFileQueueItem>([], (item) => item.id);
public readonly items = this.#items.asObservable();
#isReady = new UmbBooleanState(true);
public readonly isReady = this.#isReady.asObservable();
constructor(host: UmbControllerHostElement) {
this.#temporaryFileRepository = new UmbTemporaryFileRepository(host);
//this.items.subscribe(() => this.handleQueue());
}
uploadOne(id: string, file: File, status: TemporaryFileStatus = 'waiting') {
this.#items.appendOne({ id, file, status });
this.handleQueue();
}
upload(items: Array<TemporaryFileQueueItem>) {
this.#items.append(items);
this.handleQueue();
}
removeOne(id: string) {
this.#items.removeOne(id);
}
remove(ids: Array<string>) {
this.#items.remove(ids);
}
private async handleQueue() {
const items = this.#items.getValue();
if (!items.length && this.getIsReady()) return;
this.#isReady.next(false);
items.forEach(async (item) => {
if (item.status !== 'waiting') return;
const { error } = await this.#temporaryFileRepository.upload(item.id, 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 (error) {
this.#items.updateOne(item.id, { ...item, status: 'error' });
} else {
this.#items.updateOne(item.id, { ...item, status: 'complete' });
}
});
if (!items.find((item) => item.status === 'waiting') && !this.getIsReady()) {
this.#isReady.next(true);
}
}
getIsReady() {
return this.#items.getValue();
}
}