tempfilemanager
This commit is contained in:
@@ -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> `;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -69,3 +69,5 @@ declare global {
|
||||
'umb-temporary-file-badge': UmbTemporaryFileBadgeElement;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbTemporaryFileBadgeElement;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user