Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<Array<UmbAllowedMediaTypeModel>> {
// Check the parent which children media types are allowed
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -42,39 +37,43 @@ export class UmbInputUploadFieldElement extends UmbLitElement {
* @type {Array<string>}
* @default
*/
@property({ type: Array })
set allowedFileExtensions(value: Array<string>) {
this.#setExtensions(value);
}
get allowedFileExtensions(): Array<string> | 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<string>;

@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<ManifestFileUploadPreview> = [];

override updated(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>) {
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() {
Expand All @@ -87,15 +86,6 @@ export class UmbInputUploadFieldElement extends UmbLitElement {
return this.#manifests;
}

#setExtensions(extensions: Array<string>) {
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<void> {
this._previewAlias = await this.#getPreviewElementAlias();
}
Expand Down Expand Up @@ -151,117 +141,51 @@ 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');
}
async #onUpload(e: UmbDropzoneChangeEvent) {
e.stopImmediatePropagation();

// If the error was caused by the upload being aborted, do not show an error message.
}
}
const target = e.target as UmbInputDropzoneElement;
const file = target.value?.[0];

#handleBrowse(e: Event) {
if (!this._dropzone) return;
e.stopImmediatePropagation();
this._dropzone.browse();
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() {
if (!this.temporaryFile && !this.value.src) {
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`
<uui-file-dropzone
<umb-input-dropzone
id="dropzone"
label="dropzone"
disallowFolderUpload
accept=${ifDefined(this._extensions?.join(', '))}
@change=${this.#onUpload}
@click=${this.#handleBrowse}>
<uui-button label=${this.localize.term('media_clickToUpload')} @click=${this.#handleBrowse}></uui-button>
</uui-file-dropzone>
`;
}

#renderUploader() {
if (!this.temporaryFile) return nothing;

return html`
<div id="temporaryFile">
<div id="fileIcon">
${when(
this.temporaryFile.status === TemporaryFileStatus.SUCCESS,
() => html`<umb-icon name="check" color="green"></umb-icon>`,
)}
${when(
this.temporaryFile.status === TemporaryFileStatus.ERROR,
() => html`<umb-icon name="wrong" color="red"></umb-icon>`,
)}
</div>
<div id="fileDetails">
<div id="fileName">${this.temporaryFile.file.name}</div>
<div id="fileSize">${formatBytes(this.temporaryFile.file.size, { decimals: 2 })}: ${this._progress}%</div>
${when(
this.temporaryFile.status === TemporaryFileStatus.WAITING,
() => html`<div id="progress"><uui-loader-bar progress=${this._progress}></uui-loader-bar></div>`,
)}
${when(
this.temporaryFile.status === TemporaryFileStatus.ERROR,
() => html`<div id="error">An error occured</div>`,
)}
</div>
<div id="fileActions">
${when(
this.temporaryFile.status === TemporaryFileStatus.WAITING,
() => html`
<uui-button compact @click=${this.#handleRemove} label=${this.localize.term('general_cancel')}>
<uui-icon name="remove"></uui-icon>${this.localize.term('general_cancel')}
</uui-button>
`,
() => this.#renderButtonRemove(),
)}
</div>
</div>
disable-folder-upload
accept=${ifDefined(this._extensions?.join(','))}
@change=${this.#onUpload}></umb-input-dropzone>
`;
}

#renderFile(src: string) {
if (!src.startsWith('blob:')) {
src = this._serverUrl + src;
}

return html`
<div id="wrapper">
<div id="wrapperInner">
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
`,
];
}
Expand Down
Loading