Compare commits

...
Author SHA1 Message Date
Jacob Overgaard 990a1315ac feat: make the media manager an independent controller 2025-03-26 14:03:49 +01:00
Jacob Overgaard a5f80ef42a feat: make the media manager an independent controller 2025-03-26 13:21:58 +01:00
Jacob Overgaard 51d34c050c do not autoconfigure dropzone package 2025-03-26 11:01:16 +01:00
Jacob Overgaard e000b93ffa correct import paths 2025-03-26 10:38:39 +01:00
Jacob Overgaard 00357674c1 remove remnants of old package 2025-03-26 10:30:57 +01:00
Jacob Overgaard 99f3bb4466 Merge remote-tracking branch 'origin/v15/dev' into v15/feature/dropzone-package 2025-03-26 10:28:38 +01:00
Jacob Overgaard 3872268ea7 feat: creates dropzone root package 2025-03-26 10:28:31 +01:00
Jacob Overgaard 6945b457a8 feat: makes the manager overrideable and ensures the media manager awaits the upload of temporary files 2025-03-26 10:19:44 +01:00
Jacob Overgaard 236d0be53e moves parentUnique property back on the input element 2025-03-26 09:59:10 +01:00
Jacob Overgaard 9149139a36 Merge remote-tracking branch 'origin/v15/dev' into v15/feature/dropzone-package 2025-03-26 09:18:25 +01:00
Jacob Overgaard 4211c962f3 feat: move the dropzone package and creating a root package 2025-03-26 09:17:30 +01:00
Jacob Overgaard 35e9eebf33 Merge remote-tracking branch 'origin/v15/dev' into v15/feature/dropzone-extend 2025-03-25 14:35:56 +01:00
Jacob Overgaard e713883c84 fix: removes overflow to allow full border 2025-03-25 14:34:45 +01:00
Jacob Overgaard 91723945f0 remove unused import 2025-03-25 11:32:00 +01:00
Jacob Overgaard 908582d83a feat: adds back a text to describe where to drop files 2025-03-25 11:21:48 +01:00
Jacob Overgaard db9d47058f feat: the dropzone should fill out its host component 2025-03-25 11:21:37 +01:00
Jacob Overgaard 9b0ce81df0 feat: adds a slot to show an additional text above the dropzone graphics 2025-03-25 11:14:12 +01:00
Jacob Overgaard 67cf1e0e89 adds export for dropzone 2025-03-25 11:09:33 +01:00
Jacob Overgaard c7e74d0659 feat: use umb-dropzone-media instead 2025-03-25 11:05:38 +01:00
Jacob Overgaard 7574b36d2e feat: adds umb-dropzone-media element and deprecates umb-dropzone element 2025-03-25 11:03:19 +01:00
Jacob Overgaard 662ce99238 docs(storybook): remove old argument 2025-03-24 17:16:53 +01:00
Jacob Overgaard 2e7a6dedb0 fix: document and media type import should use other dropzone 2025-03-24 17:10:24 +01:00
Jacob Overgaard 12a817dc08 use correct import 2025-03-24 17:09:09 +01:00
Jacob Overgaard 44601a5204 removes unused export 2025-03-24 17:07:30 +01:00
Jacob Overgaard e404295151 feat: adds a browse() method 2025-03-24 16:53:17 +01:00
Jacob Overgaard d0c35e740f chore: sort imports 2025-03-24 16:51:08 +01:00
Jacob Overgaard 322d29b8ef Merge remote-tracking branch 'origin/v15/dev' into v15/feature/dropzone-extend 2025-03-24 16:49:24 +01:00
Jacob Overgaard ec5fd89c24 feat: adds a UmbDropzoneMediaManager class to handle media specifically 2025-03-24 16:49:17 +01:00
Jacob Overgaard e217246d99 feat: umb-dropzone should extend umb-input-dropzone
moves the umb-dropzone back into the media package and extends the umb-input-dropzone for common logic
2025-03-24 16:43:46 +01:00
32 changed files with 500 additions and 475 deletions
+7
View File
@@ -4338,6 +4338,10 @@
"resolved": "src/packages/documents",
"link": true
},
"node_modules/@umbraco-backoffice/dropzone": {
"resolved": "src/packages/dropzone",
"link": true
},
"node_modules/@umbraco-backoffice/embedded-media": {
"resolved": "src/packages/embedded-media",
"link": true
@@ -17701,6 +17705,9 @@
"src/packages/documents": {
"name": "@umbraco-backoffice/document"
},
"src/packages/dropzone": {
"name": "@umbraco-backoffice/dropzone"
},
"src/packages/embedded-media": {
"name": "@umbraco-backoffice/embedded-media"
},
+2 -2
View File
@@ -41,7 +41,7 @@
"./document-blueprint": "./dist-cms/packages/documents/document-blueprints/index.js",
"./document-type": "./dist-cms/packages/documents/document-types/index.js",
"./document": "./dist-cms/packages/documents/documents/index.js",
"./dropzone": "./dist-cms/packages/media/dropzone/index.js",
"./dropzone": "./dist-cms/packages/dropzone/dropzone/index.js",
"./entity-action": "./dist-cms/packages/core/entity-action/index.js",
"./entity-bulk-action": "./dist-cms/packages/core/entity-bulk-action/index.js",
"./entity-create-option-action": "./dist-cms/packages/core/entity-create-option-action/index.js",
@@ -296,4 +296,4 @@
"access": "public",
"registry": "https://registry.npmjs.org/"
}
}
}
@@ -1,4 +1,4 @@
import type { UmbUploadableItem } from '../../types.js';
import type { UmbFileDropzoneDroppedItems, UmbUploadableItem } from '../../types.js';
import { UmbFileDropzoneItemStatus } from '../../constants.js';
import { UmbDropzoneManager } from '../../dropzone-manager.class.js';
import { UmbDropzoneChangeEvent } from '../../dropzone-change.event.js';
@@ -35,6 +35,12 @@ import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
export class UmbInputDropzoneElement extends UmbFormControlMixin<UmbUploadableItem[], typeof UmbLitElement>(
UmbLitElement,
) {
/**
* The parent of the uploaded items once they are moved from the temporary folder.
*/
@property({ attribute: 'parent-unique' })
parentUnique: string | null = null;
/**
* Comma-separated list of accepted mime types or file extensions.
*/
@@ -46,7 +52,7 @@ export class UmbInputDropzoneElement extends UmbFormControlMixin<UmbUploadableIt
*/
@property({ type: Boolean, attribute: 'disable-folder-upload', reflect: true })
public set disableFolderUpload(isAllowed: boolean) {
this.#manager.setIsFoldersAllowed(!isAllowed);
this._manager.setIsFoldersAllowed(!isAllowed);
}
public get disableFolderUpload() {
return this._disableFolderUpload;
@@ -78,22 +84,22 @@ export class UmbInputDropzoneElement extends UmbFormControlMixin<UmbUploadableIt
@state()
protected _progressItems: Array<UmbUploadableItem> = [];
#manager = new UmbDropzoneManager(this);
protected _manager = new UmbDropzoneManager(this);
constructor() {
super();
this.observe(
this.#manager.progress,
this._manager.progress,
(progress) =>
this.dispatchEvent(new ProgressEvent('progress', { loaded: progress.completed, total: progress.total })),
'_observeProgress',
);
this.observe(
this.#manager.progressItems,
this._manager.progressItems,
(progressItems) => {
this._progressItems = [...progressItems];
this._progressItems = progressItems;
const waiting = this._progressItems.find((item) => item.status === UmbFileDropzoneItemStatus.WAITING);
if (this._progressItems.length && !waiting) {
this.value = [...this._progressItems];
@@ -106,7 +112,7 @@ export class UmbInputDropzoneElement extends UmbFormControlMixin<UmbUploadableIt
override disconnectedCallback(): void {
super.disconnectedCallback();
this.#manager.destroy();
this._manager.destroy();
}
/**
@@ -218,7 +224,12 @@ export class UmbInputDropzoneElement extends UmbFormControlMixin<UmbUploadableIt
if (this.disabled) return;
if (!e.detail.files.length && !e.detail.folders.length) return;
const uploadables = this.#manager.createTemporaryFiles(e.detail.files);
const droppedItems: UmbFileDropzoneDroppedItems = {
files: e.detail.files,
folders: e.detail.folders,
};
const uploadables = this._manager.createTemporaryFiles(droppedItems, this.parentUnique);
this.dispatchEvent(new UmbDropzoneSubmittedEvent(await uploadables));
}
@@ -233,7 +244,7 @@ export class UmbInputDropzoneElement extends UmbFormControlMixin<UmbUploadableIt
}
#handleRemove() {
this.#manager.removeAll();
this._manager.removeAll();
}
static override readonly styles = [
@@ -1,5 +1,3 @@
export { UMB_DROPZONE_MEDIA_TYPE_PICKER_MODAL } from './modals/dropzone-media-type-picker/dropzone-media-type-picker-modal.token.js';
export enum UmbFileDropzoneItemStatus {
WAITING = 'waiting',
COMPLETE = 'complete',
@@ -0,0 +1,196 @@
import { UmbFileDropzoneItemStatus } from './constants.js';
import type {
UmbFileDropzoneDroppedItems,
UmbFileDropzoneProgress,
UmbUploadableFile,
UmbUploadableFolder,
UmbUploadableItem,
} from './types.js';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
import { UmbId } from '@umbraco-cms/backoffice/id';
import { UmbArrayState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import {
TemporaryFileStatus,
UmbTemporaryFileManager,
type UmbTemporaryFileModel,
} from '@umbraco-cms/backoffice/temporary-file';
/**
* 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.
* @property {UmbObjectState<UmbFileDropzoneProgress>} progress - Emits the number of completed items and total items.
* @property {UmbArrayState<UmbUploadableItem>} progressItems - Emits the items with their current status.
*/
export class UmbDropzoneManager extends UmbControllerBase {
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();
#isFoldersAllowed = true;
#tempFileManager = new UmbTemporaryFileManager(this);
public setIsFoldersAllowed(isAllowed: boolean) {
this.#isFoldersAllowed = isAllowed;
}
public getIsFoldersAllowed(): boolean {
return this.#isFoldersAllowed;
}
/**
* Uploads the files as temporary files and returns the data.
* @param {UmbFileDropzoneDroppedItems} items - The items to upload.
* @param {string | null} parentUnique - The parent unique.
* @returns {Promise<Array<UmbUploadableItem>>} - Files as temporary files.
*/
public async createTemporaryFiles(
items: UmbFileDropzoneDroppedItems,
parentUnique?: string | null,
): Promise<Array<UmbUploadableItem>> {
const uploadableItems = this.#setupProgress(items, parentUnique ?? null);
const uploadedItems: Array<UmbUploadableItem> = [];
for (const item of uploadableItems) {
// Check if the item is a file
if (this.#isUploadableFile(item)) {
// Upload as temp file
const uploaded = await this.#tempFileManager.uploadOne(item.temporaryFile);
// Update progress
if (uploaded.status === TemporaryFileStatus.SUCCESS) {
this.updateStatus(item, UmbFileDropzoneItemStatus.COMPLETE);
} else {
this.updateStatus(item, UmbFileDropzoneItemStatus.ERROR);
}
}
// Add to return value
uploadedItems.push(item);
}
return uploadedItems;
}
public removeOne(item: UmbUploadableItem) {
item.temporaryFile?.abortController?.abort();
this.#progressItems.removeOne(item.unique);
if (item.temporaryFile) {
this.#tempFileManager.removeOne(item.temporaryFile.temporaryUnique);
}
}
public remove(items: Array<UmbUploadableItem>) {
const uniques: string[] = [];
for (const item of items) {
item.temporaryFile?.abortController?.abort();
if (item.temporaryFile) {
uniques.push(item.temporaryFile.temporaryUnique);
}
}
this.#progressItems.remove(uniques);
const temporaryUniques = items.map((x) => x.temporaryFile?.temporaryUnique).filter((x): x is string => !!x);
this.#tempFileManager.remove(temporaryUniques);
}
public removeAll() {
for (const item of this.#progressItems.getValue()) {
item.temporaryFile?.abortController?.abort();
}
this.#progressItems.setValue([]);
this.#tempFileManager.removeAll();
}
// Progress handling
#setupProgress(items: UmbFileDropzoneDroppedItems, parent: string | null) {
const current = this.#progress.getValue();
const uploadableItems = this.#prepareItemsAsUploadable(items, parent);
this.#progressItems.append(uploadableItems);
console.log(
'trying to append',
uploadableItems,
'to',
this.#progressItems,
'which now has the value of',
this.#progressItems.getValue(),
);
this.#progress.update({
total: current.total + uploadableItems.length,
});
return uploadableItems;
}
updateStatus(item: UmbUploadableItem, status: UmbFileDropzoneItemStatus) {
this.#progressItems.updateOne(item.unique, { status });
const progress = this.#progress.getValue();
this.#progress.update({ completed: progress.completed + 1 });
}
#isUploadableFile(item: UmbUploadableItem): item is UmbUploadableFile {
return 'temporaryFile' in item && item.temporaryFile !== undefined;
}
#updateProgress(item: UmbUploadableItem, progress: number) {
this.#progressItems.updateOne(item.unique, { progress });
}
#prepareItemsAsUploadable = (
{ folders, files }: UmbFileDropzoneDroppedItems,
parentUnique: string | null,
): Array<UmbUploadableItem> => {
const items: Array<UmbUploadableItem> = [];
for (const file of files) {
const temporaryFile: UmbTemporaryFileModel = {
file,
temporaryUnique: UmbId.new(),
abortController: new AbortController(),
onProgress: (progress) => this.#updateProgress(uploadableItem, progress),
};
const uploadableItem: UmbUploadableFile = {
unique: UmbId.new(),
parentUnique,
status: UmbFileDropzoneItemStatus.WAITING,
progress: 0,
temporaryFile,
};
temporaryFile.abortController?.signal.addEventListener('abort', () => {
this.updateStatus(uploadableItem, UmbFileDropzoneItemStatus.CANCELLED);
});
items.push(uploadableItem);
}
if (!this.getIsFoldersAllowed()) {
return items;
}
for (const subfolder of folders) {
const unique = UmbId.new();
items.push({
unique,
parentUnique,
status: UmbFileDropzoneItemStatus.WAITING,
progress: 100, // Folders are created instantly.
folder: { name: subfolder.folderName },
} satisfies UmbUploadableFolder);
items.push(...this.#prepareItemsAsUploadable({ folders: subfolder.folders, files: subfolder.files }, unique));
}
return items;
};
public override destroy() {
this.#tempFileManager.destroy();
super.destroy();
}
}
@@ -1,6 +1,5 @@
export * from './constants.js';
export * from './components/index.js';
export * from './modals/index.js';
export * from './dropzone-manager.class.js';
export * from './dropzone-submitted.event.js';
export * from './dropzone-change.event.js';
@@ -0,0 +1,8 @@
{
"name": "@umbraco-backoffice/dropzone",
"private": true,
"type": "module",
"scripts": {
"build": "vite build"
}
}
@@ -0,0 +1 @@
export const name = 'Umbraco.Core.Dropzone';
@@ -0,0 +1,17 @@
import { defineConfig } from 'vite';
import { rmSync } from 'fs';
import { getDefaultConfig } from '../../vite-config-base';
const dist = '../../../dist-cms/packages/dropzone';
// delete the unbundled dist folder
rmSync(dist, { recursive: true, force: true });
export default defineConfig({
...getDefaultConfig({
dist,
entry: {
'dropzone/index': 'dropzone/index.ts',
},
}),
});
@@ -1,396 +0,0 @@
import { UmbMediaDetailRepository } from '../media/repository/index.js';
import type { UmbMediaDetailModel, UmbMediaValueModel } from '../media/types.js';
import { UmbFileDropzoneItemStatus } from './constants.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,
type UmbTemporaryFileModel,
} from '@umbraco-cms/backoffice/temporary-file';
import { UmbArrayState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
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 type { UmbAllowedMediaTypeModel } from '@umbraco-cms/backoffice/media-type';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification';
import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api';
/**
* 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 {
readonly #host: UmbControllerHost;
#isFoldersAllowed = true;
#mediaTypeStructure = new UmbMediaTypeStructureRepository(this);
#mediaDetailRepository = new UmbMediaDetailRepository(this);
#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();
#notificationContext?: typeof UMB_NOTIFICATION_CONTEXT.TYPE;
#localization = new UmbLocalizationController(this);
constructor(host: UmbControllerHost) {
super(host);
this.#host = host;
this.consumeContext(UMB_NOTIFICATION_CONTEXT, (context) => {
this.#notificationContext = context;
});
}
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.
* @deprecated Use the {@link UmbDropzoneMediaManager} class instead. This will be removed in Umbraco 18.
* @param {UmbFileDropzoneDroppedItems} items - The files and folders to upload.
* @param {string | null} parentUnique - Where the items should be uploaded.
* @returns {Array<UmbUploadableItem>} - The items about to be uploaded.
*/
public createMediaItems(items: UmbFileDropzoneDroppedItems, parentUnique: string | null = null) {
const uploadableItems = this.#setupProgress(items, parentUnique);
if (!uploadableItems.length) return [];
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.
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.
this.#createMediaItems(uploadableItems);
}
return 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 { File[] } files - The files to upload.
* @returns {Promise<Array<UmbUploadableItem>>} - Files as temporary files.
*/
public async createTemporaryFiles(files: Array<File>): Promise<Array<UmbUploadableItem>> {
const uploadableItems = this.#setupProgress({ files, folders: [] }, null) as Array<UmbUploadableFile>;
const uploadedItems: Array<UmbUploadableItem> = [];
for (const item of uploadableItems) {
// Upload as temp file
const uploaded = await this.#tempFileManager.uploadOne(item.temporaryFile);
// Update progress
if (uploaded.status === TemporaryFileStatus.SUCCESS) {
this.#updateStatus(item, UmbFileDropzoneItemStatus.COMPLETE);
} else {
this.#updateStatus(item, UmbFileDropzoneItemStatus.ERROR);
}
// Add to return value
uploadedItems.push(item);
}
return uploadedItems;
}
public removeOne(item: UmbUploadableItem) {
item.temporaryFile?.abortController?.abort();
this.#progressItems.removeOne(item.unique);
if (item.temporaryFile) {
this.#tempFileManager.removeOne(item.temporaryFile.temporaryUnique);
}
}
public remove(items: Array<UmbUploadableItem>) {
const uniques: string[] = [];
for (const item of items) {
item.temporaryFile?.abortController?.abort();
if (item.temporaryFile) {
uniques.push(item.temporaryFile.temporaryUnique);
}
}
this.#progressItems.remove(uniques);
const temporaryUniques = items.map((x) => x.temporaryFile?.temporaryUnique).filter((x): x is string => !!x);
this.#tempFileManager.remove(temporaryUniques);
}
public removeAll() {
for (const item of this.#progressItems.getValue()) {
item.temporaryFile?.abortController?.abort();
}
this.#progressItems.setValue([]);
this.#tempFileManager.removeAll();
}
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?.mediaTypeUnique;
}
async #createOneMediaItem(item: UmbUploadableItem) {
const options = await this.#getMediaTypeOptions(item);
if (!options.length) {
this.#notificationContext?.peek('warning', {
data: {
message: `${this.#localization.term('media_disallowedFileType')}: ${item.temporaryFile?.file.name}.`,
},
});
return this.#updateStatus(item, UmbFileDropzoneItemStatus.NOT_ALLOWED);
}
const mediaTypeUnique = options.length > 1 ? await this.#showDialogMediaTypePicker(options) : options[0].unique;
if (!mediaTypeUnique) {
return this.#updateStatus(item, UmbFileDropzoneItemStatus.CANCELLED);
}
if (item.temporaryFile) {
this.#handleFile(item as UmbUploadableFile, mediaTypeUnique);
} else if (item.folder) {
this.#handleFolder(item as UmbUploadableFolder, mediaTypeUnique);
}
}
async #createMediaItems(uploadableItems: Array<UmbUploadableItem>) {
for (const item of uploadableItems) {
const options = await this.#getMediaTypeOptions(item);
if (!options.length) {
this.#updateStatus(item, UmbFileDropzoneItemStatus.NOT_ALLOWED);
continue;
}
const mediaTypeUnique = options[0].unique;
if (!mediaTypeUnique) {
throw new Error('Media type unique is not defined');
}
// 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) {
this.#handleFile(item as UmbUploadableFile, mediaTypeUnique);
} else if (item.folder) {
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.CANCELLED) {
this.#updateStatus(item, UmbFileDropzoneItemStatus.CANCELLED);
return;
}
if (temporaryFile.status !== TemporaryFileStatus.SUCCESS) {
this.#updateStatus(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.#updateStatus(item, UmbFileDropzoneItemStatus.COMPLETE);
} else {
this.#updateStatus(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.#updateStatus(item, UmbFileDropzoneItemStatus.COMPLETE);
} else {
this.#updateStatus(item, UmbFileDropzoneItemStatus.ERROR);
}
}
#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
const parent = item.parentUnique ? await this.#mediaDetailRepository.requestByUnique(item.parentUnique) : null;
const allowedChildren = await this.#getAllowedChildrenOf(parent?.data?.mediaType.unique ?? null, item.parentUnique);
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, parentUnique: 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, parentUnique);
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: '',
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
#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;
}
#updateStatus(item: UmbUploadableItem, status: UmbFileDropzoneItemStatus) {
this.#progressItems.updateOne(item.unique, { status });
const progress = this.#progress.getValue();
this.#progress.update({ completed: progress.completed + 1 });
}
#updateProgress(item: UmbUploadableItem, progress: number) {
this.#progressItems.updateOne(item.unique, { progress });
}
readonly #prepareItemsAsUploadable = (
{ folders, files }: UmbFileDropzoneDroppedItems,
parentUnique: string | null,
): Array<UmbUploadableItem> => {
const items: Array<UmbUploadableItem> = [];
for (const file of files) {
const temporaryFile: UmbTemporaryFileModel = {
file,
temporaryUnique: UmbId.new(),
abortController: new AbortController(),
onProgress: (progress) => this.#updateProgress(uploadableItem, progress),
};
const uploadableItem: UmbUploadableFile = {
unique: UmbId.new(),
parentUnique,
status: UmbFileDropzoneItemStatus.WAITING,
progress: 0,
temporaryFile,
};
temporaryFile.abortController?.signal.addEventListener('abort', () => {
this.#updateStatus(uploadableItem, UmbFileDropzoneItemStatus.CANCELLED);
});
items.push(uploadableItem);
}
for (const subfolder of folders) {
const unique = UmbId.new();
items.push({
unique,
parentUnique,
status: UmbFileDropzoneItemStatus.WAITING,
progress: 100, // Folders are created instantly.
folder: { name: subfolder.folderName },
});
items.push(...this.#prepareItemsAsUploadable({ folders: subfolder.folders, files: subfolder.files }, unique));
}
return items;
};
public override destroy() {
this.#tempFileManager.destroy();
super.destroy();
}
}
@@ -1,3 +0,0 @@
import { manifests as modalManifests } from './modals/manifests.js';
export const manifests: Array<UmbExtensionManifest> = [...modalManifests];
@@ -1 +0,0 @@
export * from './dropzone-media-type-picker/index.js';
@@ -2,7 +2,6 @@ import { manifests as mediaManifests } from './media/manifests.js';
import { manifests as mediaSectionManifests } from './media-section/manifests.js';
import { manifests as mediaTypesManifests } from './media-types/manifests.js';
import { manifests as imagingManifests } from './imaging/manifests.js';
import { manifests as dropzoneManifests } from './dropzone/manifests.js';
import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [
@@ -10,5 +9,4 @@ export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> =
...mediaManifests,
...mediaTypesManifests,
...imagingManifests,
...dropzoneManifests,
];
@@ -5,6 +5,7 @@ import type { UmbFileDropzoneItemStatus } from '@umbraco-cms/backoffice/dropzone
import { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
export class UmbMediaCollectionContext extends UmbDefaultCollectionContext<
UmbMediaCollectionItemModel,
UmbMediaCollectionFilterModel
@@ -7,9 +7,9 @@ import { css, customElement, html, ifDefined, repeat, state } from '@umbraco-cms
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UmbFileDropzoneItemStatus } from '@umbraco-cms/backoffice/dropzone';
import type { UmbModalRouteBuilder } from '@umbraco-cms/backoffice/router';
import '@umbraco-cms/backoffice/imaging';
import type { UmbModalRouteBuilder } from '@umbraco-cms/backoffice/router';
@customElement('umb-media-grid-collection-view')
export class UmbMediaGridCollectionViewElement extends UmbLitElement {
@@ -1,7 +1,8 @@
import { UMB_IMAGE_CROPPER_EDITOR_MODAL, UMB_MEDIA_PICKER_MODAL } from '../../modals/index.js';
import type { UmbMediaItemModel, UmbCropModel, UmbMediaPickerPropertyValueEntry } from '../../types.js';
import { UMB_MEDIA_ITEM_REPOSITORY_ALIAS } from '../../repository/constants.js';
import type { UmbUploadableItem } from '@umbraco-cms/backoffice/dropzone';
import type { UmbDropzoneMediaElement } from '../../dropzone/dropzone-media.element.js';
import type { UmbDropzoneChangeEvent } from '@umbraco-cms/backoffice/dropzone';
import { css, 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';
@@ -178,8 +179,17 @@ export class UmbInputRichMediaElement extends UmbFormControlMixin<
constructor() {
super();
this.observe(this.#itemManager.items, () => {
this.#populateCards();
this.observe(
this.#itemManager.items,
(items) => {
console.log('items', items);
this.#populateCards();
},
'_observeItems',
);
this.observe(this.#itemManager.uniques, (uniques) => {
console.log('[Debug] did we get new uniques?', uniques);
});
new UmbModalRouteRegistrationController(this, UMB_IMAGE_CROPPER_EDITOR_MODAL)
@@ -338,10 +348,13 @@ export class UmbInputRichMediaElement extends UmbFormControlMixin<
this.dispatchEvent(new UmbChangeEvent());
}
async #onUploadCompleted(e: CustomEvent) {
const completed = e.detail as Array<UmbUploadableItem>;
const uploaded = completed.map((file) => file.unique);
this.#addItems(uploaded);
async #onUpload(e: UmbDropzoneChangeEvent) {
const target = e.target as UmbDropzoneMediaElement;
const completed = target.value;
const uploaded = completed?.map((file) => file.unique);
if (uploaded?.length) {
this.#addItems(uploaded);
}
}
override render() {
@@ -352,11 +365,8 @@ export class UmbInputRichMediaElement extends UmbFormControlMixin<
}
#renderDropzone() {
if (this.readonly) return nothing;
if (this._cards && this._cards.length >= this.max) return;
return html`<umb-dropzone-media
?multiple=${this.max > 1}
@complete=${this.#onUploadCompleted}></umb-dropzone-media>`;
if (this.readonly || this._cards?.length >= this.max) return nothing;
return html`<umb-dropzone-media ?multiple=${this.max > 1} @change=${this.#onUpload}></umb-dropzone-media>`;
}
#renderItems() {
@@ -1,21 +1,206 @@
import { UMB_DROPZONE_MEDIA_TYPE_PICKER_MODAL } from '../constants.js';
import { UmbMediaDetailRepository } from '../repository/detail/index.js';
import type { UmbMediaDetailModel, UmbMediaValueModel } from '../types.js';
import {
UmbDropzoneManager,
type UmbFileDropzoneDroppedItems,
UmbFileDropzoneItemStatus,
type UmbDropzoneManager,
type UmbAllowedChildrenOfMediaType,
type UmbAllowedMediaTypesOfExtension,
type UmbUploadableFile,
type UmbUploadableFolder,
type UmbUploadableItem,
} from '@umbraco-cms/backoffice/dropzone';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api';
import { UmbMediaTypeStructureRepository, type UmbAllowedMediaTypeModel } from '@umbraco-cms/backoffice/media-type';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification';
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export class UmbDropzoneMediaManager extends UmbControllerBase {
// 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 #mediaTypeStructure = new UmbMediaTypeStructureRepository(this);
readonly #mediaDetailRepository = new UmbMediaDetailRepository(this);
readonly #localization = new UmbLocalizationController(this);
readonly #dropzoneManager;
constructor(host: UmbControllerHost, dropzoneManager: UmbDropzoneManager) {
super(host);
this.#dropzoneManager = dropzoneManager;
}
export class UmbDropzoneMediaManager extends UmbDropzoneManager {
/**
* 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.
* @param {Array<UmbUploadableItem>} uploadableItems - The files and folders to upload.
* @returns {Array<UmbUploadableItem>} - The items about to be uploaded.
*/
public override createMediaItems(
items: UmbFileDropzoneDroppedItems,
parentUnique: string | null,
): Array<UmbUploadableItem> {
return super.createMediaItems(items, parentUnique);
public createMediaItems(uploadableItems: Array<UmbUploadableItem>): Array<UmbUploadableItem> {
if (!uploadableItems.length) return [];
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.
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.
this.#createMediaItems(uploadableItems);
}
return uploadableItems;
}
async #showDialogMediaTypePicker(options: Array<UmbAllowedMediaTypeModel>) {
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modalContext = modalManager.open(this, UMB_DROPZONE_MEDIA_TYPE_PICKER_MODAL, { data: { options } });
const value = await modalContext.onSubmit().catch(() => undefined);
return value?.mediaTypeUnique;
}
async #createOneMediaItem(item: UmbUploadableItem) {
const options = await this.#getMediaTypeOptions(item);
const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT);
if (!options.length) {
notificationContext?.peek('warning', {
data: {
message: `${this.#localization.term('media_disallowedFileType')}: ${item.temporaryFile?.file.name}.`,
},
});
this.#dropzoneManager.updateStatus(item, UmbFileDropzoneItemStatus.NOT_ALLOWED);
return;
}
const mediaTypeUnique = options.length > 1 ? await this.#showDialogMediaTypePicker(options) : options[0].unique;
if (!mediaTypeUnique) {
this.#dropzoneManager.updateStatus(item, UmbFileDropzoneItemStatus.CANCELLED);
return;
}
if (item.temporaryFile) {
this.#handleFile(item as UmbUploadableFile, mediaTypeUnique);
} else if (item.folder) {
this.#handleFolder(item as UmbUploadableFolder, mediaTypeUnique);
}
}
async #createMediaItems(uploadableItems: Array<UmbUploadableItem>) {
for (const item of uploadableItems) {
const options = await this.#getMediaTypeOptions(item);
if (!options.length) {
this.#dropzoneManager.updateStatus(item, UmbFileDropzoneItemStatus.NOT_ALLOWED);
continue;
}
const mediaTypeUnique = options[0].unique;
if (!mediaTypeUnique) {
throw new Error('Media type unique is not defined');
}
// 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) {
this.#handleFile(item as UmbUploadableFile, mediaTypeUnique);
} else if (item.folder) {
this.#handleFolder(item as UmbUploadableFolder, mediaTypeUnique);
}
}
}
async #handleFile(item: UmbUploadableFile, mediaTypeUnique: string) {
// Create the media item.
const scaffold = await this.#getItemScaffold(item, mediaTypeUnique);
const { error } = await this.#mediaDetailRepository.create(scaffold, item.parentUnique);
this.#dropzoneManager.updateStatus(
item,
error ? UmbFileDropzoneItemStatus.ERROR : UmbFileDropzoneItemStatus.COMPLETE,
);
}
async #handleFolder(item: UmbUploadableFolder, mediaTypeUnique: string) {
const scaffold = await this.#getItemScaffold(item, mediaTypeUnique);
const { error } = await this.#mediaDetailRepository.create(scaffold, item.parentUnique);
this.#dropzoneManager.updateStatus(
item,
error ? UmbFileDropzoneItemStatus.ERROR : UmbFileDropzoneItemStatus.COMPLETE,
);
}
// 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, item.parentUnique);
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, parentUnique: 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, parentUnique);
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: '',
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!;
}
}
@@ -4,8 +4,9 @@ import {
UmbFileDropzoneItemStatus,
UmbDropzoneSubmittedEvent,
type UmbUploadableItem,
type UmbFileDropzoneDroppedItems,
} from '@umbraco-cms/backoffice/dropzone';
import { css, customElement, property } from '@umbraco-cms/backoffice/external/lit';
import { css, customElement } from '@umbraco-cms/backoffice/external/lit';
import type { UUIFileDropzoneEvent } from '@umbraco-cms/backoffice/external/uui';
/**
@@ -19,24 +20,6 @@ import type { UUIFileDropzoneEvent } from '@umbraco-cms/backoffice/external/uui'
*/
@customElement('umb-dropzone-media')
export class UmbDropzoneMediaElement extends UmbInputDropzoneElement {
@property({ attribute: 'parent-unique' })
parentUnique: string | null = null;
/**
* Determines if the dropzone should create temporary files or media items directly.
* @deprecated Use the {@link UmbInputDropzoneElement} instead.
*/
@property({ type: Boolean, attribute: 'create-as-temporary' })
createAsTemporary: boolean = false;
#dropzoneManager = new UmbDropzoneMediaManager(this);
/**
* @deprecated Please use `getItems()` instead; this method will be removed in Umbraco 17.
* @returns {Array<UmbUploadableItem>} An array of uploadable items.
*/
public getFiles = this.getItems;
/**
* Gets the current value of the uploaded items.
* @returns {Array<UmbUploadableItem>} An array of uploadable items.
@@ -45,8 +28,10 @@ export class UmbDropzoneMediaElement extends UmbInputDropzoneElement {
return this._progressItems;
}
public progressItems = () => this.#dropzoneManager.progressItems;
public progress = () => this.#dropzoneManager.progress;
public progressItems = () => this._manager.progressItems;
public progress = () => this._manager.progress;
#mediaManager = new UmbDropzoneMediaManager(this, this._manager);
constructor() {
super();
@@ -56,36 +41,38 @@ export class UmbDropzoneMediaElement extends UmbInputDropzoneElement {
document.addEventListener('drop', this.#handleDrop.bind(this));
this.observe(
this.#dropzoneManager.progressItems,
(progressItems: Array<UmbUploadableItem>) => {
this._manager.progressItems,
(progressItems) => {
const waiting = progressItems.find((item) => item.status === UmbFileDropzoneItemStatus.WAITING);
if (progressItems.length && !waiting) {
this.dispatchEvent(new CustomEvent('complete', { detail: progressItems }));
}
},
'_observeProgressItems',
'_observeProgressItemsComplete',
);
}
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));
}
override async onUpload(event: UUIFileDropzoneEvent) {
event.stopImmediatePropagation();
if (this.disabled) return;
if (!event.detail.files.length && !event.detail.folders.length) return;
if (this.createAsTemporary) {
const uploadable = this.#dropzoneManager.createTemporaryFiles(event.detail.files);
this.dispatchEvent(new UmbDropzoneSubmittedEvent(await uploadable));
} else {
const uploadable = this.#dropzoneManager.createMediaItems(event.detail, this.parentUnique);
this.dispatchEvent(new UmbDropzoneSubmittedEvent(uploadable));
}
const droppedItems: UmbFileDropzoneDroppedItems = {
files: event.detail.files,
folders: event.detail.folders,
};
const uploadableItems = await this._manager.createTemporaryFiles(droppedItems, this.parentUnique);
const uploadables = this.#mediaManager.createMediaItems(uploadableItems);
this.dispatchEvent(new UmbDropzoneSubmittedEvent(uploadables));
}
#handleDragEnter(e: DragEvent) {
@@ -1,3 +1,4 @@
export { UMB_DROPZONE_MEDIA_TYPE_PICKER_MODAL } from './dropzone-media-type-picker/index.js';
export { UMB_IMAGE_CROPPER_EDITOR_MODAL } from './image-cropper-editor/index.js';
export * from './media-caption-alt-text/constants.js';
export { UMB_MEDIA_PICKER_MODAL } from './media-picker/index.js';
@@ -3,6 +3,6 @@ export const manifests: Array<UmbExtensionManifest> = [
type: 'modal',
alias: 'Umb.Modal.Dropzone.MediaTypePicker',
name: 'Dropzone Media Type Picker Modal',
element: () => import('./dropzone-media-type-picker/dropzone-media-type-picker-modal.element.js'),
element: () => import('./dropzone-media-type-picker-modal.element.js'),
},
];
@@ -1,3 +1,4 @@
export * from './dropzone-media-type-picker/index.js';
export * from './image-cropper-editor/index.js';
export * from './media-caption-alt-text/index.js';
export * from './media-picker/index.js';
@@ -1,5 +1,11 @@
import { manifests as dropzoneMediaTypePickerManifests } from './dropzone-media-type-picker/manifests.js';
import { manifests as imageCropperEditorManifests } from './image-cropper-editor/manifests.js';
import { manifests as mediaCaptionAltTextManifests } from './media-caption-alt-text/manifests.js';
import { manifests as mediaPickerManifests } from './media-picker/manifests.js';
export const manifests = [...imageCropperEditorManifests, ...mediaCaptionAltTextManifests, ...mediaPickerManifests];
export const manifests = [
...dropzoneMediaTypePickerManifests,
...imageCropperEditorManifests,
...mediaCaptionAltTextManifests,
...mediaPickerManifests,
];
@@ -13,7 +13,6 @@ export default defineConfig({
entry: {
'entry-point': 'entry-point.ts',
'imaging/index': 'imaging/index.ts',
'dropzone/index': 'dropzone/index.ts',
'media-types/index': 'media-types/index.ts',
'media/index': 'media/index.ts',
'umbraco-package': 'umbraco-package.ts',
+1 -1
View File
@@ -68,7 +68,7 @@ DON'T EDIT THIS FILE DIRECTLY. It is generated by /devops/tsconfig/index.js
"@umbraco-cms/backoffice/document-blueprint": ["./src/packages/documents/document-blueprints/index.ts"],
"@umbraco-cms/backoffice/document-type": ["./src/packages/documents/document-types/index.ts"],
"@umbraco-cms/backoffice/document": ["./src/packages/documents/documents/index.ts"],
"@umbraco-cms/backoffice/dropzone": ["./src/packages/media/dropzone/index.ts"],
"@umbraco-cms/backoffice/dropzone": ["./src/packages/dropzone/dropzone/index.ts"],
"@umbraco-cms/backoffice/entity-action": ["./src/packages/core/entity-action/index.ts"],
"@umbraco-cms/backoffice/entity-bulk-action": ["./src/packages/core/entity-bulk-action/index.ts"],
"@umbraco-cms/backoffice/entity-create-option-action": [