V15: Upload folders in dropzone (#2116)
* handling of folders in dropzone * consolelog * load bar * request folders, skip OS files, cleanup * cleanup * deprecated * remove comments dropzone * remove unused interface * mime util * setup mime and constructor * update * setup types and saving * remove observe * rename method in repo * destroy first when total is completed * ordering and cleanup * comment * move logic back to dropzone manager * document and media type imports * scaffold * update util * update types * update progress, cleanup in dropzone manager * from switch to if statement * sonarcloud pratice * css disabled * Fixed @sonarcloud issues * Added deprecation notices on renamed methods * file extension --------- Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Co-authored-by: leekelleher <leekelleher@gmail.com>
This commit is contained in:
@@ -43,12 +43,12 @@ export class UmbDocumentTypeImportModalLayout extends UmbModalBaseElement<
|
||||
};
|
||||
}
|
||||
|
||||
#onFileDropped() {
|
||||
const data = this.dropzone?.getFiles()[0];
|
||||
if (!data) return;
|
||||
#onUploadComplete() {
|
||||
const data = this.dropzone?.getItems()[0];
|
||||
if (!data?.temporaryFile) return;
|
||||
|
||||
this.#temporaryUnique = data.temporaryUnique;
|
||||
this.#fileReader.readAsText(data.file);
|
||||
this.#temporaryUnique = data.temporaryFile.temporaryUnique;
|
||||
this.#fileReader.readAsText(data.temporaryFile.file);
|
||||
}
|
||||
|
||||
async #onFileImport() {
|
||||
@@ -136,7 +136,11 @@ export class UmbDocumentTypeImportModalLayout extends UmbModalBaseElement<
|
||||
html`<div id="wrapper">
|
||||
Drag and drop your file here
|
||||
<uui-button look="primary" label="or click here to choose a file" @click=${this.#onBrowse}></uui-button>
|
||||
<umb-dropzone id="dropzone" createAsTemporary @change=${this.#onFileDropped}> </umb-dropzone>
|
||||
<umb-dropzone
|
||||
id="dropzone"
|
||||
accept=".udt"
|
||||
@complete=${this.#onUploadComplete}
|
||||
createAsTemporary></umb-dropzone>
|
||||
</div>`,
|
||||
)}
|
||||
`;
|
||||
|
||||
@@ -33,19 +33,19 @@ export class UmbMediaTypeImportModalLayout extends UmbModalBaseElement<
|
||||
this.#fileReader.onload = (e) => {
|
||||
if (typeof e.target?.result === 'string') {
|
||||
const fileContent = e.target.result;
|
||||
this.#MediaTypePreviewBuilder(fileContent);
|
||||
this.#mediaTypePreviewBuilder(fileContent);
|
||||
} else {
|
||||
this.#requestReset();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#onFileDropped() {
|
||||
const data = this.dropzone?.getFiles()[0];
|
||||
if (!data) return;
|
||||
#onUploadCompleted() {
|
||||
const data = this.dropzone?.getItems()[0];
|
||||
if (!data?.temporaryFile) return;
|
||||
|
||||
this.#temporaryUnique = data.temporaryUnique;
|
||||
this.#fileReader.readAsText(data.file);
|
||||
this.#temporaryUnique = data.temporaryFile.temporaryUnique;
|
||||
this.#fileReader.readAsText(data.temporaryFile.file);
|
||||
}
|
||||
|
||||
async #onFileImport() {
|
||||
@@ -55,7 +55,7 @@ export class UmbMediaTypeImportModalLayout extends UmbModalBaseElement<
|
||||
this._submitModal();
|
||||
}
|
||||
|
||||
#MediaTypePreviewBuilder(htmlString: string) {
|
||||
#mediaTypePreviewBuilder(htmlString: string) {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(htmlString, 'text/xml');
|
||||
const childNodes = doc.childNodes;
|
||||
@@ -68,10 +68,10 @@ export class UmbMediaTypeImportModalLayout extends UmbModalBaseElement<
|
||||
}
|
||||
});
|
||||
|
||||
this._fileContent = this.#MediaTypePreviewItemBuilder(elements);
|
||||
this._fileContent = this.#mediaTypePreviewItemBuilder(elements);
|
||||
}
|
||||
|
||||
#MediaTypePreviewItemBuilder(elements: Array<Element>) {
|
||||
#mediaTypePreviewItemBuilder(elements: Array<Element>) {
|
||||
const mediaTypes: Array<UmbMediaTypePreview> = [];
|
||||
elements.forEach((MediaType) => {
|
||||
const info = MediaType.getElementsByTagName('Info')[0];
|
||||
@@ -129,7 +129,11 @@ export class UmbMediaTypeImportModalLayout extends UmbModalBaseElement<
|
||||
html`<div id="wrapper">
|
||||
Drag and drop your file here
|
||||
<uui-button look="primary" label="or click here to choose a file" @click=${this.#onBrowse}></uui-button>
|
||||
<umb-dropzone id="dropzone" createAsTemporary @change=${this.#onFileDropped}> </umb-dropzone>
|
||||
<umb-dropzone
|
||||
id="dropzone"
|
||||
accept=".udt"
|
||||
@complete=${this.#onUploadCompleted}
|
||||
createAsTemporary></umb-dropzone>
|
||||
</div>`,
|
||||
)}
|
||||
`;
|
||||
|
||||
@@ -21,6 +21,10 @@ export class UmbMediaTypeStructureRepository extends UmbContentTypeStructureRepo
|
||||
}) {
|
||||
return this.#dataSource.getMediaTypesOfFileExtension({ fileExtension, skip, take });
|
||||
}
|
||||
|
||||
async requestMediaTypesOfFolders({ skip = 0, take = 100 } = {}) {
|
||||
return this.#dataSource.getMediaTypesOfFolders({ skip, take });
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbMediaTypeStructureRepository;
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import type { UmbAllowedMediaTypeModel } from './types.js';
|
||||
import { UmbContentTypeStructureServerDataSourceBase } from '@umbraco-cms/backoffice/content-type';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { AllowedMediaTypeModel } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import { MediaTypeService } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import { UmbContentTypeStructureServerDataSourceBase } from '@umbraco-cms/backoffice/content-type';
|
||||
import type { AllowedMediaTypeModel } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
* @class UmbMediaTypeStructureServerDataSource
|
||||
* @augments {UmbContentTypeStructureServerDataSourceBase}
|
||||
*/
|
||||
@@ -21,6 +19,10 @@ export class UmbMediaTypeStructureServerDataSource extends UmbContentTypeStructu
|
||||
getMediaTypesOfFileExtension({ fileExtension, skip, take }: { fileExtension: string; skip: number; take: number }) {
|
||||
return getAllowedMediaTypesOfExtension({ fileExtension, skip, take });
|
||||
}
|
||||
|
||||
getMediaTypesOfFolders({ skip, take }: { skip: number; take: number }) {
|
||||
return getAllowedMediaTypesOfFolders({ skip, take });
|
||||
}
|
||||
}
|
||||
|
||||
const getAllowedChildrenOf = (unique: string | null) => {
|
||||
@@ -42,6 +44,12 @@ const mapper = (item: AllowedMediaTypeModel): UmbAllowedMediaTypeModel => {
|
||||
};
|
||||
};
|
||||
|
||||
const getAllowedMediaTypesOfFolders = async ({ skip, take }: { skip: number; take: number }) => {
|
||||
// eslint-disable-next-line local-rules/no-direct-api-import
|
||||
const { items } = await MediaTypeService.getItemMediaTypeFolders({ skip, take });
|
||||
return items.map((item) => mapper(item));
|
||||
};
|
||||
|
||||
const getAllowedMediaTypesOfExtension = async ({
|
||||
fileExtension,
|
||||
skip,
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
//TODO Can we trust this is the unique? This probably need a similar solution like the media collection repository method getDefaultConfiguration()
|
||||
// TODO: Can we trust this is the unique? This probably need a similar solution like the media collection repository method getDefaultConfiguration()
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {string} The unique identifier for the Umbraco folder media-type.
|
||||
*/
|
||||
export function getUmbracoFolderUnique(): string {
|
||||
return 'f38bd2d7-65d0-48e6-95dc-87ce06ec2d3d';
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param unique
|
||||
* @param {string} unique The unique identifier of the media-type to check.
|
||||
* @returns {boolean} True if the unique identifier is the Umbraco folder media-type.
|
||||
*/
|
||||
export function isUmbracoFolder(unique?: string): boolean {
|
||||
return unique === getUmbracoFolderUnique();
|
||||
|
||||
@@ -4,8 +4,6 @@ import type { UmbMediaCollectionContext } from './media-collection.context.js';
|
||||
import { UMB_MEDIA_COLLECTION_CONTEXT } from './media-collection.context-token.js';
|
||||
import { customElement, html, state, when } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbCollectionDefaultElement } from '@umbraco-cms/backoffice/collection';
|
||||
import type { UmbProgressEvent } from '@umbraco-cms/backoffice/event';
|
||||
|
||||
import './media-collection-toolbar.element.js';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import { UmbRequestReloadChildrenOfEntityEvent } from '@umbraco-cms/backoffice/entity-action';
|
||||
@@ -32,7 +30,7 @@ export class UmbMediaCollectionElement extends UmbCollectionDefaultElement {
|
||||
});
|
||||
}
|
||||
|
||||
async #onChange() {
|
||||
async #onComplete() {
|
||||
this._progress = -1;
|
||||
this.#mediaCollection?.requestCollection();
|
||||
|
||||
@@ -44,8 +42,11 @@ export class UmbMediaCollectionElement extends UmbCollectionDefaultElement {
|
||||
eventContext.dispatchEvent(event);
|
||||
}
|
||||
|
||||
#onProgress(event: UmbProgressEvent) {
|
||||
this._progress = event.progress;
|
||||
#onProgress(event: ProgressEvent) {
|
||||
this._progress = (event.loaded / event.total) * 100;
|
||||
if (this._progress >= 100) {
|
||||
this._progress = -1;
|
||||
}
|
||||
}
|
||||
|
||||
protected override renderToolbar() {
|
||||
@@ -54,7 +55,7 @@ export class UmbMediaCollectionElement extends UmbCollectionDefaultElement {
|
||||
${when(this._progress >= 0, () => html`<uui-loader-bar progress=${this._progress}></uui-loader-bar>`)}
|
||||
<umb-dropzone
|
||||
.parentUnique=${this._unique}
|
||||
@change=${this.#onChange}
|
||||
@complete=${this.#onComplete}
|
||||
@progress=${this.#onProgress}></umb-dropzone>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { UmbMediaItemRepository } from '../../repository/index.js';
|
||||
import { UMB_IMAGE_CROPPER_EDITOR_MODAL, UMB_MEDIA_PICKER_MODAL } from '../../modals/index.js';
|
||||
import type { UmbCropModel, UmbMediaPickerPropertyValue } from '../../types.js';
|
||||
import type { UmbMediaItemModel } from '../../repository/index.js';
|
||||
import type { UmbUploadableFileModel } from '../../dropzone/index.js';
|
||||
import type { UmbUploadableItem } from '../../dropzone/types.js';
|
||||
import { customElement, html, nothing, property, repeat, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { umbConfirmModal, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
@@ -331,7 +331,7 @@ export class UmbInputRichMediaElement extends UUIFormControlMixin(UmbLitElement,
|
||||
}
|
||||
|
||||
async #onUploadCompleted(e: CustomEvent) {
|
||||
const completed = e.detail?.completed as Array<UmbUploadableFileModel>;
|
||||
const completed = e.detail as Array<UmbUploadableItem>;
|
||||
const uploaded = completed.map((file) => file.unique);
|
||||
this.#addItems(uploaded);
|
||||
}
|
||||
@@ -346,7 +346,7 @@ export class UmbInputRichMediaElement extends UUIFormControlMixin(UmbLitElement,
|
||||
#renderDropzone() {
|
||||
if (this.readonly) return nothing;
|
||||
if (this._cards && this._cards.length >= this.max) return;
|
||||
return html`<umb-dropzone @change=${this.#onUploadCompleted}></umb-dropzone>`;
|
||||
return html`<umb-dropzone @complete=${this.#onUploadCompleted}></umb-dropzone>`;
|
||||
}
|
||||
|
||||
#renderItems() {
|
||||
|
||||
@@ -1,272 +1,328 @@
|
||||
import type { UmbMediaDetailModel } from '../types.js';
|
||||
import { UmbMediaDetailRepository } from '../repository/index.js';
|
||||
import { UMB_DROPZONE_MEDIA_TYPE_PICKER_MODAL } from './modals/dropzone-media-type-picker/dropzone-media-type-picker-modal.token.js';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { UmbMediaDetailModel, UmbMediaValueModel } from '../types.js';
|
||||
import { UmbFileDropzoneItemStatus } from './types.js';
|
||||
import { UMB_DROPZONE_MEDIA_TYPE_PICKER_MODAL } from './modals/index.js';
|
||||
import type {
|
||||
UmbUploadableFile,
|
||||
UmbUploadableFolder,
|
||||
UmbFileDropzoneDroppedItems,
|
||||
UmbFileDropzoneProgress,
|
||||
UmbUploadableItem,
|
||||
UmbAllowedMediaTypesOfExtension,
|
||||
UmbAllowedChildrenOfMediaType,
|
||||
} from './types.js';
|
||||
import { TemporaryFileStatus, UmbTemporaryFileManager } from '@umbraco-cms/backoffice/temporary-file';
|
||||
import { UmbArrayState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import { type UmbAllowedMediaTypeModel, UmbMediaTypeStructureRepository } from '@umbraco-cms/backoffice/media-type';
|
||||
import {
|
||||
TemporaryFileStatus,
|
||||
UmbTemporaryFileManager,
|
||||
type UmbTemporaryFileModel,
|
||||
} from '@umbraco-cms/backoffice/temporary-file';
|
||||
import { UmbId } from '@umbraco-cms/backoffice/id';
|
||||
import { UmbMediaTypeStructureRepository } from '@umbraco-cms/backoffice/media-type';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
|
||||
|
||||
export interface UmbUploadableFileModel extends UmbTemporaryFileModel {
|
||||
unique: string;
|
||||
mediaTypeUnique: string;
|
||||
}
|
||||
|
||||
export interface UmbUploadableExtensionModel {
|
||||
fileExtension: string;
|
||||
mediaTypes: Array<UmbAllowedMediaTypeModel>;
|
||||
}
|
||||
import type { UmbAllowedMediaTypeModel } from '@umbraco-cms/backoffice/media-type';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { UmbTemporaryFileModel } from '@umbraco-cms/backoffice/temporary-file';
|
||||
|
||||
/**
|
||||
* Manages the dropzone and uploads files to the server.
|
||||
* @function createFilesAsMedia - Upload files to the server and creates the items using corresponding media type.
|
||||
* @function createFilesAsTemporary - Upload the files as temporary files and returns the data.
|
||||
* @observable completed - Emits an array of completed uploads.
|
||||
* Manages the dropzone and uploads folders and files to the server.
|
||||
* @function createMediaItems - Upload files and folders to the server and creates the items using corresponding media type.
|
||||
* @function createTemporaryFiles - Upload the files as temporary files and returns the data.
|
||||
* @observable progress - Emits the number of completed items and total items.
|
||||
* @observable progressItems - Emits the items with their current status.
|
||||
*/
|
||||
export class UmbDropzoneManager extends UmbControllerBase {
|
||||
#host;
|
||||
|
||||
#tempFileManager = new UmbTemporaryFileManager(this);
|
||||
readonly #host: UmbControllerHost;
|
||||
#isFoldersAllowed = true;
|
||||
|
||||
#mediaTypeStructure = new UmbMediaTypeStructureRepository(this);
|
||||
#mediaDetailRepository = new UmbMediaDetailRepository(this);
|
||||
|
||||
#completed = new UmbArrayState<UmbUploadableFileModel | UmbTemporaryFileModel>(
|
||||
[],
|
||||
(upload) => upload.temporaryUnique,
|
||||
);
|
||||
public readonly completed = this.#completed.asObservable();
|
||||
#tempFileManager = new UmbTemporaryFileManager(this);
|
||||
|
||||
// The available media types for a file extension.
|
||||
readonly #availableMediaTypesOf = new UmbArrayState<UmbAllowedMediaTypesOfExtension>([], (x) => x.extension);
|
||||
|
||||
// The media types that the parent will allow to be created under it.
|
||||
readonly #allowedChildrenOf = new UmbArrayState<UmbAllowedChildrenOfMediaType>([], (x) => x.mediaTypeUnique);
|
||||
|
||||
readonly #progress = new UmbObjectState<UmbFileDropzoneProgress>({ total: 0, completed: 0 });
|
||||
public readonly progress = this.#progress.asObservable();
|
||||
|
||||
readonly #progressItems = new UmbArrayState<UmbUploadableItem>([], (x) => x.unique);
|
||||
public readonly progressItems = this.#progressItems.asObservable();
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host);
|
||||
this.#host = host;
|
||||
}
|
||||
|
||||
public setIsFoldersAllowed(isAllowed: boolean) {
|
||||
this.#isFoldersAllowed = isAllowed;
|
||||
}
|
||||
|
||||
public getIsFoldersAllowed(): boolean {
|
||||
return this.#isFoldersAllowed;
|
||||
}
|
||||
|
||||
/** @deprecated Please use `createMediaItems()` instead; this method will be removed in Umbraco 17. */
|
||||
public createFilesAsMedia = this.createMediaItems;
|
||||
|
||||
/**
|
||||
* Uploads files and folders to the server and creates the media items with corresponding media type.\
|
||||
* Allows the user to pick a media type option if multiple types are allowed.
|
||||
* @param {UmbFileDropzoneDroppedItems} items - The files and folders to upload
|
||||
* @param {string | null} parentUnique - Where the items should be uploaded
|
||||
*/
|
||||
public async createMediaItems(items: UmbFileDropzoneDroppedItems, parentUnique: string | null = null) {
|
||||
const uploadableItems = await this.#setupProgress(items, parentUnique);
|
||||
if (uploadableItems.length === 1) {
|
||||
// When there is only one item being uploaded, allow the user to pick the media type, if more than one is allowed.
|
||||
await this.#createOneMediaItem(uploadableItems[0]);
|
||||
} else {
|
||||
// When there are multiple items being uploaded, automatically pick the media types for each item. We probably want to allow the user to pick the media type in the future.
|
||||
await this.#createMediaItems(uploadableItems);
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated Please use `createTemporaryFiles()` instead; this method will be removed in Umbraco 17. */
|
||||
public createFilesAsTemporary = this.createTemporaryFiles;
|
||||
|
||||
/**
|
||||
* Uploads the files as temporary files and returns the data.
|
||||
* @param files
|
||||
* @returns Promise<Array<UmbUploadableFileModel>>
|
||||
* @param { File[] } files - The files to upload.
|
||||
* @returns {Promise<Array<UmbUploadableFileModel>>} - Files as temporary files.
|
||||
*/
|
||||
public async createFilesAsTemporary(files: Array<File>): Promise<Array<UmbTemporaryFileModel>> {
|
||||
this.#completed.setValue([]);
|
||||
const temporaryFiles: Array<UmbTemporaryFileModel> = [];
|
||||
public async createTemporaryFiles(files: Array<File>) {
|
||||
const uploadableItems = (await this.#setupProgress({ files, folders: [] }, null)) as Array<UmbUploadableFile>;
|
||||
|
||||
for (const file of files) {
|
||||
const uploaded = await this.#tempFileManager.uploadOne({ temporaryUnique: UmbId.new(), file });
|
||||
this.#completed.setValue([...this.#completed.getValue(), uploaded]);
|
||||
temporaryFiles.push(uploaded);
|
||||
}
|
||||
const uploadedItems: Array<UmbTemporaryFileModel> = [];
|
||||
|
||||
return temporaryFiles;
|
||||
}
|
||||
for (const item of uploadableItems) {
|
||||
// Upload as temp file
|
||||
const uploaded = await this.#tempFileManager.uploadOne({
|
||||
temporaryUnique: item.temporaryFile.temporaryUnique,
|
||||
file: item.temporaryFile.file,
|
||||
});
|
||||
|
||||
/**
|
||||
* Uploads files to the server and creates the items with corresponding media type.
|
||||
* Allows the user to pick a media type option if multiple types are allowed.
|
||||
* @param files
|
||||
* @param parentUnique
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
public async createFilesAsMedia(files: Array<File>, parentUnique: string | null) {
|
||||
if (!files.length) return;
|
||||
if (files.length === 1) return this.#handleOneOneFile(files[0], parentUnique);
|
||||
// Update progress
|
||||
const progress = this.#progress.getValue();
|
||||
this.#progress.update({ completed: progress.completed + 1 });
|
||||
|
||||
// Handler for multiple files dropped
|
||||
|
||||
this.#completed.setValue([]);
|
||||
// removes duplicate file types so we don't call endpoints unnecessarily when building options.
|
||||
const mimeTypes = [...new Set(files.map<string>((file) => file.type))];
|
||||
const optionsArray = await this.#buildOptionsArrayFrom(
|
||||
mimeTypes.map((mimetype) => this.#getExtensionFromMime(mimetype)),
|
||||
parentUnique,
|
||||
);
|
||||
|
||||
if (!optionsArray.length) return; // None of the files are allowed in current dropzone.
|
||||
|
||||
// Building an array of uploadable files. Do we want to build an array of failed files to let the user know which ones?
|
||||
const uploadableFiles: Array<UmbUploadableFileModel> = [];
|
||||
const notAllowedFiles: Array<File> = [];
|
||||
|
||||
for (const file of files) {
|
||||
const extension = this.#getExtensionFromMime(file.type);
|
||||
if (!extension) {
|
||||
// Folders have no extension on file drop. We assume it is a folder being uploaded.
|
||||
continue;
|
||||
}
|
||||
const options = optionsArray.find((option) => option.fileExtension === extension)?.mediaTypes;
|
||||
|
||||
if (!options || !options.length) {
|
||||
// TODO Current dropped file not allowed in this area. Find a good way to show this to the user after we finish uploading the rest of the files.
|
||||
notAllowedFiles.push(file);
|
||||
continue;
|
||||
if (uploaded.status === TemporaryFileStatus.SUCCESS) {
|
||||
this.#progressItems.updateOne(item.unique, { status: UmbFileDropzoneItemStatus.COMPLETE });
|
||||
} else {
|
||||
this.#progressItems.updateOne(item.unique, { status: UmbFileDropzoneItemStatus.ERROR });
|
||||
}
|
||||
|
||||
// Since we are uploading multiple files, we will pick first allowed option.
|
||||
// Consider a way we can handle this differently in the future to let the user choose. Maybe a list of all files with an allowed media type dropdown?
|
||||
const mediaType = options[0];
|
||||
uploadableFiles.push({
|
||||
temporaryUnique: UmbId.new(),
|
||||
file,
|
||||
mediaTypeUnique: mediaType.unique,
|
||||
unique: UmbId.new(),
|
||||
});
|
||||
// Add to return value
|
||||
uploadedItems.push(uploaded);
|
||||
}
|
||||
|
||||
notAllowedFiles.forEach((file) => {
|
||||
// TODO: It seems like some implementation(user feedback) is missing here? [NL]
|
||||
console.error(`File ${file.name} of type ${file.type} is not allowed here.`);
|
||||
});
|
||||
|
||||
if (!uploadableFiles.length) return;
|
||||
|
||||
await this.#handleUpload(uploadableFiles, parentUnique);
|
||||
}
|
||||
|
||||
async #handleOneOneFile(file: File, parentUnique: string | null) {
|
||||
this.#completed.setValue([]);
|
||||
const extension = this.#getExtensionFromMime(file.type);
|
||||
|
||||
if (!extension) {
|
||||
// TODO Folders have no extension on file drop. Assume it is a folder being uploaded.
|
||||
return;
|
||||
}
|
||||
|
||||
const optionsArray = await this.#buildOptionsArrayFrom([extension], parentUnique);
|
||||
if (!optionsArray.length || !optionsArray[0].mediaTypes.length) {
|
||||
throw new Error(`File ${file.name} of type ${file.type} is not allowed here.`); // Parent does not allow this file type here.
|
||||
}
|
||||
|
||||
const mediaTypes = optionsArray[0].mediaTypes;
|
||||
if (mediaTypes.length === 1) {
|
||||
// Only one allowed option, upload file using that option.
|
||||
const uploadableFile: UmbUploadableFileModel = {
|
||||
unique: UmbId.new(),
|
||||
temporaryUnique: UmbId.new(),
|
||||
file,
|
||||
mediaTypeUnique: mediaTypes[0].unique,
|
||||
};
|
||||
|
||||
await this.#handleUpload([uploadableFile], parentUnique);
|
||||
return;
|
||||
}
|
||||
|
||||
// Multiple options, show a dialog for the user to pick one.
|
||||
const mediaType = await this.#showDialogMediaTypePicker(mediaTypes);
|
||||
if (!mediaType) return; // Upload cancelled.
|
||||
|
||||
const uploadableFile: UmbUploadableFileModel = {
|
||||
unique: UmbId.new(),
|
||||
temporaryUnique: UmbId.new(),
|
||||
file,
|
||||
mediaTypeUnique: mediaType.unique,
|
||||
};
|
||||
await this.#handleUpload([uploadableFile], parentUnique);
|
||||
}
|
||||
|
||||
#getExtensionFromMime(mime: string): string {
|
||||
//TODO Temporary solution.
|
||||
if (!mime) return ''; //folders
|
||||
const extension = mime.split('/')[1];
|
||||
switch (extension) {
|
||||
case 'svg+xml':
|
||||
return 'svg';
|
||||
default:
|
||||
return extension;
|
||||
}
|
||||
}
|
||||
|
||||
async #buildOptionsArrayFrom(
|
||||
fileExtensions: Array<string>,
|
||||
parentUnique: string | null,
|
||||
): Promise<Array<UmbUploadableExtensionModel>> {
|
||||
let parentMediaType: string | null = null;
|
||||
if (parentUnique) {
|
||||
const { data } = await this.#mediaDetailRepository.requestByUnique(parentUnique);
|
||||
parentMediaType = data?.mediaType.unique ?? null;
|
||||
}
|
||||
|
||||
// Getting all media types allowed in our current position based on parent's media type.
|
||||
|
||||
const { data: allAllowedMediaTypes } = await this.#mediaTypeStructure.requestAllowedChildrenOf(parentMediaType);
|
||||
if (!allAllowedMediaTypes?.items.length) return [];
|
||||
|
||||
const allowedByParent = allAllowedMediaTypes.items;
|
||||
|
||||
// Building an array of options the files can be uploaded as.
|
||||
const options: Array<UmbUploadableExtensionModel> = [];
|
||||
|
||||
for (const fileExtension of fileExtensions) {
|
||||
const extensionOptions = await this.#mediaTypeStructure.requestMediaTypesOf({ fileExtension });
|
||||
const mediaTypes = extensionOptions.filter((option) => {
|
||||
return allowedByParent.find((allowed) => option.unique === allowed.unique);
|
||||
});
|
||||
options.push({ fileExtension, mediaTypes });
|
||||
}
|
||||
return options;
|
||||
return uploadedItems;
|
||||
}
|
||||
|
||||
async #showDialogMediaTypePicker(options: Array<UmbAllowedMediaTypeModel>) {
|
||||
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
|
||||
const modalContext = modalManager.open(this.#host, UMB_DROPZONE_MEDIA_TYPE_PICKER_MODAL, { data: { options } });
|
||||
const value = await modalContext.onSubmit().catch(() => undefined);
|
||||
return value ? { unique: value.mediaTypeUnique ?? options[0].unique } : null;
|
||||
return value?.mediaTypeUnique;
|
||||
}
|
||||
|
||||
async #handleUpload(files: Array<UmbUploadableFileModel>, parentUnique: string | null) {
|
||||
for (const file of files) {
|
||||
const upload = (await this.#tempFileManager.uploadOne(file)) as UmbUploadableFileModel;
|
||||
async #createOneMediaItem(item: UmbUploadableItem) {
|
||||
const options = await this.#getMediaTypeOptions(item);
|
||||
if (!options.length) {
|
||||
return this.#updateProgress(item, UmbFileDropzoneItemStatus.NOT_ALLOWED);
|
||||
}
|
||||
|
||||
if (upload.status === TemporaryFileStatus.SUCCESS) {
|
||||
// Upload successful. Create media item.
|
||||
// TODO: Use a scaffolding feature to ensure consistency. [NL]
|
||||
const preset: Partial<UmbMediaDetailModel> = {
|
||||
unique: file.unique,
|
||||
mediaType: {
|
||||
unique: upload.mediaTypeUnique,
|
||||
collection: null,
|
||||
},
|
||||
variants: [
|
||||
{
|
||||
culture: null,
|
||||
segment: null,
|
||||
name: upload.file.name,
|
||||
createDate: null,
|
||||
updateDate: null,
|
||||
},
|
||||
],
|
||||
values: [
|
||||
{
|
||||
// We do not need to parse the right editorAlias here, because the server does not read it. If we need to parse it we would need to load the contentType to make this happen properly. [NL]
|
||||
editorAlias: null as any,
|
||||
alias: 'umbracoFile',
|
||||
value: { temporaryFileId: upload.temporaryUnique },
|
||||
culture: null,
|
||||
segment: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
const { data } = await this.#mediaDetailRepository.createScaffold(preset);
|
||||
await this.#mediaDetailRepository.create(data!, parentUnique);
|
||||
}
|
||||
// TODO Find a good way to show files that ended up as TemporaryFileStatus.ERROR. Notice that they were allowed in current area
|
||||
const mediaTypeUnique = options.length > 1 ? await this.#showDialogMediaTypePicker(options) : options[0].unique;
|
||||
|
||||
this.#completed.setValue([...this.#completed.getValue(), upload]);
|
||||
if (!mediaTypeUnique) {
|
||||
return this.#updateProgress(item, UmbFileDropzoneItemStatus.CANCELLED);
|
||||
}
|
||||
|
||||
if (item.temporaryFile) {
|
||||
this.#handleFile(item as UmbUploadableFile, mediaTypeUnique);
|
||||
} else if (item.folder) {
|
||||
this.#handleFolder(item as UmbUploadableFolder, mediaTypeUnique);
|
||||
}
|
||||
}
|
||||
|
||||
private _reset() {
|
||||
//
|
||||
async #createMediaItems(uploadableItems: Array<UmbUploadableItem>) {
|
||||
for (const item of uploadableItems) {
|
||||
const options = await this.#getMediaTypeOptions(item);
|
||||
if (!options.length) {
|
||||
this.#updateProgress(item, UmbFileDropzoneItemStatus.NOT_ALLOWED);
|
||||
continue;
|
||||
}
|
||||
|
||||
const mediaTypeUnique = options[0].unique;
|
||||
|
||||
// Handle files and folders differently: a file is uploaded as temp then created as a media item, and a folder is created as a media item directly
|
||||
if (item.temporaryFile) {
|
||||
await this.#handleFile(item as UmbUploadableFile, mediaTypeUnique);
|
||||
} else if (item.folder) {
|
||||
await this.#handleFolder(item as UmbUploadableFolder, mediaTypeUnique);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async #handleFile(item: UmbUploadableFile, mediaTypeUnique: string) {
|
||||
// Upload the file as a temporary file and update progress.
|
||||
const temporaryFile = await this.#uploadAsTemporaryFile(item);
|
||||
if (temporaryFile.status !== TemporaryFileStatus.SUCCESS) {
|
||||
this.#updateProgress(item, UmbFileDropzoneItemStatus.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the media item.
|
||||
const scaffold = await this.#getItemScaffold(item, mediaTypeUnique);
|
||||
const { data } = await this.#mediaDetailRepository.create(scaffold, item.parentUnique);
|
||||
|
||||
if (data) {
|
||||
this.#updateProgress(item, UmbFileDropzoneItemStatus.COMPLETE);
|
||||
} else {
|
||||
this.#updateProgress(item, UmbFileDropzoneItemStatus.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async #handleFolder(item: UmbUploadableFolder, mediaTypeUnique: string) {
|
||||
const scaffold = await this.#getItemScaffold(item, mediaTypeUnique);
|
||||
const { data } = await this.#mediaDetailRepository.create(scaffold, item.parentUnique);
|
||||
if (data) {
|
||||
this.#updateProgress(item, UmbFileDropzoneItemStatus.COMPLETE);
|
||||
} else {
|
||||
this.#updateProgress(item, UmbFileDropzoneItemStatus.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async #uploadAsTemporaryFile(item: UmbUploadableFile) {
|
||||
return await this.#tempFileManager.uploadOne({
|
||||
temporaryUnique: item.temporaryFile.temporaryUnique,
|
||||
file: item.temporaryFile.file,
|
||||
});
|
||||
}
|
||||
|
||||
// Media types
|
||||
async #getMediaTypeOptions(item: UmbUploadableItem): Promise<Array<UmbAllowedMediaTypeModel>> {
|
||||
// Check the parent which children media types are allowed
|
||||
const parent = item.parentUnique ? await this.#mediaDetailRepository.requestByUnique(item.parentUnique) : null;
|
||||
const allowedChildren = await this.#getAllowedChildrenOf(parent?.data?.mediaType.unique ?? null);
|
||||
|
||||
const extension = item.temporaryFile?.file.name.split('.').pop() ?? null;
|
||||
|
||||
// Check which media types allow the file's extension
|
||||
const availableMediaType = await this.#getAvailableMediaTypesOf(extension);
|
||||
|
||||
if (!availableMediaType.length) return [];
|
||||
|
||||
const options = allowedChildren.filter((x) => availableMediaType.find((y) => y.unique === x.unique));
|
||||
return options;
|
||||
}
|
||||
|
||||
async #getAvailableMediaTypesOf(extension: string | null) {
|
||||
// Check if we already have information on this file extension.
|
||||
const available = this.#availableMediaTypesOf
|
||||
.getValue()
|
||||
.find((x) => x.extension === extension)?.availableMediaTypes;
|
||||
if (available) return available;
|
||||
|
||||
// Request information on this file extension
|
||||
const availableMediaTypes = extension
|
||||
? await this.#mediaTypeStructure.requestMediaTypesOf({ fileExtension: extension })
|
||||
: await this.#mediaTypeStructure.requestMediaTypesOfFolders();
|
||||
|
||||
this.#availableMediaTypesOf.appendOne({ extension, availableMediaTypes });
|
||||
return availableMediaTypes;
|
||||
}
|
||||
|
||||
async #getAllowedChildrenOf(mediaTypeUnique: string | null) {
|
||||
//Check if we already got information on this media type.
|
||||
const allowed = this.#allowedChildrenOf
|
||||
.getValue()
|
||||
.find((x) => x.mediaTypeUnique === mediaTypeUnique)?.allowedChildren;
|
||||
if (allowed) return allowed;
|
||||
|
||||
// Request information on this media type.
|
||||
const { data } = await this.#mediaTypeStructure.requestAllowedChildrenOf(mediaTypeUnique);
|
||||
if (!data) throw new Error('Parent media type does not exists');
|
||||
|
||||
this.#allowedChildrenOf.appendOne({ mediaTypeUnique, allowedChildren: data.items });
|
||||
return data.items;
|
||||
}
|
||||
|
||||
// Scaffold
|
||||
async #getItemScaffold(item: UmbUploadableItem, mediaTypeUnique: string): Promise<UmbMediaDetailModel> {
|
||||
// TODO: Use a scaffolding feature to ensure consistency. [NL]
|
||||
const name = item.temporaryFile ? item.temporaryFile.file.name : (item.folder?.name ?? '');
|
||||
const umbracoFile: UmbMediaValueModel = {
|
||||
editorAlias: null as any,
|
||||
alias: 'umbracoFile',
|
||||
value: { temporaryFileId: item.temporaryFile?.temporaryUnique },
|
||||
culture: null,
|
||||
segment: null,
|
||||
};
|
||||
|
||||
const preset: Partial<UmbMediaDetailModel> = {
|
||||
unique: item.unique,
|
||||
mediaType: { unique: mediaTypeUnique, collection: null },
|
||||
variants: [{ culture: null, segment: null, createDate: null, updateDate: null, name }],
|
||||
values: item.temporaryFile ? [umbracoFile] : undefined,
|
||||
};
|
||||
const { data } = await this.#mediaDetailRepository.createScaffold(preset);
|
||||
return data!;
|
||||
}
|
||||
|
||||
// Progress handling
|
||||
async #setupProgress(items: UmbFileDropzoneDroppedItems, parent: string | null) {
|
||||
const current = this.#progress.getValue();
|
||||
const currentItems = this.#progressItems.getValue();
|
||||
|
||||
const uploadableItems = this.#prepareItemsAsUploadable({ folders: items.folders, files: items.files }, parent);
|
||||
|
||||
this.#progressItems.setValue([...currentItems, ...uploadableItems]);
|
||||
this.#progress.setValue({ total: current.total + uploadableItems.length, completed: current.completed });
|
||||
|
||||
return uploadableItems;
|
||||
}
|
||||
|
||||
#updateProgress(item: UmbUploadableItem, status: UmbFileDropzoneItemStatus) {
|
||||
this.#progressItems.updateOne(item.unique, { status });
|
||||
const progress = this.#progress.getValue();
|
||||
this.#progress.update({ completed: progress.completed + 1 });
|
||||
}
|
||||
|
||||
readonly #prepareItemsAsUploadable = (
|
||||
{ folders, files }: UmbFileDropzoneDroppedItems,
|
||||
parentUnique: string | null,
|
||||
): Array<UmbUploadableItem> => {
|
||||
const items: Array<UmbUploadableItem> = [];
|
||||
|
||||
for (const file of files) {
|
||||
const unique = UmbId.new();
|
||||
if (file.type) {
|
||||
items.push({
|
||||
unique,
|
||||
parentUnique,
|
||||
status: UmbFileDropzoneItemStatus.WAITING,
|
||||
temporaryFile: { file, temporaryUnique: UmbId.new() },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const subfolder of folders) {
|
||||
const unique = UmbId.new();
|
||||
items.push({
|
||||
unique,
|
||||
parentUnique,
|
||||
status: UmbFileDropzoneItemStatus.WAITING,
|
||||
folder: { name: subfolder.folderName },
|
||||
});
|
||||
|
||||
items.push(...this.#prepareItemsAsUploadable({ folders: subfolder.folders, files: subfolder.files }, unique));
|
||||
}
|
||||
return items;
|
||||
};
|
||||
|
||||
public override destroy() {
|
||||
this.#tempFileManager.destroy();
|
||||
this.#completed.destroy();
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { UmbDropzoneManager, type UmbUploadableFileModel } from './dropzone-manager.class.js';
|
||||
import { UmbProgressEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UUIFileDropzoneElement, UUIFileDropzoneEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UmbDropzoneManager } from './dropzone-manager.class.js';
|
||||
import { UmbFileDropzoneItemStatus, type UmbUploadableItem } from './types.js';
|
||||
import { css, customElement, html, ifDefined, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import type { UmbTemporaryFileModel } from '@umbraco-cms/backoffice/temporary-file';
|
||||
import type { UUIFileDropzoneElement, UUIFileDropzoneEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
|
||||
@customElement('umb-dropzone')
|
||||
export class UmbDropzoneElement extends UmbLitElement {
|
||||
@@ -16,24 +15,47 @@ export class UmbDropzoneElement extends UmbLitElement {
|
||||
@property({ type: Boolean })
|
||||
createAsTemporary: boolean = false;
|
||||
|
||||
@property({ type: Array, attribute: false })
|
||||
accept: Array<string> = [];
|
||||
@property({ type: String })
|
||||
accept?: string;
|
||||
|
||||
//TODO: logic to disable the dropzone?
|
||||
@property({ type: Boolean, reflect: true })
|
||||
disabled = false;
|
||||
|
||||
#files: Array<UmbUploadableFileModel | UmbTemporaryFileModel> = [];
|
||||
@property({ type: Boolean, attribute: 'disable-folder-upload', reflect: true })
|
||||
public get disableFolderUpload() {
|
||||
return this._disableFolderUpload;
|
||||
}
|
||||
public set disableFolderUpload(isAllowed: boolean) {
|
||||
this.dropzoneManager.setIsFoldersAllowed(!isAllowed);
|
||||
}
|
||||
private readonly _disableFolderUpload = false;
|
||||
|
||||
@state()
|
||||
private _progressItems: Array<UmbUploadableItem> = [];
|
||||
|
||||
public dropzoneManager: UmbDropzoneManager;
|
||||
|
||||
/**
|
||||
* @deprecated Please use `getItems()` instead; this method will be removed in Umbraco 17.
|
||||
* @returns {Array<UmbUploadableItem>} An array of uploadable items.
|
||||
*/
|
||||
public getFiles() {
|
||||
return this.#files;
|
||||
return this.getItems();
|
||||
}
|
||||
|
||||
public getItems() {
|
||||
return this._progressItems;
|
||||
}
|
||||
|
||||
public browse() {
|
||||
if (this.disabled) return;
|
||||
const element = this.shadowRoot?.querySelector('#dropzone') as UUIFileDropzoneElement;
|
||||
return element.browse();
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.dropzoneManager = new UmbDropzoneManager(this);
|
||||
document.addEventListener('dragenter', this.#handleDragEnter.bind(this));
|
||||
document.addEventListener('dragleave', this.#handleDragLeave.bind(this));
|
||||
document.addEventListener('drop', this.#handleDrop.bind(this));
|
||||
@@ -41,70 +63,73 @@ export class UmbDropzoneElement extends UmbLitElement {
|
||||
|
||||
override disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this.dropzoneManager.destroy();
|
||||
document.removeEventListener('dragenter', this.#handleDragEnter.bind(this));
|
||||
document.removeEventListener('dragleave', this.#handleDragLeave.bind(this));
|
||||
document.removeEventListener('drop', this.#handleDrop.bind(this));
|
||||
}
|
||||
|
||||
#handleDragEnter(e: DragEvent) {
|
||||
if (this.disabled) return;
|
||||
// Avoid collision with UmbSorterController
|
||||
const types = e.dataTransfer?.types;
|
||||
if (!types?.length || !types?.includes('Files')) return;
|
||||
|
||||
this.toggleAttribute('dragging', true);
|
||||
}
|
||||
|
||||
#handleDragLeave() {
|
||||
if (this.disabled) return;
|
||||
this.toggleAttribute('dragging', false);
|
||||
}
|
||||
|
||||
#handleDrop(event: DragEvent) {
|
||||
event.preventDefault();
|
||||
if (this.disabled) return;
|
||||
this.toggleAttribute('dragging', false);
|
||||
}
|
||||
|
||||
async #onDropFiles(event: UUIFileDropzoneEvent) {
|
||||
// TODO Handle of folder uploads.
|
||||
if (this.disabled) return;
|
||||
if (!event.detail.files.length && !event.detail.folders.length) return;
|
||||
|
||||
const files: Array<File> = event.detail.files;
|
||||
if (!files.length) return;
|
||||
// TODO Create some placeholder items while files are being uploaded? Could update them as they get completed.
|
||||
// We can observe progressItems and check for any files that did not succeed, then show some kind of dialog to the user with the information.
|
||||
|
||||
const dropzoneManager = new UmbDropzoneManager(this);
|
||||
this.observe(
|
||||
dropzoneManager.completed,
|
||||
(completed) => {
|
||||
if (!completed.length) return;
|
||||
|
||||
const progress = Math.floor(completed.length / files.length);
|
||||
this.dispatchEvent(new UmbProgressEvent(progress));
|
||||
|
||||
if (completed.length === files.length) {
|
||||
this.#files = completed;
|
||||
this.dispatchEvent(new CustomEvent('change', { detail: { completed } }));
|
||||
dropzoneManager.destroy();
|
||||
}
|
||||
},
|
||||
'_observeCompleted',
|
||||
this.dropzoneManager.progress,
|
||||
(progress) =>
|
||||
this.dispatchEvent(new ProgressEvent('progress', { loaded: progress.completed, total: progress.total })),
|
||||
'_observeProgress',
|
||||
);
|
||||
//TODO Create some placeholder items while files are being uploaded? Could update them as they get completed.
|
||||
|
||||
this.observe(this.dropzoneManager.progressItems, (progressItems: Array<UmbUploadableItem>) => {
|
||||
this._progressItems = progressItems;
|
||||
const waiting = progressItems.find((item) => item.status === UmbFileDropzoneItemStatus.WAITING);
|
||||
if (progressItems.length && !waiting) {
|
||||
this.dispatchEvent(new CustomEvent('complete', { detail: progressItems }));
|
||||
}
|
||||
});
|
||||
|
||||
if (this.createAsTemporary) {
|
||||
await dropzoneManager.createFilesAsTemporary(files);
|
||||
this.dropzoneManager.createTemporaryFiles(event.detail.files);
|
||||
} else {
|
||||
await dropzoneManager.createFilesAsMedia(files, this.parentUnique);
|
||||
this.dropzoneManager.createMediaItems(event.detail, this.parentUnique);
|
||||
}
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html`<uui-file-dropzone
|
||||
id="dropzone"
|
||||
.accept=${this.accept?.join(',')}
|
||||
accept=${ifDefined(this.accept)}
|
||||
?multiple=${this.multiple}
|
||||
@change=${this.#onDropFiles}
|
||||
label="${this.localize.term('media_dragAndDropYourFilesIntoTheArea')}"></uui-file-dropzone>`;
|
||||
label=${this.localize.term('media_dragAndDropYourFilesIntoTheArea')}></uui-file-dropzone>`;
|
||||
}
|
||||
|
||||
static override styles = [
|
||||
css`
|
||||
:host([dragging]) #dropzone {
|
||||
:host(:not([disabled])[dragging]) #dropzone {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { UUIFileFolder } from '@umbraco-cms/backoffice/external/uui';
|
||||
import type { UmbAllowedMediaTypeModel } from '@umbraco-cms/backoffice/media-type';
|
||||
import type { UmbTemporaryFileModel } from '@umbraco-cms/backoffice/temporary-file';
|
||||
|
||||
export interface UmbFileDropzoneDroppedItems {
|
||||
files: Array<File>;
|
||||
folders: Array<UUIFileFolder>;
|
||||
}
|
||||
|
||||
export interface UmbUploadableItem {
|
||||
unique: string;
|
||||
parentUnique: string | null;
|
||||
status: UmbFileDropzoneItemStatus;
|
||||
folder?: { name: string };
|
||||
temporaryFile?: UmbTemporaryFileModel;
|
||||
}
|
||||
|
||||
export interface UmbUploadableFile extends UmbUploadableItem {
|
||||
temporaryFile: UmbTemporaryFileModel;
|
||||
}
|
||||
|
||||
export interface UmbUploadableFolder extends UmbUploadableItem {
|
||||
folder: { name: string };
|
||||
}
|
||||
|
||||
export interface UmbAllowedMediaTypesOfExtension {
|
||||
extension: string | null; // Null is considered a folder.
|
||||
availableMediaTypes: Array<UmbAllowedMediaTypeModel>;
|
||||
}
|
||||
|
||||
export interface UmbAllowedChildrenOfMediaType {
|
||||
mediaTypeUnique: string | null;
|
||||
allowedChildren: Array<UmbAllowedMediaTypeModel>;
|
||||
}
|
||||
|
||||
export interface UmbFileDropzoneProgress {
|
||||
total: number;
|
||||
completed: number;
|
||||
}
|
||||
|
||||
export enum UmbFileDropzoneItemStatus {
|
||||
WAITING = 'waiting',
|
||||
COMPLETE = 'complete',
|
||||
NOT_ALLOWED = 'not allowed',
|
||||
CANCELLED = 'cancelled',
|
||||
ERROR = 'error',
|
||||
}
|
||||
@@ -168,7 +168,7 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement<
|
||||
|
||||
#renderBody() {
|
||||
return html`${this.#renderToolbar()}
|
||||
<umb-dropzone id="dropzone" @change=${() => this.#loadMediaFolder()} .parentUnique=${this._currentMediaEntity.unique}></umb-dropzone>
|
||||
<umb-dropzone id="dropzone" @complete=${() => this.#loadMediaFolder()} .parentUnique=${this._currentMediaEntity.unique}></umb-dropzone>
|
||||
${
|
||||
!this._mediaFilteredList.length
|
||||
? html`<div class="container"><p>${this.localize.term('content_listViewNoItems')}</p></div>`
|
||||
|
||||
Reference in New Issue
Block a user