diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/dropzone-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/dropzone-manager.class.ts index 52d4636371..64c6bfb6ca 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/dropzone-manager.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/dropzone/dropzone-manager.class.ts @@ -70,7 +70,6 @@ export class UmbDropzoneManager extends UmbControllerBase { } /** - * @param isAllowed * @deprecated Not used anymore; this method will be removed in Umbraco 17. */ public setIsFoldersAllowed(isAllowed: boolean) { @@ -128,7 +127,9 @@ export class UmbDropzoneManager extends UmbControllerBase { const uploaded = await this.#tempFileManager.uploadOne(item.temporaryFile); // Update progress - if (uploaded.status === TemporaryFileStatus.SUCCESS) { + if (uploaded.status === TemporaryFileStatus.CANCELLED) { + this.#updateStatus(item, UmbFileDropzoneItemStatus.CANCELLED); + } else if (uploaded.status === TemporaryFileStatus.SUCCESS) { this.#updateStatus(item, UmbFileDropzoneItemStatus.COMPLETE); } else { this.#updateStatus(item, UmbFileDropzoneItemStatus.ERROR); @@ -226,7 +227,8 @@ export class UmbDropzoneManager extends UmbControllerBase { async #handleFile(item: UmbUploadableFile, mediaTypeUnique: string) { // Upload the file as a temporary file and update progress. - const temporaryFile = await this.#uploadAsTemporaryFile(item); + const temporaryFile = await this.#tempFileManager.uploadOne(item.temporaryFile); + if (temporaryFile.status === TemporaryFileStatus.CANCELLED) { this.#updateStatus(item, UmbFileDropzoneItemStatus.CANCELLED); return; @@ -257,10 +259,6 @@ export class UmbDropzoneManager extends UmbControllerBase { } } - #uploadAsTemporaryFile(item: UmbUploadableFile) { - return this.#tempFileManager.uploadOne(item.temporaryFile); - } - // Media types async #getMediaTypeOptions(item: UmbUploadableItem): Promise> { // Check the parent which children media types are allowed diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/input-upload-field.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/input-upload-field.element.ts index 7c7cd25841..3ada651c56 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/input-upload-field.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/input-upload-field.element.ts @@ -1,33 +1,28 @@ import type { MediaValueType } from '../../property-editors/upload-field/types.js'; import type { ManifestFileUploadPreview } from './file-upload-preview.extension.js'; import { getMimeTypeFromExtension } from './utils.js'; -import { - css, - html, - nothing, - ifDefined, - customElement, - property, - query, - state, - when, -} from '@umbraco-cms/backoffice/external/lit'; -import { formatBytes, stringOrStringArrayContains } from '@umbraco-cms/backoffice/utils'; -import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import { css, customElement, html, ifDefined, nothing, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { stringOrStringArrayContains } from '@umbraco-cms/backoffice/utils'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbExtensionsManifestInitializer } from '@umbraco-cms/backoffice/extension-api'; -import { UmbId } from '@umbraco-cms/backoffice/id'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import { UmbFileDropzoneItemStatus, UmbInputDropzoneDashedStyles } from '@umbraco-cms/backoffice/dropzone'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbTemporaryFileManager, TemporaryFileStatus } from '@umbraco-cms/backoffice/temporary-file'; -import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app'; +import type { + UmbDropzoneChangeEvent, + UmbInputDropzoneElement, + UmbUploadableFile, +} from '@umbraco-cms/backoffice/dropzone'; import type { UmbTemporaryFileModel } from '@umbraco-cms/backoffice/temporary-file'; -import type { UUIFileDropzoneElement, UUIFileDropzoneEvent } from '@umbraco-cms/backoffice/external/uui'; @customElement('umb-input-upload-field') export class UmbInputUploadFieldElement extends UmbLitElement { - @property({ type: Object }) + @property({ type: Object, attribute: false }) set value(value: MediaValueType) { this.#src = value?.src ?? ''; + this.#setPreviewAlias(); } get value(): MediaValueType { return { @@ -42,39 +37,43 @@ export class UmbInputUploadFieldElement extends UmbLitElement { * @type {Array} * @default */ - @property({ type: Array }) - set allowedFileExtensions(value: Array) { - this.#setExtensions(value); - } - get allowedFileExtensions(): Array | undefined { - return this._extensions; - } + @property({ + type: Array, + attribute: 'allowed-file-extensions', + converter(value) { + if (typeof value === 'string') { + return value.split(',').map((ext) => ext.trim()); + } + return value; + }, + }) + allowedFileExtensions?: Array; @state() public temporaryFile?: UmbTemporaryFileModel; - @state() - private _progress = 0; - @state() private _extensions?: string[]; @state() private _previewAlias?: string; - @query('#dropzone') - private _dropzone?: UUIFileDropzoneElement; - - #manager = new UmbTemporaryFileManager(this); + @state() + private _serverUrl = ''; #manifests: Array = []; - override updated(changedProperties: PropertyValueMap | Map) { - super.updated(changedProperties); + constructor() { + super(); - if (changedProperties.has('value') && changedProperties.get('value')?.src !== this.value.src) { - this.#setPreviewAlias(); - } + this.consumeContext(UMB_APP_CONTEXT, (context) => { + this._serverUrl = context.getServerUrl(); + }); + } + + override disconnectedCallback(): void { + super.disconnectedCallback(); + this.#clearObjectUrl(); } async #getManifests() { @@ -87,15 +86,6 @@ export class UmbInputUploadFieldElement extends UmbLitElement { return this.#manifests; } - #setExtensions(extensions: Array) { - if (!extensions?.length) { - this._extensions = undefined; - return; - } - // TODO: The dropzone uui component does not support file extensions without a dot. Remove this when it does. - this._extensions = extensions?.map((extension) => `.${extension}`); - } - async #setPreviewAlias(): Promise { this._previewAlias = await this.#getPreviewElementAlias(); } @@ -151,47 +141,22 @@ export class UmbInputUploadFieldElement extends UmbLitElement { return getMimeTypeFromExtension('.' + extension); } - async #onUpload(e: UUIFileDropzoneEvent) { - try { - //Property Editor for Upload field will always only have one file. - this.temporaryFile = { - temporaryUnique: UmbId.new(), - status: TemporaryFileStatus.WAITING, - file: e.detail.files[0], - onProgress: (p) => { - this._progress = Math.ceil(p); - }, - abortController: new AbortController(), - }; - - const uploaded = await this.#manager.uploadOne(this.temporaryFile); - - if (uploaded.status === TemporaryFileStatus.SUCCESS) { - this.temporaryFile.status = TemporaryFileStatus.SUCCESS; - - const blobUrl = URL.createObjectURL(this.temporaryFile.file); - this.value = { src: blobUrl }; - - this.dispatchEvent(new UmbChangeEvent()); - } else { - this.temporaryFile.status = TemporaryFileStatus.ERROR; - this.requestUpdate('temporaryFile'); - } - } catch { - // If we still have a temporary file, set it to error. - if (this.temporaryFile) { - this.temporaryFile.status = TemporaryFileStatus.ERROR; - this.requestUpdate('temporaryFile'); - } - - // If the error was caused by the upload being aborted, do not show an error message. - } - } - - #handleBrowse(e: Event) { - if (!this._dropzone) return; + async #onUpload(e: UmbDropzoneChangeEvent) { e.stopImmediatePropagation(); - this._dropzone.browse(); + + const target = e.target as UmbInputDropzoneElement; + const file = target.value?.[0]; + + if (file?.status !== UmbFileDropzoneItemStatus.COMPLETE) return; + + this.temporaryFile = (file as UmbUploadableFile).temporaryFile; + + this.#clearObjectUrl(); + + const blobUrl = URL.createObjectURL(this.temporaryFile.file); + this.value = { src: blobUrl }; + + this.dispatchEvent(new UmbChangeEvent()); } override render() { @@ -199,69 +164,28 @@ export class UmbInputUploadFieldElement extends UmbLitElement { return this.#renderDropzone(); } - return html` - ${this.temporaryFile ? this.#renderUploader() : nothing} - ${this.value.src && this._previewAlias ? this.#renderFile(this.value.src) : nothing} - `; + if (this.value?.src && this._previewAlias) { + return this.#renderFile(this.value.src); + } + + return nothing; } #renderDropzone() { return html` - - - - `; - } - - #renderUploader() { - if (!this.temporaryFile) return nothing; - - return html` -
-
- ${when( - this.temporaryFile.status === TemporaryFileStatus.SUCCESS, - () => html``, - )} - ${when( - this.temporaryFile.status === TemporaryFileStatus.ERROR, - () => html``, - )} -
-
-
${this.temporaryFile.file.name}
-
${formatBytes(this.temporaryFile.file.size, { decimals: 2 })}: ${this._progress}%
- ${when( - this.temporaryFile.status === TemporaryFileStatus.WAITING, - () => html`
`, - )} - ${when( - this.temporaryFile.status === TemporaryFileStatus.ERROR, - () => html`
An error occured
`, - )} -
-
- ${when( - this.temporaryFile.status === TemporaryFileStatus.WAITING, - () => html` - - ${this.localize.term('general_cancel')} - - `, - () => this.#renderButtonRemove(), - )} -
-
+ disable-folder-upload + accept=${ifDefined(this._extensions?.join(','))} + @change=${this.#onUpload}> `; } #renderFile(src: string) { + if (!src.startsWith('blob:')) { + src = this._serverUrl + src; + } + return html`
@@ -288,13 +212,25 @@ export class UmbInputUploadFieldElement extends UmbLitElement { // If the upload promise happens to be in progress, cancel it. this.temporaryFile?.abortController?.abort(); + this.#clearObjectUrl(); + this.value = { src: undefined }; this.temporaryFile = undefined; - this._progress = 0; this.dispatchEvent(new UmbChangeEvent()); } + /** + * If there is a former File, revoke the object URL. + */ + #clearObjectUrl(): void { + if (this.value?.src?.startsWith('blob:')) { + URL.revokeObjectURL(this.value.src); + } + } + static override readonly styles = [ + UmbTextStyles, + UmbInputDropzoneDashedStyles, css` :host { position: relative; @@ -323,51 +259,6 @@ export class UmbInputUploadFieldElement extends UmbLitElement { width: fit-content; max-width: 100%; } - - #temporaryFile { - display: grid; - grid-template-columns: auto auto auto; - width: fit-content; - max-width: 100%; - margin: var(--uui-size-layout-1) 0; - padding: var(--uui-size-space-3); - border: 1px dashed var(--uui-color-divider-emphasis); - } - - #fileIcon, - #fileActions { - place-self: center center; - padding: 0 var(--uui-size-layout-1); - } - - #fileName { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-size: var(--uui-size-5); - } - - #fileSize { - font-size: var(--uui-font-size-small); - color: var(--uui-color-text-alt); - } - - #error { - color: var(--uui-color-danger); - } - - uui-file-dropzone { - position: relative; - display: block; - padding: 3px; /** Dropzone background is blurry and covers slightly into other elements. Hack to avoid this */ - } - uui-file-dropzone::after { - content: ''; - position: absolute; - inset: 0; - cursor: pointer; - border: 1px dashed var(--uui-color-divider-emphasis); - } `, ]; }