- ${repeat(
- this._paths,
- (path) => path.unique,
- (path) =>
- html`
this.#goToFolder(path.unique)}>/`,
- )}${this._typingNewFolder
- ? html`
`
- : html`
+`}
+ #renderToolbar() {
+ return html`
`;
}
- #renderCard(item: UmbMediaTreeItemModel) {
+ #renderCard(item: MappedMediaItem) {
return html`
this.#onSelected(item)}
@deselected=${() => this.#onDeselected(item)}
?selected=${this.value?.selection?.find((value) => value === item.unique)}
- ?selectable=${item.mediaType.unique !== FOLDER || this._selectableFolders}
- ?select-only=${item.mediaType.unique !== FOLDER}
- file-ext=${item.mediaType.unique !== FOLDER ? item.mediaType.icon : ''}>
+ ?selectable=${(!item.isFolder || this._selectableFolders) &&
+ (item.isImageRenderable || this._selectableNonImages)}
+ ?select-only=${!item.isFolder}
+ file-ext=${ifDefined(item.extension)}>
+ ${item.isImageRenderable && item.url ? html`
` : ''}
`;
}
@@ -258,18 +240,6 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement
undefined);
- const mediaData = modalHandler?.getValue();
+ const mediaData = await modalHandler?.onSubmit().catch(() => null);
+ if (!mediaData) return;
const media: MediaPickerTargetData = {
altText: mediaData?.altText,
From 3286d557122628b6c157e6b664cd008157570d63 Mon Sep 17 00:00:00 2001
From: Lone Iversen <108085781+loivsen@users.noreply.github.com>
Date: Fri, 26 Apr 2024 10:25:03 +0200
Subject: [PATCH 010/134] fix issue on dropzone where only images would get
uploaded
---
.../media/components/dropzone-media/dropzone-media.element.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone-media/dropzone-media.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone-media/dropzone-media.element.ts
index 6f999a43a8..2a129deb8f 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone-media/dropzone-media.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone-media/dropzone-media.element.ts
@@ -7,6 +7,7 @@ import {
type UmbAllowedMediaTypeModel,
UmbMediaTypeStructureRepository,
getMediaTypeByFileMimeType,
+ UmbMediaTypeFileType,
} from '@umbraco-cms/backoffice/media-type';
import {
UmbTemporaryFileManager,
@@ -101,6 +102,7 @@ export class UmbDropzoneMediaElement extends UmbLitElement {
for (const upload of uploads) {
const mediaType = this.#getMediaTypeFromMime(upload.file.type);
+ const value = mediaType.unique === UmbMediaTypeFileType.IMAGE ? { src: upload.unique } : upload.unique;
const preset: Partial = {
mediaType: {
@@ -120,7 +122,7 @@ export class UmbDropzoneMediaElement extends UmbLitElement {
? [
{
alias: 'umbracoFile',
- value: { src: upload.unique },
+ value,
culture: null,
segment: null,
},
From 4716c8021e5c166d7c2b3ddbb7ff3779b85d702e Mon Sep 17 00:00:00 2001
From: Lone Iversen <108085781+loivsen@users.noreply.github.com>
Date: Tue, 30 Apr 2024 13:21:28 +0200
Subject: [PATCH 011/134] dropzone management
---
src/Umbraco.Web.UI.Client/package-lock.json | 3 +-
src/Umbraco.Web.UI.Client/package.json | 2 +
.../src/external/backend-api/src/models.ts | 77 +++--
.../src/external/backend-api/src/services.ts | 167 +++++++---
.../src/external/mime-types/index.ts | 1 +
.../media-type-structure.repository.ts | 14 +
...media-type-structure.server.data-source.ts | 18 ++
.../packages/media/media-types/utils/index.ts | 31 +-
.../dropzone/dropzone-manager.class.ts | 46 +++
.../components/dropzone/dropzone.element.ts | 302 ++++++++++++++++++
.../media/media/components/dropzone/index.ts | 2 +
...oadable-structure-data-source.interface.ts | 16 +
...pe-uploadable-structure-repository-base.ts | 32 ++
...loadable-structure-repository.interface.ts | 7 +
...dable-structure-server-data-source-base.ts | 66 ++++
src/Umbraco.Web.UI.Client/tsconfig.json | 1 +
16 files changed, 691 insertions(+), 94 deletions(-)
create mode 100644 src/Umbraco.Web.UI.Client/src/external/mime-types/index.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/dropzone-manager.class.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/dropzone.element.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/index.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/repository/content-type-uploadable-structure-data-source.interface.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/repository/content-type-uploadable-structure-repository-base.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/repository/content-type-uploadable-structure-repository.interface.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/repository/content-type-uploadable-structure-server-data-source-base.ts
diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json
index 3a51ab3741..42b2aa56f6 100644
--- a/src/Umbraco.Web.UI.Client/package-lock.json
+++ b/src/Umbraco.Web.UI.Client/package-lock.json
@@ -20,6 +20,7 @@
"element-internals-polyfill": "^1.3.10",
"lit": "^3.1.2",
"marked": "^12.0.0",
+ "mime-types": "^2.1.35",
"monaco-editor": "^0.46.0",
"rxjs": "^7.8.1",
"tinymce": "^6.8.3",
@@ -15849,7 +15850,6 @@
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "dev": true,
"engines": {
"node": ">= 0.6"
}
@@ -15858,7 +15858,6 @@
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "dev": true,
"dependencies": {
"mime-db": "1.52.0"
},
diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json
index 73eef9f83d..62eb0b7644 100644
--- a/src/Umbraco.Web.UI.Client/package.json
+++ b/src/Umbraco.Web.UI.Client/package.json
@@ -96,6 +96,7 @@
"./external/dompurify": "./dist-cms/external/dompurify/index.js",
"./external/lit": "./dist-cms/external/lit/index.js",
"./external/marked": "./dist-cms/external/marked/index.js",
+ "./external/mime-types": "./dist-cms/external/mime-types/index.js",
"./external/monaco-editor": "./dist-cms/external/monaco-editor/index.js",
"./external/openid": "./dist-cms/external/openid/index.js",
"./external/router-slot": "./dist-cms/external/router-slot/index.js",
@@ -179,6 +180,7 @@
"element-internals-polyfill": "^1.3.10",
"lit": "^3.1.2",
"marked": "^12.0.0",
+ "mime-types": "^2.1.35",
"monaco-editor": "^0.46.0",
"rxjs": "^7.8.1",
"tinymce": "^6.8.3",
diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models.ts
index 1bcb75c327..1987ac421e 100644
--- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models.ts
+++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models.ts
@@ -625,8 +625,7 @@ updater?: string | null
};
export type DocumentConfigurationResponseModel = {
- sanitizeTinyMce: boolean
-disableDeleteWhenReferenced: boolean
+ disableDeleteWhenReferenced: boolean
disableUnpublishWhenReferenced: boolean
allowEditInvariantFromNonDefault: boolean
allowNonExistingSegmentsCreation: boolean
@@ -1124,7 +1123,6 @@ mediaType: MediaTypeCollectionReferenceResponseModel
export type MediaConfigurationResponseModel = {
disableDeleteWhenReferenced: boolean
disableUnpublishWhenReferenced: boolean
-sanitizeTinyMce: boolean
reservedFieldNames: Array
};
@@ -1788,6 +1786,11 @@ export type PagedSearcherResponseModel = {
items: Array
};
+export type PagedSegmentResponseModel = {
+ total: number
+items: Array
+ };
+
export type PagedTagResponseModel = {
total: number
items: Array
@@ -1894,6 +1897,13 @@ memberUserNames: Array
memberGroupNames: Array
};
+export type PublicAccessResponseModel = {
+ loginDocument: ReferenceByIdModel
+errorDocument: ReferenceByIdModel
+members: Array
+groups: Array
+ };
+
export type PublishDocumentRequestModel = {
publishSchedules: Array
};
@@ -2052,6 +2062,11 @@ export type SecurityConfigurationResponseModel = {
passwordConfiguration: PasswordConfigurationResponseModel
};
+export type SegmentResponseModel = {
+ name: string
+alias: string
+ };
+
export type ServerConfigurationItemResponseModel = {
name: string
data: string
@@ -3397,7 +3412,7 @@ take?: number
,PutDocumentByIdNotifications: string
,PostDocumentByIdPublicAccess: string
,DeleteDocumentByIdPublicAccess: string
- ,GetDocumentByIdPublicAccess: void
+ ,GetDocumentByIdPublicAccess: PublicAccessResponseModel
,PutDocumentByIdPublicAccess: string
,PutDocumentByIdPublish: string
,PutDocumentByIdPublishWithDescendants: string
@@ -3583,6 +3598,7 @@ requestBody?: UpdateLanguageRequestModel
responses: {
GetItemLanguage: Array
+ ,GetItemLanguageDefault: LanguageItemResponseModel
,GetLanguage: PagedLanguageResponseModel
,PostLanguage: string
,GetLanguageByIsoCode: LanguageResponseModel
@@ -3681,6 +3697,12 @@ export type MediaTypeData = {
GetItemMediaType: {
id?: Array
+ };
+GetItemMediaTypeAllowed: {
+ fileExtension?: string
+skip?: number
+take?: number
+
};
GetItemMediaTypeSearch: {
query?: string
@@ -3773,6 +3795,7 @@ take?: number
responses: {
GetItemMediaType: Array
+ ,GetItemMediaTypeAllowed: PagedModelMediaTypeItemResponseModel
,GetItemMediaTypeSearch: PagedModelMediaTypeItemResponseModel
,PostMediaType: string
,GetMediaTypeById: MediaTypeResponseModel
@@ -3996,10 +4019,10 @@ take?: number
responses: {
GetItemMemberGroup: Array
,GetMemberGroup: PagedMemberGroupResponseModel
- ,PostMemberGroup: MemberGroupResponseModel
+ ,PostMemberGroup: string
,GetMemberGroupById: MemberGroupResponseModel
,DeleteMemberGroupById: string
- ,PutMemberGroupById: MemberGroupResponseModel
+ ,PutMemberGroupById: string
,GetTreeMemberGroupRoot: PagedNamedEntityTreeItemResponseModel
}
@@ -4591,6 +4614,24 @@ PostSecurityForgotPasswordVerify: {
}
+export type SegmentData = {
+
+ payloads: {
+ GetSegment: {
+ skip?: number
+take?: number
+
+ };
+ }
+
+
+ responses: {
+ GetSegment: PagedSegmentResponseModel
+
+ }
+
+ }
+
export type ServerData = {
@@ -4879,10 +4920,6 @@ export type UserDataData = {
PostUserData: {
requestBody?: CreateUserDataRequestModel
- };
-PutUserData: {
- requestBody?: UpdateUserDataRequestModel
-
};
GetUserData: {
groups?: Array
@@ -4890,6 +4927,10 @@ identifiers?: Array
skip?: number
take?: number
+ };
+PutUserData: {
+ requestBody?: UpdateUserDataRequestModel
+
};
GetUserDataById: {
id: string
@@ -4900,8 +4941,8 @@ GetUserDataById: {
responses: {
PostUserData: string
- ,PutUserData: string
,GetUserData: PagedUserDataResponseModel
+ ,PutUserData: string
,GetUserDataById: UserDataModel
}
@@ -5152,7 +5193,11 @@ PostUserUnlock: {
export type WebhookData = {
payloads: {
- GetWebhook: {
+ GetItemWebhook: {
+ id?: Array
+
+ };
+GetWebhook: {
skip?: number
take?: number
@@ -5173,21 +5218,17 @@ requestBody?: UpdateWebhookRequestModel
DeleteWebhookById: {
id: string
- };
-GetWebhookItem: {
- ids?: Array
-
};
}
responses: {
- GetWebhook: PagedWebhookResponseModel
+ GetItemWebhook: Array
+ ,GetWebhook: PagedWebhookResponseModel
,PostWebhook: string
,GetWebhookById: WebhookResponseModel
,PutWebhookById: string
,DeleteWebhookById: string
- ,GetWebhookItem: Array
}
diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services.ts
index b00b6aad71..64e967c0c7 100644
--- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services.ts
+++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services.ts
@@ -1,7 +1,7 @@
import type { CancelablePromise } from './core/CancelablePromise';
import { OpenAPI } from './core/OpenAPI';
import { request as __request } from './core/request';
-import type { AuditLogData, CultureData, DataTypeData, DictionaryData, DocumentBlueprintData, DocumentTypeData, DocumentVersionData, DocumentData, DynamicRootData, HealthCheckData, HelpData, IndexerData, InstallData, LanguageData, LogViewerData, ManifestData, MediaTypeData, MediaData, MemberGroupData, MemberTypeData, MemberData, ModelsBuilderData, ObjectTypesData, PackageData, PartialViewData, PreviewData, ProfilingData, PropertyTypeData, PublishedCacheData, RedirectManagementData, RelationTypeData, RelationData, ScriptData, SearcherData, SecurityData, ServerData, StaticFileData, StylesheetData, TagData, TelemetryData, TemplateData, TemporaryFileData, UpgradeData, UserDataData, UserGroupData, UserData, WebhookData } from './models';
+import type { AuditLogData, CultureData, DataTypeData, DictionaryData, DocumentBlueprintData, DocumentTypeData, DocumentVersionData, DocumentData, DynamicRootData, HealthCheckData, HelpData, IndexerData, InstallData, LanguageData, LogViewerData, ManifestData, MediaTypeData, MediaData, MemberGroupData, MemberTypeData, MemberData, ModelsBuilderData, ObjectTypesData, PackageData, PartialViewData, PreviewData, ProfilingData, PropertyTypeData, PublishedCacheData, RedirectManagementData, RelationTypeData, RelationData, ScriptData, SearcherData, SecurityData, SegmentData, ServerData, StaticFileData, StylesheetData, TagData, TelemetryData, TemplateData, TemporaryFileData, UpgradeData, UserDataData, UserGroupData, UserData, WebhookData } from './models';
export class AuditLogService {
@@ -2220,6 +2220,7 @@ requestBody
}
/**
+ * @returns unknown Success
* @throws ApiError
*/
public static getDocumentByIdPublicAccess(data: DocumentData['payloads']['GetDocumentByIdPublicAccess']): CancelablePromise {
@@ -3129,6 +3130,22 @@ export class LanguageService {
});
}
+ /**
+ * @returns unknown Success
+ * @throws ApiError
+ */
+ public static getItemLanguageDefault(): CancelablePromise {
+
+ return __request(OpenAPI, {
+ method: 'GET',
+ url: '/umbraco/management/api/v1/item/language/default',
+ errors: {
+ 401: `The resource is protected and requires an authentication token`,
+ 403: `The authenticated user do not have access to this resource`,
+ },
+ });
+ }
+
/**
* @returns unknown Success
* @throws ApiError
@@ -3546,6 +3563,30 @@ export class MediaTypeService {
});
}
+ /**
+ * @returns unknown Success
+ * @throws ApiError
+ */
+ public static getItemMediaTypeAllowed(data: MediaTypeData['payloads']['GetItemMediaTypeAllowed'] = {}): CancelablePromise {
+ const {
+
+ fileExtension,
+skip,
+take
+ } = data;
+ return __request(OpenAPI, {
+ method: 'GET',
+ url: '/umbraco/management/api/v1/item/media-type/allowed',
+ query: {
+ fileExtension, skip, take
+ },
+ errors: {
+ 401: `The resource is protected and requires an authentication token`,
+ 403: `The authenticated user do not have access to this resource`,
+ },
+ });
+ }
+
/**
* @returns unknown Success
* @throws ApiError
@@ -4679,7 +4720,7 @@ take
}
/**
- * @returns unknown Success
+ * @returns string Created
* @throws ApiError
*/
public static postMemberGroup(data: MemberGroupData['payloads']['PostMemberGroup'] = {}): CancelablePromise {
@@ -4692,6 +4733,7 @@ take
url: '/umbraco/management/api/v1/member-group',
body: requestBody,
mediaType: 'application/json',
+ responseHeader: 'Umb-Generated-Resource',
errors: {
400: `Bad Request`,
401: `The resource is protected and requires an authentication token`,
@@ -4749,7 +4791,7 @@ take
}
/**
- * @returns unknown Success
+ * @returns string Success
* @throws ApiError
*/
public static putMemberGroupById(data: MemberGroupData['payloads']['PutMemberGroupById']): CancelablePromise {
@@ -4766,6 +4808,7 @@ requestBody
},
body: requestBody,
mediaType: 'application/json',
+ responseHeader: 'Umb-Notifications',
errors: {
400: `Bad Request`,
401: `The resource is protected and requires an authentication token`,
@@ -6756,6 +6799,34 @@ export class SecurityService {
}
+export class SegmentService {
+
+ /**
+ * @returns unknown Success
+ * @throws ApiError
+ */
+ public static getSegment(data: SegmentData['payloads']['GetSegment'] = {}): CancelablePromise {
+ const {
+
+ skip,
+take
+ } = data;
+ return __request(OpenAPI, {
+ method: 'GET',
+ url: '/umbraco/management/api/v1/segment',
+ query: {
+ skip, take
+ },
+ errors: {
+ 400: `Bad Request`,
+ 401: `The resource is protected and requires an authentication token`,
+ 403: `The authenticated user do not have access to this resource`,
+ },
+ });
+ }
+
+}
+
export class ServerService {
/**
@@ -7726,29 +7797,6 @@ export class UserDataService {
});
}
- /**
- * @returns string Success
- * @throws ApiError
- */
- public static putUserData(data: UserDataData['payloads']['PutUserData'] = {}): CancelablePromise {
- const {
-
- requestBody
- } = data;
- return __request(OpenAPI, {
- method: 'PUT',
- url: '/umbraco/management/api/v1/user-data',
- body: requestBody,
- mediaType: 'application/json',
- responseHeader: 'Umb-Notifications',
- errors: {
- 400: `Bad Request`,
- 401: `The resource is protected and requires an authentication token`,
- 404: `Not Found`,
- },
- });
- }
-
/**
* @returns unknown Success
* @throws ApiError
@@ -7773,6 +7821,29 @@ take
});
}
+ /**
+ * @returns string Success
+ * @throws ApiError
+ */
+ public static putUserData(data: UserDataData['payloads']['PutUserData'] = {}): CancelablePromise {
+ const {
+
+ requestBody
+ } = data;
+ return __request(OpenAPI, {
+ method: 'PUT',
+ url: '/umbraco/management/api/v1/user-data',
+ body: requestBody,
+ mediaType: 'application/json',
+ responseHeader: 'Umb-Notifications',
+ errors: {
+ 400: `Bad Request`,
+ 401: `The resource is protected and requires an authentication token`,
+ 404: `Not Found`,
+ },
+ });
+ }
+
/**
* @returns unknown Success
* @throws ApiError
@@ -8829,6 +8900,28 @@ requestBody
export class WebhookService {
+ /**
+ * @returns unknown Success
+ * @throws ApiError
+ */
+ public static getItemWebhook(data: WebhookData['payloads']['GetItemWebhook'] = {}): CancelablePromise {
+ const {
+
+ id
+ } = data;
+ return __request(OpenAPI, {
+ method: 'GET',
+ url: '/umbraco/management/api/v1/item/webhook',
+ query: {
+ id
+ },
+ errors: {
+ 401: `The resource is protected and requires an authentication token`,
+ 403: `The authenticated user do not have access to this resource`,
+ },
+ });
+ }
+
/**
* @returns unknown Success
* @throws ApiError
@@ -8950,26 +9043,4 @@ requestBody
});
}
- /**
- * @returns unknown Success
- * @throws ApiError
- */
- public static getWebhookItem(data: WebhookData['payloads']['GetWebhookItem'] = {}): CancelablePromise {
- const {
-
- ids
- } = data;
- return __request(OpenAPI, {
- method: 'GET',
- url: '/umbraco/management/api/v1/webhook/item',
- query: {
- ids
- },
- errors: {
- 401: `The resource is protected and requires an authentication token`,
- 403: `The authenticated user do not have access to this resource`,
- },
- });
- }
-
}
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/external/mime-types/index.ts b/src/Umbraco.Web.UI.Client/src/external/mime-types/index.ts
new file mode 100644
index 0000000000..38a5df465f
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/external/mime-types/index.ts
@@ -0,0 +1 @@
+export * as mime from 'mime-types';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/media-type-structure.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/media-type-structure.repository.ts
index 0db7b421b7..86733391e0 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/media-type-structure.repository.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/media-type-structure.repository.ts
@@ -4,8 +4,22 @@ import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbContentTypeStructureRepositoryBase } from '@umbraco-cms/backoffice/content-type';
export class UmbMediaTypeStructureRepository extends UmbContentTypeStructureRepositoryBase {
+ #dataSource;
constructor(host: UmbControllerHost) {
super(host, UmbMediaTypeStructureServerDataSource);
+ this.#dataSource = new UmbMediaTypeStructureServerDataSource(host);
+ }
+
+ async requestMediaTypesOf({
+ fileExtension,
+ skip = 0,
+ take = 100,
+ }: {
+ fileExtension: string;
+ skip?: number;
+ take?: number;
+ }) {
+ return this.#dataSource.getMediaTypesOfFileExtension({ fileExtension, skip, take });
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/media-type-structure.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/media-type-structure.server.data-source.ts
index 22b833b379..acdfad98f0 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/media-type-structure.server.data-source.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/media-type-structure.server.data-source.ts
@@ -17,6 +17,10 @@ export class UmbMediaTypeStructureServerDataSource extends UmbContentTypeStructu
constructor(host: UmbControllerHost) {
super(host, { getAllowedChildrenOf, mapper });
}
+
+ getMediaTypesOfFileExtension({ fileExtension, skip, take }: { fileExtension: string; skip: number; take: number }) {
+ return getAllowedMediaTypesOfExtension({ fileExtension, skip, take });
+ }
}
const getAllowedChildrenOf = (unique: string | null) => {
@@ -37,3 +41,17 @@ const mapper = (item: AllowedMediaTypeModel): UmbAllowedMediaTypeModel => {
icon: item.icon || null,
};
};
+
+const getAllowedMediaTypesOfExtension = async ({
+ fileExtension,
+ skip,
+ take,
+}: {
+ fileExtension: string;
+ skip: number;
+ take: number;
+}) => {
+ // eslint-disable-next-line local-rules/no-direct-api-import
+ const { items } = await MediaTypeService.getItemMediaTypeAllowed({ fileExtension, skip, take });
+ return items.map((item) => mapper(item));
+};
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/utils/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/utils/index.ts
index b67055a38e..2557bafb9c 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/utils/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/utils/index.ts
@@ -1,28 +1,7 @@
-export enum UmbMediaTypeFileType {
- SVG = 'Vector Graphics (SVG)',
- IMAGE = 'Image',
- AUDIO = 'Audio',
- VIDEO = 'Video',
- ARTICLE = 'Article',
- FILE = 'File',
-}
+import { mime } from '@umbraco-cms/backoffice/external/mime-types';
-export function getMediaTypeByFileExtension(extension: string) {
- if (extension === 'svg') return UmbMediaTypeFileType.SVG;
- if (['jpg', 'jpeg', 'gif', 'bmp', 'png', 'tiff', 'tif', 'webp'].includes(extension))
- return UmbMediaTypeFileType.IMAGE;
- if (['mp3', 'weba', 'oga', 'opus'].includes(extension)) return UmbMediaTypeFileType.AUDIO;
- if (['mp4', 'webm', 'ogv'].includes(extension)) return UmbMediaTypeFileType.VIDEO;
- if (['pdf', 'docx', 'doc'].includes(extension)) return UmbMediaTypeFileType.ARTICLE;
- return UmbMediaTypeFileType.FILE;
-}
-
-export function getMediaTypeByFileMimeType(mimetype: string) {
- if (mimetype === 'image/svg+xml') return UmbMediaTypeFileType.SVG;
- const [type, extension] = mimetype.split('/');
- if (type === 'image') return UmbMediaTypeFileType.IMAGE;
- if (type === 'audio') return UmbMediaTypeFileType.AUDIO;
- if (type === 'video') return UmbMediaTypeFileType.VIDEO;
- if (['pdf', 'docx', 'doc'].includes(extension)) return UmbMediaTypeFileType.ARTICLE;
- return UmbMediaTypeFileType.FILE;
+export function getExtensionFromMime(mimeType: string): string | undefined {
+ const extension = mime.extension(mimeType);
+ if (!extension) return; // extension doesn't exist.
+ return extension;
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/dropzone-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/dropzone-manager.class.ts
new file mode 100644
index 0000000000..6abcd410ba
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/dropzone-manager.class.ts
@@ -0,0 +1,46 @@
+import type { UmbContentTypeUploadableStructureRepositoryBase } from './repository/content-type-uploadable-structure-repository-base.js';
+import { UmbId } from '@umbraco-cms/backoffice/id';
+import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
+import type { UmbMediaTypeItemModel } from '@umbraco-cms/backoffice/media-type';
+import { UmbTemporaryFileManager } from '@umbraco-cms/backoffice/temporary-file';
+
+export class UmbDropzoneManager extends UmbControllerBase {
+ #init!: Promise;
+
+ #fileManager = new UmbTemporaryFileManager(this);
+ #repository: UmbContentTypeUploadableStructureRepositoryBase;
+
+ #parentUnique: string | null = null;
+
+ constructor(
+ host: UmbControllerHost,
+ typeRepository: UmbContentTypeUploadableStructureRepositoryBase,
+ parentUnique: string | null,
+ ) {
+ super(host);
+ this.#repository = typeRepository;
+ this.#parentUnique = parentUnique;
+ }
+
+ public async dropOneFile(file: File) {
+ const matchingMediaTypes = await this.#repository.requestAllowedMediaTypesOf(file.type);
+ //const options = this.#allowedMediaTypes.filter((allowedMediaType) => matchingMediaTypes.includes(allowedMediaType));
+ }
+
+ public async dropFiles(files: Array) {}
+
+ async #requestAllowedMediaTypesOf(fileExtension: string) {
+ const { data } = await this.#repository.requestAllowedMediaTypesOf(fileExtension);
+ //const mediaTypes = data?.filter((option) => this.#allowedMediaTypes.includes(option));
+ //return { fileExtension, mediaTypes };
+ }
+
+ private _reset() {
+ //
+ }
+
+ public destroy() {
+ super.destroy();
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/dropzone.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/dropzone.element.ts
new file mode 100644
index 0000000000..d4885ccbec
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/dropzone.element.ts
@@ -0,0 +1,302 @@
+import { UmbMediaDetailRepository } from '../../repository/index.js';
+import type { UmbMediaDetailModel } from '../../types.js';
+import { css, html, customElement, state, property } from '@umbraco-cms/backoffice/external/lit';
+import type { UUIFileDropzoneElement, UUIFileDropzoneEvent } from '@umbraco-cms/backoffice/external/uui';
+import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
+import {
+ type UmbAllowedMediaTypeModel,
+ UmbMediaTypeStructureRepository,
+ UmbMediaTypeDetailRepository,
+ getExtensionFromMime,
+} from '@umbraco-cms/backoffice/media-type';
+import {
+ UmbTemporaryFileManager,
+ type UmbTemporaryFileQueueModel,
+ type UmbTemporaryFileModel,
+} from '@umbraco-cms/backoffice/temporary-file';
+import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
+
+interface MediaTypeOptions {
+ fileExtension: string;
+ mediaTypes: Array;
+}
+
+interface UploadableFile {
+ file: File;
+ mediaType: UmbAllowedMediaTypeModel;
+ regularUploadField?: boolean;
+ temporaryUnique?: string;
+}
+
+@customElement('umb-dropzone')
+export class UmbDropzoneElement extends UmbLitElement {
+ #fileManager = new UmbTemporaryFileManager(this);
+ #mediaTypeStructure = new UmbMediaTypeStructureRepository(this);
+ #mediaDetailRepository = new UmbMediaDetailRepository(this);
+ #mediaTypeDetailRepository = new UmbMediaTypeDetailRepository(this);
+
+ #allowedMediaTypes: Array = [];
+
+ @state()
+ private queue: Array = [];
+
+ @property({ attribute: false })
+ parentUnique: string | null = null;
+
+ public browse() {
+ const element = this.shadowRoot?.querySelector('#dropzone') as UUIFileDropzoneElement;
+ return element.browse();
+ }
+
+ async #getAllowedMediaTypes(): Promise {
+ const { data } = await this.#mediaTypeStructure.requestAllowedChildrenOf(this.parentUnique);
+ return data?.items ?? [];
+ }
+
+ async #getAllowedMediaTypesOf(fileExtension: string): Promise {
+ const options = await this.#mediaTypeStructure.requestMediaTypesOf({ fileExtension });
+ const mediaTypes = options.filter((option) => this.#allowedMediaTypes.includes(option));
+ return { fileExtension, mediaTypes };
+ }
+
+ constructor() {
+ super();
+ document.addEventListener('dragenter', this.#handleDragEnter.bind(this));
+ document.addEventListener('dragleave', this.#handleDragLeave.bind(this));
+ document.addEventListener('drop', this.#handleDrop.bind(this));
+ }
+
+ disconnectedCallback(): void {
+ super.disconnectedCallback();
+ document.removeEventListener('dragenter', this.#handleDragEnter.bind(this));
+ document.removeEventListener('dragleave', this.#handleDragLeave.bind(this));
+ document.removeEventListener('drop', this.#handleDrop.bind(this));
+ }
+
+ #handleDragEnter() {
+ this.toggleAttribute('dragging', true);
+ }
+
+ #handleDragLeave() {
+ this.toggleAttribute('dragging', false);
+ }
+
+ #handleDrop(event: DragEvent) {
+ event.preventDefault();
+ this.toggleAttribute('dragging', false);
+ }
+
+ async #onDropFiles(event: UUIFileDropzoneEvent) {
+ // TODO Handle of folder uploads.
+
+ const files: Array = event.detail.files;
+ if (!files.length) return;
+
+ this.#allowedMediaTypes = await this.#getAllowedMediaTypes();
+ if (!this.#allowedMediaTypes.length) return;
+ // If we have files that are not allowed to be uploaded, we show those in a dialog to the user?
+
+ if (files.length === 1) {
+ this.#handleOneFile(files[0]);
+ } else {
+ this.#handleMultipleFiles(files);
+ }
+ }
+
+ async #handleOneFile(file: File) {
+ const extension = getExtensionFromMime(file.type);
+ if (!extension) return; // Extension doesn't exist.
+
+ const options = await this.#getAllowedMediaTypesOf(extension);
+ if (!options.mediaTypes.length) return; // File type not allowed in current dropzone.
+
+ if (options.mediaTypes.length === 1) {
+ this.#uploadFiles([{ file, mediaType: options.mediaTypes[0] }]);
+ return;
+ }
+
+ // Multiple options, show a dialog to the user to pick one.
+ //TODO: Implement dialog.
+ }
+
+ async #handleMultipleFiles(files: Array) {
+ // removes duplicate file types so we don't call the endpoint unnecessarily for every file.
+ const types = [...new Set(files.map((file) => file.type))];
+ const options: Array = [];
+
+ for (const type of types) {
+ const extension = getExtensionFromMime(type);
+ if (!extension) return; // Extension doesn't exist.
+
+ options.push(await this.#getAllowedMediaTypesOf(extension));
+ }
+
+ // We are just going to automatically pick the first possible media type option for now, but consider an option dialog in the future.
+ const uploadable: Array = [];
+ files.forEach((file) => {
+ const mediaType = options.find((option) => option.fileExtension === file.type)?.mediaTypes[0] ?? undefined;
+ if (mediaType) uploadable.push({ file, mediaType });
+ });
+
+ this.#uploadFiles(uploadable);
+ }
+
+ async #uploadFiles(uploadable: Array) {
+ const queue = uploadable.map(
+ (item): UmbTemporaryFileQueueModel => ({ file: item.file, unique: item.temporaryUnique }),
+ );
+
+ const uploaded = await this.#fileManager.upload(queue);
+
+ for (const upload of uploaded) {
+ const mediaType = uploadable.find((item) => item.temporaryUnique === upload.unique)?.mediaType;
+ const value = mediaType?.unique === UmbMediaTypeFileType.IMAGE ? { src: upload.unique } : upload.unique;
+
+ if (!mediaType) return;
+
+ const preset: Partial = {
+ mediaType: {
+ unique: mediaType.unique,
+ collection: null,
+ },
+ variants: [
+ {
+ culture: null,
+ segment: null,
+ name: upload.file.name,
+ createDate: null,
+ updateDate: null,
+ },
+ ],
+ values: upload.file.type
+ ? [
+ {
+ alias: 'umbracoFile',
+ value,
+ culture: null,
+ segment: null,
+ },
+ ]
+ : [],
+ };
+
+ const { data } = await this.#mediaDetailRepository.createScaffold(preset);
+ if (!data) return;
+
+ await this.#mediaDetailRepository.create(data, this.parentUnique);
+
+ this.dispatchEvent(new UmbChangeEvent());
+ }
+ }
+
+ async #uploadHandler(files: Array) {
+ //TODO: Folders uploaded via UUIDropzone are always empty. Investigate why.
+
+ const folders = files.filter((item) => !item.type).map((file): UmbTemporaryFileQueueModel => ({ file }));
+ const mediaItems = files.filter((item) => item.type);
+
+ const queue = mediaItems.map((file): UmbTemporaryFileQueueModel => ({ file }));
+
+ const uploaded = await this.#fileManager.upload(queue);
+ return [...folders, ...uploaded];
+ }
+
+ async #onFileUpload(event: UUIFileDropzoneEvent) {
+ const files: Array = event.detail.files;
+
+ if (!files.length) return;
+ const uploads = await this.#uploadHandler(files);
+
+ for (const upload of uploads) {
+ const mediaType = /*this.#getMediaTypeFromMime(upload.file.type); */ '' as any;
+ const value = mediaType.unique === UmbMediaTypeFileType.IMAGE ? { src: upload.unique } : upload.unique;
+
+ const preset: Partial = {
+ mediaType: {
+ unique: mediaType.unique,
+ collection: null,
+ },
+ variants: [
+ {
+ culture: null,
+ segment: null,
+ name: upload.file.name,
+ createDate: null,
+ updateDate: null,
+ },
+ ],
+ values: upload.file.type
+ ? [
+ {
+ alias: 'umbracoFile',
+ value,
+ culture: null,
+ segment: null,
+ },
+ ]
+ : [],
+ };
+
+ const { data } = await this.#mediaDetailRepository.createScaffold(preset);
+ if (!data) return;
+
+ await this.#mediaDetailRepository.create(data, this.parentUnique);
+
+ this.dispatchEvent(new UmbChangeEvent());
+ }
+ }
+
+ render() {
+ return html``;
+ }
+
+ static styles = [
+ css`
+ :host([dragging]) #dropzone {
+ opacity: 1;
+ pointer-events: all;
+ }
+
+ [dropzone] {
+ opacity: 0;
+ }
+
+ #dropzone {
+ opacity: 0;
+ pointer-events: none;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: absolute;
+ inset: 0px;
+ z-index: 100;
+ backdrop-filter: opacity(1); /* Removes the built in blur effect */
+ border-radius: var(--uui-border-radius);
+ overflow: clip;
+ border: 1px solid var(--uui-color-focus);
+ }
+ #dropzone:after {
+ content: '';
+ display: block;
+ position: absolute;
+ inset: 0;
+ border-radius: var(--uui-border-radius);
+ background-color: var(--uui-color-focus);
+ opacity: 0.2;
+ }
+ `,
+ ];
+}
+
+export default UmbDropzoneElement;
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'umb-dropzone': UmbDropzoneElement;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/index.ts
new file mode 100644
index 0000000000..38624dcb34
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/index.ts
@@ -0,0 +1,2 @@
+export * from './dropzone.element.js';
+export * from './dropzone-manager.class.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/repository/content-type-uploadable-structure-data-source.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/repository/content-type-uploadable-structure-data-source.interface.ts
new file mode 100644
index 0000000000..35422dfc25
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/repository/content-type-uploadable-structure-data-source.interface.ts
@@ -0,0 +1,16 @@
+import type {
+ UmbContentTypeStructureDataSource,
+ UmbContentTypeStructureDataSourceConstructor,
+} from '@umbraco-cms/backoffice/content-type';
+import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import type { UmbDataSourceResponse } from '@umbraco-cms/backoffice/repository';
+
+export interface UmbContentTypeUploadableStructureDataSourceConstructor
+ extends UmbContentTypeStructureDataSourceConstructor {
+ new (host: UmbControllerHost): UmbContentTypeUploadableStructureDataSource;
+}
+
+export interface UmbContentTypeUploadableStructureDataSource
+ extends UmbContentTypeStructureDataSource {
+ getAllowedMediaTypesOf(fileExtension: string | null): Promise>;
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/repository/content-type-uploadable-structure-repository-base.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/repository/content-type-uploadable-structure-repository-base.ts
new file mode 100644
index 0000000000..60805f94cf
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/repository/content-type-uploadable-structure-repository-base.ts
@@ -0,0 +1,32 @@
+import type {
+ UmbContentTypeUploadableStructureDataSource,
+ UmbContentTypeUploadableStructureDataSourceConstructor,
+} from './content-type-uploadable-structure-data-source.interface.js';
+import type { UmbContentTypeUploadableStructureRepository } from './content-type-uploadable-structure-repository.interface.js';
+import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import { UmbContentTypeStructureRepositoryBase } from '@umbraco-cms/backoffice/content-type';
+
+export abstract class UmbContentTypeUploadableStructureRepositoryBase
+ extends UmbContentTypeStructureRepositoryBase
+ implements UmbContentTypeUploadableStructureRepository
+{
+ #structureSource: UmbContentTypeUploadableStructureDataSource;
+
+ constructor(
+ host: UmbControllerHost,
+ structureSource: UmbContentTypeUploadableStructureDataSourceConstructor,
+ ) {
+ super(host, structureSource);
+ this.#structureSource = new structureSource(host);
+ }
+
+ /**
+ * Returns a promise with the allowed media-types of a uploadable content type.
+ * @param {string} unique
+ * @return {*}
+ * @memberof UmbContentTypeUploadableStructureRepositoryBase
+ */
+ requestAllowedMediaTypesOf(fileExtension: string | null) {
+ return this.#structureSource.getAllowedMediaTypesOf(fileExtension);
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/repository/content-type-uploadable-structure-repository.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/repository/content-type-uploadable-structure-repository.interface.ts
new file mode 100644
index 0000000000..b25083e1ae
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/repository/content-type-uploadable-structure-repository.interface.ts
@@ -0,0 +1,7 @@
+import type { UmbContentTypeStructureRepository } from '@umbraco-cms/backoffice/content-type';
+import type { UmbDataSourceResponse } from '@umbraco-cms/backoffice/repository';
+
+export interface UmbContentTypeUploadableStructureRepository
+ extends UmbContentTypeStructureRepository {
+ requestAllowedMediaTypesOf(unique: string): Promise>;
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/repository/content-type-uploadable-structure-server-data-source-base.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/repository/content-type-uploadable-structure-server-data-source-base.ts
new file mode 100644
index 0000000000..31e110ee31
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/repository/content-type-uploadable-structure-server-data-source-base.ts
@@ -0,0 +1,66 @@
+import {
+ type UmbContentTypeStructureDataSource,
+ UmbContentTypeStructureServerDataSourceBase,
+ type UmbContentTypeStructureServerDataSourceBaseArgs,
+} from '@umbraco-cms/backoffice/content-type';
+import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
+
+// Temp type
+type AllowedContentTypeModel = {
+ id: string;
+ name: string;
+ description?: string | null;
+ icon?: string | null;
+};
+
+export interface UmbContentTypeUploadableStructureServerDataSourceBaseArgs<
+ ServerItemType extends AllowedContentTypeModel,
+ ClientItemType extends { unique: string },
+> extends UmbContentTypeStructureServerDataSourceBaseArgs {
+ getAllowedMediaTypesOf: (fileExtension: string | null) => Promise;
+}
+
+export abstract class UmbContentTypeUploadableStructureServerDataSourceBase<
+ ServerItemType extends AllowedContentTypeModel,
+ ClientItemType extends { unique: string },
+ >
+ extends UmbContentTypeStructureServerDataSourceBase
+ implements UmbContentTypeStructureDataSource
+{
+ #host;
+ #getAllowedChildrenOf;
+ #mapper;
+
+ /**
+ * Creates an instance of UmbContentTypeStructureServerDataSourceBase.
+ * @param {UmbControllerHost} host
+ * @memberof UmbItemServerDataSourceBase
+ */
+ constructor(
+ host: UmbControllerHost,
+ args: UmbContentTypeUploadableStructureServerDataSourceBaseArgs,
+ ) {
+ super(host, args);
+ this.#host = host;
+ this.#getAllowedChildrenOf = args.getAllowedChildrenOf;
+ this.#mapper = args.mapper;
+ }
+
+ /**
+ * Returns a promise with the allowed content types for the given unique
+ * @param {string} unique
+ * @return {*}
+ * @memberof UmbContentTypeStructureServerDataSourceBase
+ */
+ async getAllowedMediaTypesOf(fileExtension: string | null) {
+ const { data, error } = await tryExecuteAndNotify(this.#host, this.#getAllowedChildrenOf(fileExtension));
+
+ if (data) {
+ const items = data.items.map((item) => this.#mapper(item));
+ return { data: { items, total: data.total } };
+ }
+
+ return { error };
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json
index af06940c67..03f8acb7a5 100644
--- a/src/Umbraco.Web.UI.Client/tsconfig.json
+++ b/src/Umbraco.Web.UI.Client/tsconfig.json
@@ -114,6 +114,7 @@
"@umbraco-cms/backoffice/external/dompurify": ["./src/external/dompurify/index.ts"],
"@umbraco-cms/backoffice/external/lit": ["./src/external/lit/index.ts"],
"@umbraco-cms/backoffice/external/marked": ["./src/external/marked/index.ts"],
+ "@umbraco-cms/backoffice/external/mime-types": ["./src/external/mime-types/index.ts"],
"@umbraco-cms/backoffice/external/monaco-editor": ["./src/external/monaco-editor/index.ts"],
"@umbraco-cms/backoffice/external/openid": ["./src/external/openid/index.ts"],
"@umbraco-cms/backoffice/external/router-slot": ["./src/external/router-slot/index.ts"],
From 87bafe1d01d83fd8b013f7c445e29a33a33562e6 Mon Sep 17 00:00:00 2001
From: Lone Iversen <108085781+loivsen@users.noreply.github.com>
Date: Wed, 1 May 2024 13:55:49 +0200
Subject: [PATCH 012/134] moving files
---
.../src/external/backend-api/src/models.ts | 132 ++++++------
.../src/external/backend-api/src/services.ts | 198 ++++++++++--------
.../src/external/mime-types/index.ts | 2 +-
.../src/packages/media/media-types/index.ts | 2 -
.../packages/media/media-types/utils/index.ts | 7 -
.../collection/media-collection.element.ts | 2 +-
.../dropzone-media/dropzone-media.element.ts | 19 +-
.../dropzone/dropzone-manager.class.ts | 46 ----
.../media/dropzone/dropzone-manager.class.ts | 146 +++++++++++++
.../dropzone/dropzone.element.ts | 34 +--
.../media/{components => }/dropzone/index.ts | 0
.../media/media/dropzone/manifests.ts | 1 +
...ropzone-media-type-picker-modal.element.ts | 55 +++++
.../dropzone-media-type-picker-modal.token.ts | 19 ++
.../dropzone-media-type-picker/index.ts | 2 +
.../media/media/dropzone/modals/index.ts | 1 +
.../media/media/dropzone/modals/manifests.ts | 12 ++
...oadable-structure-data-source.interface.ts | 0
...pe-uploadable-structure-repository-base.ts | 0
...loadable-structure-repository.interface.ts | 0
...dable-structure-server-data-source-base.ts | 0
.../src/packages/media/media/index.ts | 1 +
.../src/packages/media/media/manifests.ts | 2 +
23 files changed, 450 insertions(+), 231 deletions(-)
delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/utils/index.ts
delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/dropzone-manager.class.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts
rename src/Umbraco.Web.UI.Client/src/packages/media/media/{components => }/dropzone/dropzone.element.ts (92%)
rename src/Umbraco.Web.UI.Client/src/packages/media/media/{components => }/dropzone/index.ts (100%)
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/manifests.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/dropzone-media-type-picker/dropzone-media-type-picker-modal.element.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/dropzone-media-type-picker/dropzone-media-type-picker-modal.token.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/dropzone-media-type-picker/index.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/index.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/manifests.ts
rename src/Umbraco.Web.UI.Client/src/packages/media/media/{components => }/dropzone/repository/content-type-uploadable-structure-data-source.interface.ts (100%)
rename src/Umbraco.Web.UI.Client/src/packages/media/media/{components => }/dropzone/repository/content-type-uploadable-structure-repository-base.ts (100%)
rename src/Umbraco.Web.UI.Client/src/packages/media/media/{components => }/dropzone/repository/content-type-uploadable-structure-repository.interface.ts (100%)
rename src/Umbraco.Web.UI.Client/src/packages/media/media/{components => }/dropzone/repository/content-type-uploadable-structure-server-data-source-base.ts (100%)
diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models.ts
index 1987ac421e..0f950e5d28 100644
--- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models.ts
+++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models.ts
@@ -14,31 +14,14 @@ description?: string | null
icon?: string | null
};
-export type AuditLogEntityModel = {
- id?: string | null
-type?: string | null
- };
-
export type AuditLogResponseModel = {
user: ReferenceByIdModel
-entity?: AuditLogEntityModel | null
timestamp: string
logType: AuditTypeModel
comment?: string | null
parameters?: string | null
};
-export type AuditLogWithUsernameResponseModel = {
- user: ReferenceByIdModel
-entity?: AuditLogEntityModel | null
-timestamp: string
-logType: AuditTypeModel
-comment?: string | null
-parameters?: string | null
-userName?: string | null
-userAvatars: Array
- };
-
export enum AuditTypeModel {
NEW = 'New',
SAVE = 'Save',
@@ -130,7 +113,7 @@ export type CopyMediaTypeRequestModel = {
export type CreateDataTypeRequestModel = {
name: string
editorAlias: string
-editorUiAlias?: string | null
+editorUiAlias: string
values: Array
id?: string | null
parent?: ReferenceByIdModel | null
@@ -496,7 +479,7 @@ properties: Array
export type DataTypeResponseModel = {
name: string
editorAlias: string
-editorUiAlias?: string | null
+editorUiAlias: string
values: Array
id: string
isDeletable: boolean
@@ -1396,6 +1379,14 @@ id: string
compositions: Array
};
+export type MemberTypeTreeItemResponseModel = {
+ hasChildren: boolean
+id: string
+parent?: ReferenceByIdModel | null
+name: string
+icon: string
+ };
+
export type MemberValueModel = {
culture?: string | null
segment?: string | null
@@ -1477,6 +1468,10 @@ category: string
type: EventMessageTypeModel
};
+export type OEmbedResponseModel = {
+ markup: string
+ };
+
export type ObjectTypeResponseModel = {
name?: string | null
id: string
@@ -1546,11 +1541,6 @@ export type PagedAuditLogResponseModel = {
items: Array
};
-export type PagedAuditLogWithUsernameResponseModel = {
- total: number
-items: Array
- };
-
export type PagedCultureReponseModel = {
total: number
items: Array
@@ -1681,6 +1671,11 @@ export type PagedMemberResponseModel = {
items: Array
};
+export type PagedMemberTypeTreeItemResponseModel = {
+ total: number
+items: Array
+ };
+
export type PagedModelDataTypeItemResponseModel = {
items: Array
total: number
@@ -2268,7 +2263,7 @@ export type UnpublishDocumentRequestModel = {
export type UpdateDataTypeRequestModel = {
name: string
editorAlias: string
-editorUiAlias?: string | null
+editorUiAlias: string
values: Array
};
@@ -2703,43 +2698,6 @@ id: string
events: Array
};
-export type AuditLogData = {
-
- payloads: {
- GetAuditLog: {
- orderDirection?: DirectionModel
-sinceDate?: string
-skip?: number
-take?: number
-
- };
-GetAuditLogById: {
- id: string
-orderDirection?: DirectionModel
-sinceDate?: string
-skip?: number
-take?: number
-
- };
-GetAuditLogTypeByLogType: {
- logType: AuditTypeModel
-sinceDate?: string
-skip?: number
-take?: number
-
- };
- }
-
-
- responses: {
- GetAuditLog: PagedAuditLogWithUsernameResponseModel
- ,GetAuditLogById: PagedAuditLogResponseModel
- ,GetAuditLogTypeByLogType: PagedAuditLogResponseModel
-
- }
-
- }
-
export type CultureData = {
payloads: {
@@ -3242,6 +3200,14 @@ PutDocumentById: {
id: string
requestBody?: UpdateDocumentRequestModel
+ };
+GetDocumentByIdAuditLog: {
+ id: string
+orderDirection?: DirectionModel
+sinceDate?: string
+skip?: number
+take?: number
+
};
PostDocumentByIdCopy: {
id: string
@@ -3403,6 +3369,7 @@ take?: number
,GetDocumentById: DocumentResponseModel
,DeleteDocumentById: string
,PutDocumentById: string
+ ,GetDocumentByIdAuditLog: PagedAuditLogResponseModel
,PostDocumentByIdCopy: string
,GetDocumentByIdDomains: DomainsResponseModel
,PutDocumentByIdDomains: string
@@ -3858,6 +3825,14 @@ PutMediaById: {
id: string
requestBody?: UpdateMediaRequestModel
+ };
+GetMediaByIdAuditLog: {
+ id: string
+orderDirection?: DirectionModel
+sinceDate?: string
+skip?: number
+take?: number
+
};
PutMediaByIdMove: {
id: string
@@ -3955,6 +3930,7 @@ take?: number
,GetMediaById: MediaResponseModel
,DeleteMediaById: string
,PutMediaById: string
+ ,GetMediaByIdAuditLog: PagedAuditLogResponseModel
,PutMediaByIdMove: string
,PutMediaByIdMoveToRecycleBin: string
,GetMediaByIdReferencedBy: PagedIReferenceResponseModel
@@ -4089,7 +4065,7 @@ take?: number
,GetMemberTypeByIdCompositionReferences: Array
,PostMemberTypeByIdCopy: string
,PostMemberTypeAvailableCompositions: Array
- ,GetTreeMemberTypeRoot: PagedNamedEntityTreeItemResponseModel
+ ,GetTreeMemberTypeRoot: PagedMemberTypeTreeItemResponseModel
}
@@ -4195,6 +4171,25 @@ take?: number
}
+export type OembedData = {
+
+ payloads: {
+ GetOembedQuery: {
+ maxHeight?: number
+maxWidth?: number
+url?: string
+
+ };
+ }
+
+
+ responses: {
+ GetOembedQuery: OEmbedResponseModel
+
+ }
+
+ }
+
export type PackageData = {
payloads: {
@@ -4952,7 +4947,13 @@ GetUserDataById: {
export type UserGroupData = {
payloads: {
- GetItemUserGroup: {
+ GetFilterUserGroup: {
+ filter?: string
+skip?: number
+take?: number
+
+ };
+GetItemUserGroup: {
id?: Array
};
@@ -4996,7 +4997,8 @@ requestBody?: Array
responses: {
- GetItemUserGroup: Array
+ GetFilterUserGroup: PagedUserGroupResponseModel
+ ,GetItemUserGroup: Array
,DeleteUserGroup: string
,PostUserGroup: string
,GetUserGroup: PagedUserGroupResponseModel
diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services.ts
index 64e967c0c7..476f2ab3c6 100644
--- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services.ts
+++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services.ts
@@ -1,91 +1,7 @@
import type { CancelablePromise } from './core/CancelablePromise';
import { OpenAPI } from './core/OpenAPI';
import { request as __request } from './core/request';
-import type { AuditLogData, CultureData, DataTypeData, DictionaryData, DocumentBlueprintData, DocumentTypeData, DocumentVersionData, DocumentData, DynamicRootData, HealthCheckData, HelpData, IndexerData, InstallData, LanguageData, LogViewerData, ManifestData, MediaTypeData, MediaData, MemberGroupData, MemberTypeData, MemberData, ModelsBuilderData, ObjectTypesData, PackageData, PartialViewData, PreviewData, ProfilingData, PropertyTypeData, PublishedCacheData, RedirectManagementData, RelationTypeData, RelationData, ScriptData, SearcherData, SecurityData, SegmentData, ServerData, StaticFileData, StylesheetData, TagData, TelemetryData, TemplateData, TemporaryFileData, UpgradeData, UserDataData, UserGroupData, UserData, WebhookData } from './models';
-
-export class AuditLogService {
-
- /**
- * @returns unknown Success
- * @throws ApiError
- */
- public static getAuditLog(data: AuditLogData['payloads']['GetAuditLog'] = {}): CancelablePromise {
- const {
-
- orderDirection,
-sinceDate,
-skip,
-take
- } = data;
- return __request(OpenAPI, {
- method: 'GET',
- url: '/umbraco/management/api/v1/audit-log',
- query: {
- orderDirection, sinceDate, skip, take
- },
- errors: {
- 401: `The resource is protected and requires an authentication token`,
- },
- });
- }
-
- /**
- * @returns unknown Success
- * @throws ApiError
- */
- public static getAuditLogById(data: AuditLogData['payloads']['GetAuditLogById']): CancelablePromise {
- const {
-
- id,
-orderDirection,
-sinceDate,
-skip,
-take
- } = data;
- return __request(OpenAPI, {
- method: 'GET',
- url: '/umbraco/management/api/v1/audit-log/{id}',
- path: {
- id
- },
- query: {
- orderDirection, sinceDate, skip, take
- },
- errors: {
- 401: `The resource is protected and requires an authentication token`,
- 403: `The authenticated user do not have access to this resource`,
- },
- });
- }
-
- /**
- * @returns unknown Success
- * @throws ApiError
- */
- public static getAuditLogTypeByLogType(data: AuditLogData['payloads']['GetAuditLogTypeByLogType']): CancelablePromise {
- const {
-
- logType,
-sinceDate,
-skip,
-take
- } = data;
- return __request(OpenAPI, {
- method: 'GET',
- url: '/umbraco/management/api/v1/audit-log/type/{logType}',
- path: {
- logType
- },
- query: {
- sinceDate, skip, take
- },
- errors: {
- 401: `The resource is protected and requires an authentication token`,
- },
- });
- }
-
-}
+import type { CultureData, DataTypeData, DictionaryData, DocumentBlueprintData, DocumentTypeData, DocumentVersionData, DocumentData, DynamicRootData, HealthCheckData, HelpData, IndexerData, InstallData, LanguageData, LogViewerData, ManifestData, MediaTypeData, MediaData, MemberGroupData, MemberTypeData, MemberData, ModelsBuilderData, ObjectTypesData, OembedData, PackageData, PartialViewData, PreviewData, ProfilingData, PropertyTypeData, PublishedCacheData, RedirectManagementData, RelationTypeData, RelationData, ScriptData, SearcherData, SecurityData, SegmentData, ServerData, StaticFileData, StylesheetData, TagData, TelemetryData, TemplateData, TemporaryFileData, UpgradeData, UserDataData, UserGroupData, UserData, WebhookData } from './models';
export class CultureService {
@@ -1987,6 +1903,35 @@ requestBody
});
}
+ /**
+ * @returns unknown Success
+ * @throws ApiError
+ */
+ public static getDocumentByIdAuditLog(data: DocumentData['payloads']['GetDocumentByIdAuditLog']): CancelablePromise {
+ const {
+
+ id,
+orderDirection,
+sinceDate,
+skip,
+take
+ } = data;
+ return __request(OpenAPI, {
+ method: 'GET',
+ url: '/umbraco/management/api/v1/document/{id}/audit-log',
+ path: {
+ id
+ },
+ query: {
+ orderDirection, sinceDate, skip, take
+ },
+ errors: {
+ 401: `The resource is protected and requires an authentication token`,
+ 403: `The authenticated user do not have access to this resource`,
+ },
+ });
+ }
+
/**
* @returns string Created
* @throws ApiError
@@ -4213,6 +4158,35 @@ requestBody
});
}
+ /**
+ * @returns unknown Success
+ * @throws ApiError
+ */
+ public static getMediaByIdAuditLog(data: MediaData['payloads']['GetMediaByIdAuditLog']): CancelablePromise {
+ const {
+
+ id,
+orderDirection,
+sinceDate,
+skip,
+take
+ } = data;
+ return __request(OpenAPI, {
+ method: 'GET',
+ url: '/umbraco/management/api/v1/media/{id}/audit-log',
+ path: {
+ id
+ },
+ query: {
+ orderDirection, sinceDate, skip, take
+ },
+ errors: {
+ 401: `The resource is protected and requires an authentication token`,
+ 403: `The authenticated user do not have access to this resource`,
+ },
+ });
+ }
+
/**
* @returns string Success
* @throws ApiError
@@ -5414,6 +5388,34 @@ take
}
+export class OEmbedService {
+
+ /**
+ * @returns unknown Success
+ * @throws ApiError
+ */
+ public static getOembedQuery(data: OembedData['payloads']['GetOembedQuery'] = {}): CancelablePromise {
+ const {
+
+ url,
+maxWidth,
+maxHeight
+ } = data;
+ return __request(OpenAPI, {
+ method: 'GET',
+ url: '/umbraco/management/api/v1/oembed/query',
+ query: {
+ url, maxWidth, maxHeight
+ },
+ errors: {
+ 401: `The resource is protected and requires an authentication token`,
+ 403: `The authenticated user do not have access to this resource`,
+ },
+ });
+ }
+
+}
+
export class PackageService {
/**
@@ -7870,6 +7872,32 @@ take
export class UserGroupService {
+ /**
+ * @returns unknown Success
+ * @throws ApiError
+ */
+ public static getFilterUserGroup(data: UserGroupData['payloads']['GetFilterUserGroup'] = {}): CancelablePromise {
+ const {
+
+ skip,
+take,
+filter
+ } = data;
+ return __request(OpenAPI, {
+ method: 'GET',
+ url: '/umbraco/management/api/v1/filter/user-group',
+ query: {
+ skip, take, filter
+ },
+ errors: {
+ 400: `Bad Request`,
+ 401: `The resource is protected and requires an authentication token`,
+ 403: `The authenticated user do not have access to this resource`,
+ 404: `Not Found`,
+ },
+ });
+ }
+
/**
* @returns unknown Success
* @throws ApiError
diff --git a/src/Umbraco.Web.UI.Client/src/external/mime-types/index.ts b/src/Umbraco.Web.UI.Client/src/external/mime-types/index.ts
index 38a5df465f..44b10e99aa 100644
--- a/src/Umbraco.Web.UI.Client/src/external/mime-types/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/external/mime-types/index.ts
@@ -1 +1 @@
-export * as mime from 'mime-types';
+export { extension as mimeToExtension } from 'mime-types';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/index.ts
index e746aa874e..94205d2f84 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/index.ts
@@ -7,6 +7,4 @@ export * from './repository/index.js';
export * from './tree/types.js';
export * from './types.js';
-export * from './utils/index.js';
-
export { UMB_MEDIA_TYPE_PICKER_MODAL } from './tree/index.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/utils/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/utils/index.ts
deleted file mode 100644
index 2557bafb9c..0000000000
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/utils/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { mime } from '@umbraco-cms/backoffice/external/mime-types';
-
-export function getExtensionFromMime(mimeType: string): string | undefined {
- const extension = mime.extension(mimeType);
- if (!extension) return; // extension doesn't exist.
- return extension;
-}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.element.ts
index dd74cf2241..d3e7300801 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.element.ts
@@ -32,7 +32,7 @@ export class UmbMediaCollectionElement extends UmbCollectionDefaultElement {
return html`
${when(this._progress >= 0, () => html``)}
-
+
`;
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone-media/dropzone-media.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone-media/dropzone-media.element.ts
index 6a03dc5e19..12e32b5490 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone-media/dropzone-media.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone-media/dropzone-media.element.ts
@@ -3,11 +3,7 @@ import type { UmbMediaDetailModel } from '../../types.js';
import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
import type { UUIFileDropzoneEvent } from '@umbraco-cms/backoffice/external/uui';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
-import {
- type UmbAllowedMediaTypeModel,
- UmbMediaTypeStructureRepository,
- getMediaTypeByFileMimeType,
-} from '@umbraco-cms/backoffice/media-type';
+import { type UmbAllowedMediaTypeModel, UmbMediaTypeStructureRepository } from '@umbraco-cms/backoffice/media-type';
import {
UmbTemporaryFileManager,
type UmbTemporaryFileQueueModel,
@@ -66,11 +62,12 @@ export class UmbDropzoneMediaElement extends UmbLitElement {
if (!data) return;
this.#allowedMediaTypes = data.items;
}
-
- #getMediaTypeFromMime(mimetype: string): UmbAllowedMediaTypeModel {
- const mediaTypeName = getMediaTypeByFileMimeType(mimetype);
- return this.#allowedMediaTypes.find((type) => type.name === mediaTypeName)!;
+ /*
+ #getMediaTypeFromMime(mimetype: string): UmbAllowedMediaTypeModel | undefined {
+ //const mediaTypeName = getMediaTypeByFileMimeType(mimetype);
+ //return this.#allowedMediaTypes.find((type) => type.name === mediaTypeName)!;
}
+ */
async #uploadHandler(files: Array) {
const queue = files.map((file): UmbTemporaryFileQueueModel => ({ file }));
@@ -84,11 +81,11 @@ export class UmbDropzoneMediaElement extends UmbLitElement {
const uploads = await this.#uploadHandler(files);
for (const upload of uploads) {
- const mediaType = this.#getMediaTypeFromMime(upload.file.type);
+ //const mediaType = this.#getMediaTypeFromMime(upload.file.type);
const preset: Partial = {
mediaType: {
- unique: mediaType.unique,
+ unique: /*mediaType.unique*/ '123',
collection: null,
},
variants: [
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/dropzone-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/dropzone-manager.class.ts
deleted file mode 100644
index 6abcd410ba..0000000000
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/dropzone-manager.class.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import type { UmbContentTypeUploadableStructureRepositoryBase } from './repository/content-type-uploadable-structure-repository-base.js';
-import { UmbId } from '@umbraco-cms/backoffice/id';
-import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
-import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
-import type { UmbMediaTypeItemModel } from '@umbraco-cms/backoffice/media-type';
-import { UmbTemporaryFileManager } from '@umbraco-cms/backoffice/temporary-file';
-
-export class UmbDropzoneManager extends UmbControllerBase {
- #init!: Promise;
-
- #fileManager = new UmbTemporaryFileManager(this);
- #repository: UmbContentTypeUploadableStructureRepositoryBase;
-
- #parentUnique: string | null = null;
-
- constructor(
- host: UmbControllerHost,
- typeRepository: UmbContentTypeUploadableStructureRepositoryBase,
- parentUnique: string | null,
- ) {
- super(host);
- this.#repository = typeRepository;
- this.#parentUnique = parentUnique;
- }
-
- public async dropOneFile(file: File) {
- const matchingMediaTypes = await this.#repository.requestAllowedMediaTypesOf(file.type);
- //const options = this.#allowedMediaTypes.filter((allowedMediaType) => matchingMediaTypes.includes(allowedMediaType));
- }
-
- public async dropFiles(files: Array) {}
-
- async #requestAllowedMediaTypesOf(fileExtension: string) {
- const { data } = await this.#repository.requestAllowedMediaTypesOf(fileExtension);
- //const mediaTypes = data?.filter((option) => this.#allowedMediaTypes.includes(option));
- //return { fileExtension, mediaTypes };
- }
-
- private _reset() {
- //
- }
-
- public destroy() {
- super.destroy();
- }
-}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts
new file mode 100644
index 0000000000..f8885f58b1
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts
@@ -0,0 +1,146 @@
+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 { mimeToExtension } from '@umbraco-cms/backoffice/external/mime-types';
+import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
+import {
+ type UmbAllowedMediaTypeModel,
+ UmbMediaTypeDetailRepository,
+ UmbMediaTypeStructureRepository,
+} from '@umbraco-cms/backoffice/media-type';
+import { UmbTemporaryFileManager } from '@umbraco-cms/backoffice/temporary-file';
+import { UmbId } from '@umbraco-cms/backoffice/id';
+import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
+
+interface UmbUploadableFileModel {
+ unique: string;
+ file: File;
+ mediaTypeUnique: string;
+}
+
+interface UmbUploadableFileExtensionModel {
+ fileExtension: string;
+ mediaTypes: Array;
+}
+
+export function getExtensionFromMime(mimeType: string): string | undefined {
+ const extension = mimeToExtension(mimeType);
+ if (!extension) return; // extension doesn't exist.
+ return extension;
+}
+
+export class UmbDropzoneManager extends UmbControllerBase {
+ #host;
+ #tempFileManager = new UmbTemporaryFileManager(this);
+
+ #mediaTypeStructure = new UmbMediaTypeStructureRepository(this);
+ #mediaDetailRepository = new UmbMediaDetailRepository(this);
+ #mediaTypeDetailRepository = new UmbMediaTypeDetailRepository(this);
+
+ #parentUnique: string | null;
+
+ #getExtensionFromMimeType(mimeType: string): string {
+ return getExtensionFromMime(mimeType) || '';
+ }
+
+ async #buildOptionsArrayFrom(fileExtensions: Array): Promise> {
+ // Getting all media types allowed in our current position based on parent unique.
+ const { data: allAllowedMediaTypes } = await this.#mediaTypeStructure.requestAllowedChildrenOf(this.#parentUnique);
+ if (!allAllowedMediaTypes?.items.length) return [];
+
+ const allowedByParent = allAllowedMediaTypes.items;
+
+ // Building an array of options the files can be uploaded as.
+ const options: Array = [];
+
+ for (const fileExtension of fileExtensions) {
+ const extensionOptions = await this.#mediaTypeStructure.requestMediaTypesOf({ fileExtension });
+ const mediaTypes = extensionOptions.filter((option) => allowedByParent.includes(option));
+ options.push({ fileExtension, mediaTypes });
+ }
+
+ return options;
+ }
+
+ constructor(host: UmbControllerHost, parentUnique: string | null) {
+ super(host);
+ this.#host = host;
+ this.#parentUnique = parentUnique;
+ }
+
+ async #showDialogMediaTypePicker(options: Array) {
+ 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;
+ }
+
+ public async dropOneFile(file: File) {
+ const extension = this.#getExtensionFromMimeType(file.type);
+ if (!extension) return; // Extension doesn't exist.
+
+ const optionsArray = await this.#buildOptionsArrayFrom([extension]);
+ if (!optionsArray.length) return; // File not allowed in current dropzone.
+
+ const mediaTypes = optionsArray[0].mediaTypes; // Because we are only uploading one file.
+
+ if (mediaTypes.length === 1) {
+ // Only one allowed option, upload file using that option.
+ const uploadableFile: UmbUploadableFileModel = {
+ unique: UmbId.new(),
+ file,
+ mediaTypeUnique: mediaTypes[0].unique,
+ };
+
+ console.log('you made it!', uploadableFile);
+ 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(),
+ file,
+ mediaTypeUnique: mediaType,
+ };
+ console.log('you made it!', uploadableFile);
+ }
+
+ public async dropFiles(files: Array) {
+ // removes duplicate file types so we don't call endpoints unnecessarily when building options.
+ const mimeTypes = [...new Set(files.map((file) => file.type))];
+ const optionsArray = await this.#buildOptionsArrayFrom(
+ mimeTypes.map((mimetype) => this.#getExtensionFromMimeType(mimetype)),
+ );
+
+ 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 = [];
+
+ for (const file of files) {
+ const extension = this.#getExtensionFromMimeType(file.type);
+ const options = optionsArray.find((option) => option.fileExtension === extension)?.mediaTypes;
+
+ if (!options) return; // Dropped file not allowed in current dropzone.
+
+ // 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({ unique: UmbId.new(), file, mediaTypeUnique: mediaType.unique });
+ }
+
+ console.log('you made it!', uploadableFiles);
+ }
+
+ private _reset() {
+ //
+ }
+
+ public destroy() {
+ super.destroy();
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/dropzone.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone.element.ts
similarity index 92%
rename from src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/dropzone.element.ts
rename to src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone.element.ts
index d4885ccbec..96f6e638a0 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/dropzone.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone.element.ts
@@ -1,5 +1,6 @@
-import { UmbMediaDetailRepository } from '../../repository/index.js';
-import type { UmbMediaDetailModel } from '../../types.js';
+import { UmbMediaDetailRepository } from '../repository/index.js';
+import type { UmbMediaDetailModel } from '../types.js';
+import { UmbDropzoneManager } from './dropzone-manager.class.js';
import { css, html, customElement, state, property } from '@umbraco-cms/backoffice/external/lit';
import type { UUIFileDropzoneElement, UUIFileDropzoneEvent } from '@umbraco-cms/backoffice/external/uui';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
@@ -7,7 +8,6 @@ import {
type UmbAllowedMediaTypeModel,
UmbMediaTypeStructureRepository,
UmbMediaTypeDetailRepository,
- getExtensionFromMime,
} from '@umbraco-cms/backoffice/media-type';
import {
UmbTemporaryFileManager,
@@ -35,13 +35,22 @@ export class UmbDropzoneElement extends UmbLitElement {
#mediaDetailRepository = new UmbMediaDetailRepository(this);
#mediaTypeDetailRepository = new UmbMediaTypeDetailRepository(this);
+ #parentUnique: string | null = null;
+ #dropzoneManager = new UmbDropzoneManager(this, null);
+
#allowedMediaTypes: Array = [];
@state()
private queue: Array = [];
@property({ attribute: false })
- parentUnique: string | null = null;
+ public set parentUnique(value: string | null) {
+ this.#parentUnique = value;
+ //this.#dropzoneManager.setParentUnique(value);
+ }
+ public get parentUnique(): string | null {
+ return this.#parentUnique;
+ }
public browse() {
const element = this.shadowRoot?.querySelector('#dropzone') as UUIFileDropzoneElement;
@@ -92,17 +101,13 @@ export class UmbDropzoneElement extends UmbLitElement {
const files: Array = event.detail.files;
if (!files.length) return;
- this.#allowedMediaTypes = await this.#getAllowedMediaTypes();
- if (!this.#allowedMediaTypes.length) return;
- // If we have files that are not allowed to be uploaded, we show those in a dialog to the user?
-
if (files.length === 1) {
- this.#handleOneFile(files[0]);
+ this.#dropzoneManager.dropOneFile(files[0]);
} else {
- this.#handleMultipleFiles(files);
+ this.#dropzoneManager.dropFiles(files);
}
}
-
+ /*
async #handleOneFile(file: File) {
const extension = getExtensionFromMime(file.type);
if (!extension) return; // Extension doesn't exist.
@@ -201,6 +206,7 @@ export class UmbDropzoneElement extends UmbLitElement {
return [...folders, ...uploaded];
}
+
async #onFileUpload(event: UUIFileDropzoneEvent) {
const files: Array = event.detail.files;
@@ -208,7 +214,7 @@ export class UmbDropzoneElement extends UmbLitElement {
const uploads = await this.#uploadHandler(files);
for (const upload of uploads) {
- const mediaType = /*this.#getMediaTypeFromMime(upload.file.type); */ '' as any;
+ const mediaType = this.#getMediaTypeFromMime(upload.file.type);
const value = mediaType.unique === UmbMediaTypeFileType.IMAGE ? { src: upload.unique } : upload.unique;
const preset: Partial = {
@@ -246,11 +252,13 @@ export class UmbDropzoneElement extends UmbLitElement {
}
}
+ */
+
render() {
return html``;
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/index.ts
similarity index 100%
rename from src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/index.ts
rename to src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/index.ts
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/manifests.ts
new file mode 100644
index 0000000000..c777b79000
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/manifests.ts
@@ -0,0 +1 @@
+export * from './modals/manifests.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/dropzone-media-type-picker/dropzone-media-type-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/dropzone-media-type-picker/dropzone-media-type-picker-modal.element.ts
new file mode 100644
index 0000000000..8180a3df83
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/dropzone-media-type-picker/dropzone-media-type-picker-modal.element.ts
@@ -0,0 +1,55 @@
+import type {
+ UmbDropzoneMediaTypePickerModalData,
+ UmbDropzoneMediaTypePickerModalValue,
+} from './dropzone-media-type-picker-modal.token.js';
+import { css, customElement, html, repeat, state } from '@umbraco-cms/backoffice/external/lit';
+import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
+import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
+import type { UmbAllowedMediaTypeModel } from '@umbraco-cms/backoffice/media-type';
+
+@customElement('umb-dropzone-media-type-picker-modal')
+export class UmbDropzoneMediaTypePickerModalElement extends UmbModalBaseElement<
+ UmbDropzoneMediaTypePickerModalData,
+ UmbDropzoneMediaTypePickerModalValue
+> {
+ @state()
+ private _options: Array = [];
+
+ connectedCallback() {
+ super.connectedCallback();
+ this._options = this.data?.options ?? [];
+ }
+
+ #onMediaTypePick(unique?: string) {
+ this.value = { mediaTypeUnique: unique };
+ this._submitModal();
+ }
+
+ render() {
+ return html` this.#onMediaTypePick()} label="Automatically" compact>
+
+ ${repeat(
+ this._options,
+ (option) => option.unique,
+ (option) =>
+ html` this.#onMediaTypePick(option.unique)}
+ label=${option.name}
+ compact>
+
+ `,
+ )}`;
+ }
+
+ static styles = [UmbTextStyles, css``];
+}
+
+export default UmbDropzoneMediaTypePickerModalElement;
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'umb-dropzone-media-type-picker-modal': UmbDropzoneMediaTypePickerModalElement;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/dropzone-media-type-picker/dropzone-media-type-picker-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/dropzone-media-type-picker/dropzone-media-type-picker-modal.token.ts
new file mode 100644
index 0000000000..4cd4a52974
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/dropzone-media-type-picker/dropzone-media-type-picker-modal.token.ts
@@ -0,0 +1,19 @@
+import type { UmbAllowedMediaTypeModel } from '@umbraco-cms/backoffice/media-type';
+import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
+
+export interface UmbDropzoneMediaTypePickerModalData {
+ options: Array;
+}
+
+export type UmbDropzoneMediaTypePickerModalValue = {
+ mediaTypeUnique: string | undefined;
+};
+
+export const UMB_DROPZONE_MEDIA_TYPE_PICKER_MODAL = new UmbModalToken<
+ UmbDropzoneMediaTypePickerModalData,
+ UmbDropzoneMediaTypePickerModalValue
+>('Umb.Modal.Dropzone.MediaTypePicker', {
+ modal: {
+ type: 'dialog',
+ },
+});
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/dropzone-media-type-picker/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/dropzone-media-type-picker/index.ts
new file mode 100644
index 0000000000..b698197915
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/dropzone-media-type-picker/index.ts
@@ -0,0 +1,2 @@
+export * from './dropzone-media-type-picker-modal.element.js';
+export * from './dropzone-media-type-picker-modal.token.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/index.ts
new file mode 100644
index 0000000000..cd9b3459fe
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/index.ts
@@ -0,0 +1 @@
+export * from './dropzone-media-type-picker/index.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/manifests.ts
new file mode 100644
index 0000000000..e6500d5a4e
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/manifests.ts
@@ -0,0 +1,12 @@
+import type { ManifestModal, ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
+
+const modals: Array = [
+ {
+ type: 'modal',
+ alias: 'Umb.Modal.Dropzone.MediaTypePicker',
+ name: 'Dropzone Media Type Picker Modal',
+ js: () => import('./dropzone-media-type-picker/dropzone-media-type-picker-modal.element.js'),
+ },
+];
+
+export const manifests: Array = [...modals];
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/repository/content-type-uploadable-structure-data-source.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/repository/content-type-uploadable-structure-data-source.interface.ts
similarity index 100%
rename from src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/repository/content-type-uploadable-structure-data-source.interface.ts
rename to src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/repository/content-type-uploadable-structure-data-source.interface.ts
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/repository/content-type-uploadable-structure-repository-base.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/repository/content-type-uploadable-structure-repository-base.ts
similarity index 100%
rename from src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/repository/content-type-uploadable-structure-repository-base.ts
rename to src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/repository/content-type-uploadable-structure-repository-base.ts
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/repository/content-type-uploadable-structure-repository.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/repository/content-type-uploadable-structure-repository.interface.ts
similarity index 100%
rename from src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/repository/content-type-uploadable-structure-repository.interface.ts
rename to src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/repository/content-type-uploadable-structure-repository.interface.ts
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/repository/content-type-uploadable-structure-server-data-source-base.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/repository/content-type-uploadable-structure-server-data-source-base.ts
similarity index 100%
rename from src/Umbraco.Web.UI.Client/src/packages/media/media/components/dropzone/repository/content-type-uploadable-structure-server-data-source-base.ts
rename to src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/repository/content-type-uploadable-structure-server-data-source-base.ts
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts
index 774113923c..24f307b2f6 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts
@@ -4,6 +4,7 @@ export * from './repository/index.js';
export * from './workspace/index.js';
export * from './reference/index.js';
export * from './components/index.js';
+export * from './dropzone/index.js';
export * from './entity.js';
export * from './utils/index.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/manifests.ts
index 1d355b70f6..61ae7c5407 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/manifests.ts
@@ -1,4 +1,5 @@
import { manifests as collectionManifests } from './collection/manifests.js';
+import { manifests as dropzoneManifests } from './dropzone/manifests.js';
import { manifests as entityActionsManifests } from './entity-actions/manifests.js';
import { manifests as entityBulkActionsManifests } from './entity-bulk-actions/manifests.js';
import { manifests as menuManifests } from './menu/manifests.js';
@@ -12,6 +13,7 @@ import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array = [
...collectionManifests,
+ ...dropzoneManifests,
...entityActionsManifests,
...entityBulkActionsManifests,
...menuManifests,
From e713fc83bc973eb58660318953c16d8f773c8d1d Mon Sep 17 00:00:00 2001
From: Lone Iversen <108085781+loivsen@users.noreply.github.com>
Date: Wed, 1 May 2024 16:19:04 +0200
Subject: [PATCH 013/134] modal
---
.../media/dropzone/dropzone-manager.class.ts | 104 +++++++++++-------
...ropzone-media-type-picker-modal.element.ts | 45 ++++++--
.../dropzone-media-type-picker-modal.token.ts | 1 +
3 files changed, 104 insertions(+), 46 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts
index f8885f58b1..f9caed9882 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts
@@ -18,7 +18,7 @@ interface UmbUploadableFileModel {
mediaTypeUnique: string;
}
-interface UmbUploadableFileExtensionModel {
+export interface UmbUploadableFileExtensionModel {
fileExtension: string;
mediaTypes: Array;
}
@@ -39,52 +39,32 @@ export class UmbDropzoneManager extends UmbControllerBase {
#parentUnique: string | null;
- #getExtensionFromMimeType(mimeType: string): string {
- return getExtensionFromMime(mimeType) || '';
- }
-
- async #buildOptionsArrayFrom(fileExtensions: Array): Promise> {
- // Getting all media types allowed in our current position based on parent unique.
- const { data: allAllowedMediaTypes } = await this.#mediaTypeStructure.requestAllowedChildrenOf(this.#parentUnique);
- if (!allAllowedMediaTypes?.items.length) return [];
-
- const allowedByParent = allAllowedMediaTypes.items;
-
- // Building an array of options the files can be uploaded as.
- const options: Array = [];
-
- for (const fileExtension of fileExtensions) {
- const extensionOptions = await this.#mediaTypeStructure.requestMediaTypesOf({ fileExtension });
- const mediaTypes = extensionOptions.filter((option) => allowedByParent.includes(option));
- options.push({ fileExtension, mediaTypes });
- }
-
- return options;
- }
-
constructor(host: UmbControllerHost, parentUnique: string | null) {
super(host);
this.#host = host;
this.#parentUnique = parentUnique;
}
- async #showDialogMediaTypePicker(options: Array) {
- 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;
+ public setParentUnique(parentUnique: string | null) {
+ this.#parentUnique = parentUnique;
+ }
+ public getParentUnique() {
+ return this.#parentUnique;
}
public async dropOneFile(file: File) {
const extension = this.#getExtensionFromMimeType(file.type);
- if (!extension) return; // Extension doesn't exist.
+
+ if (!extension) {
+ // Folders have no extension on file drop. We assume it is a folder being uploaded.
+ this.#handleFolder(file);
+ return;
+ }
const optionsArray = await this.#buildOptionsArrayFrom([extension]);
- if (!optionsArray.length) return; // File not allowed in current dropzone.
-
- const mediaTypes = optionsArray[0].mediaTypes; // Because we are only uploading one file.
+ if (!optionsArray.length) throw new Error('File 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 = {
@@ -93,7 +73,7 @@ export class UmbDropzoneManager extends UmbControllerBase {
mediaTypeUnique: mediaTypes[0].unique,
};
- console.log('you made it!', uploadableFile);
+ await this.#uploadOne(uploadableFile);
return;
}
@@ -104,9 +84,9 @@ export class UmbDropzoneManager extends UmbControllerBase {
const uploadableFile: UmbUploadableFileModel = {
unique: UmbId.new(),
file,
- mediaTypeUnique: mediaType,
+ mediaTypeUnique: mediaType.unique,
};
- console.log('you made it!', uploadableFile);
+ await this.#uploadOne(uploadableFile);
}
public async dropFiles(files: Array) {
@@ -123,6 +103,11 @@ export class UmbDropzoneManager extends UmbControllerBase {
for (const file of files) {
const extension = this.#getExtensionFromMimeType(file.type);
+ if (!extension) {
+ // Folders have no extension on file drop. We assume it is a folder being uploaded.
+ this.#handleFolder(file);
+ return;
+ }
const options = optionsArray.find((option) => option.fileExtension === extension)?.mediaTypes;
if (!options) return; // Dropped file not allowed in current dropzone.
@@ -133,7 +118,50 @@ export class UmbDropzoneManager extends UmbControllerBase {
uploadableFiles.push({ unique: UmbId.new(), file, mediaTypeUnique: mediaType.unique });
}
- console.log('you made it!', uploadableFiles);
+ await this.#upload(uploadableFiles);
+ }
+
+ #getExtensionFromMimeType(mimeType: string): string {
+ return getExtensionFromMime(mimeType) || '';
+ }
+
+ async #buildOptionsArrayFrom(fileExtensions: Array): Promise> {
+ // Getting all media types allowed in our current position based on parent unique.
+ const { data: allAllowedMediaTypes } = await this.#mediaTypeStructure.requestAllowedChildrenOf(this.#parentUnique);
+ if (!allAllowedMediaTypes?.items.length) return [];
+
+ const allowedByParent = allAllowedMediaTypes.items;
+
+ // Building an array of options the files can be uploaded as.
+ const options: Array = [];
+
+ 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;
+ }
+
+ async #showDialogMediaTypePicker(options: Array) {
+ 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;
+ }
+
+ async #upload(files: Array) {
+ await this.#tempFileManager.upload(files);
+ }
+
+ async #uploadOne(file: UmbUploadableFileModel) {
+ await this.#tempFileManager.uploadOne(file);
+ }
+
+ async #handleFolder(file: File) {
+ throw new Error('Not implemented: Folders coming soon!');
}
private _reset() {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/dropzone-media-type-picker/dropzone-media-type-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/dropzone-media-type-picker/dropzone-media-type-picker-modal.element.ts
index 8180a3df83..859a8d77cc 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/dropzone-media-type-picker/dropzone-media-type-picker-modal.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/dropzone-media-type-picker/dropzone-media-type-picker-modal.element.ts
@@ -2,10 +2,11 @@ import type {
UmbDropzoneMediaTypePickerModalData,
UmbDropzoneMediaTypePickerModalValue,
} from './dropzone-media-type-picker-modal.token.js';
-import { css, customElement, html, repeat, state } from '@umbraco-cms/backoffice/external/lit';
+import { css, customElement, html, query, repeat, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { UmbAllowedMediaTypeModel } from '@umbraco-cms/backoffice/media-type';
+import type { UUIButtonElement } from '@umbraco-cms/backoffice/external/uui';
@customElement('umb-dropzone-media-type-picker-modal')
export class UmbDropzoneMediaTypePickerModalElement extends UmbModalBaseElement<
@@ -15,20 +16,30 @@ export class UmbDropzoneMediaTypePickerModalElement extends UmbModalBaseElement<
@state()
private _options: Array = [];
+ @query('#auto')
+ private _buttonAuto!: UUIButtonElement;
+
connectedCallback() {
super.connectedCallback();
this._options = this.data?.options ?? [];
+ requestAnimationFrame(() => this._buttonAuto.focus());
}
- #onMediaTypePick(unique?: string) {
+ #onMediaTypePick(unique: string | undefined) {
this.value = { mediaTypeUnique: unique };
this._submitModal();
}
render() {
- return html` this.#onMediaTypePick()} label="Automatically" compact>
-
+ return html`
+ this.#onMediaTypePick(undefined)}
+ label="Automatically"
+ compact>
+ Auto pick
+
${repeat(
this._options,
(option) => option.unique,
@@ -38,12 +49,30 @@ export class UmbDropzoneMediaTypePickerModalElement extends UmbModalBaseElement<
@click=${() => this.#onMediaTypePick(option.unique)}
label=${option.name}
compact>
-
+ ${option.name}
`,
- )}`;
+ )}
+
`;
}
- static styles = [UmbTextStyles, css``];
+ static styles = [
+ UmbTextStyles,
+ css`
+ #options {
+ display: flex;
+ margin: var(--uui-size-layout-1);
+ gap: var(--uui-size-3);
+ }
+ uui-button {
+ width: 150px;
+ height: 150px;
+ }
+ umb-icon {
+ font-size: var(--uui-size-10);
+ margin-bottom: var(--uui-size-2);
+ }
+ `,
+ ];
}
export default UmbDropzoneMediaTypePickerModalElement;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/dropzone-media-type-picker/dropzone-media-type-picker-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/dropzone-media-type-picker/dropzone-media-type-picker-modal.token.ts
index 4cd4a52974..e8437bf919 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/dropzone-media-type-picker/dropzone-media-type-picker-modal.token.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/dropzone-media-type-picker/dropzone-media-type-picker-modal.token.ts
@@ -3,6 +3,7 @@ import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
export interface UmbDropzoneMediaTypePickerModalData {
options: Array;
+ files?: Array;
}
export type UmbDropzoneMediaTypePickerModalValue = {
From 376d08481f9cd4a20474b116e22ef378d1838947 Mon Sep 17 00:00:00 2001
From: Lone Iversen <108085781+loivsen@users.noreply.github.com>
Date: Wed, 1 May 2024 16:34:29 +0200
Subject: [PATCH 014/134] parentunique
---
.../media/dropzone/dropzone-manager.class.ts | 34 ++++++-------------
1 file changed, 11 insertions(+), 23 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts
index f9caed9882..47254ee17e 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts
@@ -37,31 +37,20 @@ export class UmbDropzoneManager extends UmbControllerBase {
#mediaDetailRepository = new UmbMediaDetailRepository(this);
#mediaTypeDetailRepository = new UmbMediaTypeDetailRepository(this);
- #parentUnique: string | null;
-
- constructor(host: UmbControllerHost, parentUnique: string | null) {
+ constructor(host: UmbControllerHost) {
super(host);
this.#host = host;
- this.#parentUnique = parentUnique;
}
- public setParentUnique(parentUnique: string | null) {
- this.#parentUnique = parentUnique;
- }
- public getParentUnique() {
- return this.#parentUnique;
- }
-
- public async dropOneFile(file: File) {
+ public async dropOneFile(file: File, parentUnique: string | null) {
const extension = this.#getExtensionFromMimeType(file.type);
if (!extension) {
- // Folders have no extension on file drop. We assume it is a folder being uploaded.
- this.#handleFolder(file);
+ // TODO Folders have no extension on file drop. Assume it is a folder being uploaded.
return;
}
- const optionsArray = await this.#buildOptionsArrayFrom([extension]);
+ const optionsArray = await this.#buildOptionsArrayFrom([extension], parentUnique);
if (!optionsArray.length) throw new Error('File not allowed here.'); // Parent does not allow this file type here.
const mediaTypes = optionsArray[0].mediaTypes;
@@ -89,11 +78,12 @@ export class UmbDropzoneManager extends UmbControllerBase {
await this.#uploadOne(uploadableFile);
}
- public async dropFiles(files: Array) {
+ public async dropFiles(files: Array, parentUnique: string | null) {
// removes duplicate file types so we don't call endpoints unnecessarily when building options.
const mimeTypes = [...new Set(files.map((file) => file.type))];
const optionsArray = await this.#buildOptionsArrayFrom(
mimeTypes.map((mimetype) => this.#getExtensionFromMimeType(mimetype)),
+ parentUnique,
);
if (!optionsArray.length) return; // None of the files are allowed in current dropzone.
@@ -105,7 +95,6 @@ export class UmbDropzoneManager extends UmbControllerBase {
const extension = this.#getExtensionFromMimeType(file.type);
if (!extension) {
// Folders have no extension on file drop. We assume it is a folder being uploaded.
- this.#handleFolder(file);
return;
}
const options = optionsArray.find((option) => option.fileExtension === extension)?.mediaTypes;
@@ -125,9 +114,12 @@ export class UmbDropzoneManager extends UmbControllerBase {
return getExtensionFromMime(mimeType) || '';
}
- async #buildOptionsArrayFrom(fileExtensions: Array): Promise> {
+ async #buildOptionsArrayFrom(
+ fileExtensions: Array,
+ parentUnique: string | null,
+ ): Promise> {
// Getting all media types allowed in our current position based on parent unique.
- const { data: allAllowedMediaTypes } = await this.#mediaTypeStructure.requestAllowedChildrenOf(this.#parentUnique);
+ const { data: allAllowedMediaTypes } = await this.#mediaTypeStructure.requestAllowedChildrenOf(parentUnique);
if (!allAllowedMediaTypes?.items.length) return [];
const allowedByParent = allAllowedMediaTypes.items;
@@ -160,10 +152,6 @@ export class UmbDropzoneManager extends UmbControllerBase {
await this.#tempFileManager.uploadOne(file);
}
- async #handleFolder(file: File) {
- throw new Error('Not implemented: Folders coming soon!');
- }
-
private _reset() {
//
}
From 11af14852402481cdf31edbb5eab62b45475f6e7 Mon Sep 17 00:00:00 2001
From: Lone Iversen <108085781+loivsen@users.noreply.github.com>
Date: Thu, 2 May 2024 09:12:30 +0200
Subject: [PATCH 015/134] temporary file manager item
---
.../temporary-file-manager.class.ts | 47 +++++++++++--------
.../media/dropzone/dropzone-manager.class.ts | 20 ++++----
2 files changed, 37 insertions(+), 30 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/temporary-file-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/temporary-file-manager.class.ts
index 2f349c4bbf..a77778e47e 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/temporary-file-manager.class.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/temporary-file-manager.class.ts
@@ -4,7 +4,13 @@ import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
import { UmbId } from '@umbraco-cms/backoffice/id';
-export type TemporaryFileStatus = 'success' | 'waiting' | 'error';
+///export type TemporaryFileStatus = 'success' | 'waiting' | 'error';
+
+export enum TemporaryFileStatus {
+ SUCCESS = 'success',
+ WAITING = 'waiting',
+ ERROR = 'error',
+}
export interface UmbTemporaryFileModel {
file: File;
@@ -16,10 +22,12 @@ export interface UmbTemporaryFileQueueModel extends Partial extends UmbControllerBase {
#temporaryFileRepository;
- #queue = new UmbArrayState([], (item) => item.unique);
+ #queue = new UmbArrayState([], (item) => item.unique);
public readonly queue = this.#queue.asObservable();
constructor(host: UmbControllerHost) {
@@ -27,28 +35,27 @@ export class UmbTemporaryFileManager extends UmbControllerBase {
this.#temporaryFileRepository = new UmbTemporaryFileRepository(host);
}
- async uploadOne(queueItem: UmbTemporaryFileQueueModel): Promise> {
+ async uploadOne(uploadableItem: UploadableItem): Promise {
this.#queue.setValue([]);
- const item: UmbTemporaryFileModel = {
- file: queueItem.file,
- unique: queueItem.unique ?? UmbId.new(),
- status: queueItem.status ?? 'waiting',
+
+ const item: UploadableItem = {
+ unique: UmbId.new(),
+ status: TemporaryFileStatus.WAITING,
+ ...uploadableItem,
};
+
this.#queue.appendOne(item);
- return this.handleQueue();
+ return (await this.#handleQueue())[0];
}
- async upload(queueItems: Array): Promise> {
+ async upload(queueItems: Array): Promise> {
this.#queue.setValue([]);
+
const items = queueItems.map(
- (item): UmbTemporaryFileModel => ({
- file: item.file,
- unique: item.unique ?? UmbId.new(),
- status: item.status ?? 'waiting',
- }),
+ (item): UploadableItem => ({ unique: UmbId.new(), status: TemporaryFileStatus.WAITING, ...item }),
);
this.#queue.append(items);
- return this.handleQueue();
+ return this.#handleQueue();
}
removeOne(unique: string) {
@@ -59,8 +66,8 @@ export class UmbTemporaryFileManager extends UmbControllerBase {
this.#queue.remove(uniques);
}
- private async handleQueue() {
- const filesCompleted: Array = [];
+ async #handleQueue() {
+ const filesCompleted: Array = [];
const queue = this.#queue.getValue();
if (!queue.length) return filesCompleted;
@@ -73,10 +80,10 @@ export class UmbTemporaryFileManager extends UmbControllerBase {
let status: TemporaryFileStatus;
if (error) {
- status = 'error';
+ status = TemporaryFileStatus.ERROR;
this.#queue.updateOne(item.unique, { ...item, status });
} else {
- status = 'success';
+ status = TemporaryFileStatus.SUCCESS;
this.#queue.updateOne(item.unique, { ...item, status });
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts
index 47254ee17e..477cf60c9c 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts
@@ -8,7 +8,7 @@ import {
UmbMediaTypeDetailRepository,
UmbMediaTypeStructureRepository,
} from '@umbraco-cms/backoffice/media-type';
-import { UmbTemporaryFileManager } from '@umbraco-cms/backoffice/temporary-file';
+import { TemporaryFileStatus, UmbTemporaryFileManager } from '@umbraco-cms/backoffice/temporary-file';
import { UmbId } from '@umbraco-cms/backoffice/id';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
@@ -62,7 +62,7 @@ export class UmbDropzoneManager extends UmbControllerBase {
mediaTypeUnique: mediaTypes[0].unique,
};
- await this.#uploadOne(uploadableFile);
+ await this.#handleUpload([uploadableFile]);
return;
}
@@ -75,7 +75,7 @@ export class UmbDropzoneManager extends UmbControllerBase {
file,
mediaTypeUnique: mediaType.unique,
};
- await this.#uploadOne(uploadableFile);
+ await this.#handleUpload([uploadableFile]);
}
public async dropFiles(files: Array, parentUnique: string | null) {
@@ -107,7 +107,7 @@ export class UmbDropzoneManager extends UmbControllerBase {
uploadableFiles.push({ unique: UmbId.new(), file, mediaTypeUnique: mediaType.unique });
}
- await this.#upload(uploadableFiles);
+ await this.#handleUpload(uploadableFiles);
}
#getExtensionFromMimeType(mimeType: string): string {
@@ -144,12 +144,12 @@ export class UmbDropzoneManager extends UmbControllerBase {
return value ? { unique: value.mediaTypeUnique ?? options[0].unique } : null;
}
- async #upload(files: Array) {
- await this.#tempFileManager.upload(files);
- }
-
- async #uploadOne(file: UmbUploadableFileModel) {
- await this.#tempFileManager.uploadOne(file);
+ async #handleUpload(files: Array) {
+ const uploads = await this.#tempFileManager.upload(files);
+ for (const upload of uploads) {
+ if (upload.status !== TemporaryFileStatus.SUCCESS) return;
+ // TODO: Create item
+ }
}
private _reset() {
From a37e828bbbff09e64247bdbcefcddf9156888c1b Mon Sep 17 00:00:00 2001
From: Lone Iversen <108085781+loivsen@users.noreply.github.com>
Date: Thu, 2 May 2024 21:44:48 +0200
Subject: [PATCH 016/134] progressbar
---
.../temporary-file-manager.class.ts | 12 +-
.../media/dropzone/dropzone-manager.class.ts | 84 +++++--
.../media/media/dropzone/dropzone.element.ts | 219 ++----------------
3 files changed, 85 insertions(+), 230 deletions(-)
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/temporary-file-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/temporary-file-manager.class.ts
index a77778e47e..c56077d8db 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/temporary-file-manager.class.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/temporary-file-manager.class.ts
@@ -14,16 +14,12 @@ export enum TemporaryFileStatus {
export interface UmbTemporaryFileModel {
file: File;
- unique: string;
- status: TemporaryFileStatus;
-}
-
-export interface UmbTemporaryFileQueueModel extends Partial {
- file: File;
+ unique?: string;
+ status?: TemporaryFileStatus;
}
export class UmbTemporaryFileManager<
- UploadableItem extends UmbTemporaryFileQueueModel = UmbTemporaryFileQueueModel,
+ UploadableItem extends UmbTemporaryFileModel = UmbTemporaryFileModel,
> extends UmbControllerBase {
#temporaryFileRepository;
@@ -76,7 +72,7 @@ export class UmbTemporaryFileManager<
if (!item.unique) throw new Error(`Unique is missing for item ${item}`);
const { error } = await this.#temporaryFileRepository.upload(item.unique, item.file);
- await new Promise((resolve) => setTimeout(resolve, (Math.random() + 0.5) * 1000)); // simulate small delay so that the upload badge is properly shown
+ //await new Promise((resolve) => setTimeout(resolve, (Math.random() + 0.5) * 1000)); // simulate small delay so that the upload badge is properly shown
let status: TemporaryFileStatus;
if (error) {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts
index 477cf60c9c..1ddeaf2a4a 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts
@@ -1,24 +1,26 @@
+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 { mimeToExtension } from '@umbraco-cms/backoffice/external/mime-types';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
+import { type UmbAllowedMediaTypeModel, UmbMediaTypeStructureRepository } from '@umbraco-cms/backoffice/media-type';
import {
- type UmbAllowedMediaTypeModel,
- UmbMediaTypeDetailRepository,
- UmbMediaTypeStructureRepository,
-} from '@umbraco-cms/backoffice/media-type';
-import { TemporaryFileStatus, UmbTemporaryFileManager } from '@umbraco-cms/backoffice/temporary-file';
+ TemporaryFileStatus,
+ UmbTemporaryFileManager,
+ type UmbTemporaryFileModel,
+} from '@umbraco-cms/backoffice/temporary-file';
import { UmbId } from '@umbraco-cms/backoffice/id';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
+import { UmbNumberState } from '@umbraco-cms/backoffice/observable-api';
-interface UmbUploadableFileModel {
+export interface UmbUploadableFileModel extends UmbTemporaryFileModel {
unique: string;
file: File;
mediaTypeUnique: string;
}
-export interface UmbUploadableFileExtensionModel {
+export interface UmbUploadableExtensionModel {
fileExtension: string;
mediaTypes: Array;
}
@@ -31,18 +33,35 @@ export function getExtensionFromMime(mimeType: string): string | undefined {
export class UmbDropzoneManager extends UmbControllerBase {
#host;
+
#tempFileManager = new UmbTemporaryFileManager(this);
#mediaTypeStructure = new UmbMediaTypeStructureRepository(this);
#mediaDetailRepository = new UmbMediaDetailRepository(this);
- #mediaTypeDetailRepository = new UmbMediaTypeDetailRepository(this);
+
+ #progress = new UmbNumberState(undefined);
+ public readonly progress = this.#progress.asObservable();
constructor(host: UmbControllerHost) {
super(host);
this.#host = host;
+
+ this.observe(
+ this.#tempFileManager.queue,
+ (queue) => {
+ // TODO Reconsider how the progress bar should be handled. Here we are just showing the progress of the queue, rather than the progress of the files getting created as a media item.
+ // Maybe we want to create the media item right away after the corresponding file is uploaded as temp file and set the progress, then continue on to the next file...
+ if (!queue.length) return;
+ const waiting = queue.filter((item) => item.status === TemporaryFileStatus.WAITING);
+ const progress = waiting.length ? waiting.length / queue.length : 0;
+ this.#progress.setValue(progress);
+ },
+ '_observeQueue',
+ );
}
public async dropOneFile(file: File, parentUnique: string | null) {
+ this.#progress.setValue(0);
const extension = this.#getExtensionFromMimeType(file.type);
if (!extension) {
@@ -62,7 +81,7 @@ export class UmbDropzoneManager extends UmbControllerBase {
mediaTypeUnique: mediaTypes[0].unique,
};
- await this.#handleUpload([uploadableFile]);
+ await this.#handleUpload([uploadableFile], parentUnique);
return;
}
@@ -75,10 +94,11 @@ export class UmbDropzoneManager extends UmbControllerBase {
file,
mediaTypeUnique: mediaType.unique,
};
- await this.#handleUpload([uploadableFile]);
+ await this.#handleUpload([uploadableFile], parentUnique);
}
public async dropFiles(files: Array, parentUnique: string | null) {
+ this.#progress.setValue(0);
// removes duplicate file types so we don't call endpoints unnecessarily when building options.
const mimeTypes = [...new Set(files.map((file) => file.type))];
const optionsArray = await this.#buildOptionsArrayFrom(
@@ -107,7 +127,7 @@ export class UmbDropzoneManager extends UmbControllerBase {
uploadableFiles.push({ unique: UmbId.new(), file, mediaTypeUnique: mediaType.unique });
}
- await this.#handleUpload(uploadableFiles);
+ await this.#handleUpload(uploadableFiles, parentUnique);
}
#getExtensionFromMimeType(mimeType: string): string {
@@ -117,7 +137,7 @@ export class UmbDropzoneManager extends UmbControllerBase {
async #buildOptionsArrayFrom(
fileExtensions: Array,
parentUnique: string | null,
- ): Promise> {
+ ): Promise> {
// Getting all media types allowed in our current position based on parent unique.
const { data: allAllowedMediaTypes } = await this.#mediaTypeStructure.requestAllowedChildrenOf(parentUnique);
if (!allAllowedMediaTypes?.items.length) return [];
@@ -125,7 +145,7 @@ export class UmbDropzoneManager extends UmbControllerBase {
const allowedByParent = allAllowedMediaTypes.items;
// Building an array of options the files can be uploaded as.
- const options: Array = [];
+ const options: Array = [];
for (const fileExtension of fileExtensions) {
const extensionOptions = await this.#mediaTypeStructure.requestMediaTypesOf({ fileExtension });
@@ -144,11 +164,39 @@ export class UmbDropzoneManager extends UmbControllerBase {
return value ? { unique: value.mediaTypeUnique ?? options[0].unique } : null;
}
- async #handleUpload(files: Array) {
- const uploads = await this.#tempFileManager.upload(files);
+ async #handleUpload(files: Array, parentUnique: string | null) {
+ const uploads = (await this.#tempFileManager.upload(files)) as Array;
for (const upload of uploads) {
- if (upload.status !== TemporaryFileStatus.SUCCESS) return;
- // TODO: Create item
+ if (upload.status !== TemporaryFileStatus.SUCCESS) return; // Upload failed. In what way do we let the user know?
+ const preset: Partial = {
+ mediaType: {
+ unique: upload.mediaTypeUnique,
+ collection: null,
+ },
+ variants: [
+ {
+ culture: null,
+ segment: null,
+ name: upload.file.name,
+ createDate: null,
+ updateDate: null,
+ },
+ ],
+ values: [
+ {
+ alias: 'umbracoFile',
+ //value: { temporaryFileId: upload.unique },
+ value: { src: upload.unique },
+ culture: null,
+ segment: null,
+ },
+ ],
+ };
+
+ const { data } = await this.#mediaDetailRepository.createScaffold(preset);
+ if (!data) return;
+
+ await this.#mediaDetailRepository.create(data, parentUnique);
}
}
@@ -157,6 +205,8 @@ export class UmbDropzoneManager extends UmbControllerBase {
}
public destroy() {
+ this.#tempFileManager.destroy();
+ this.#progress.destroy();
super.destroy();
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone.element.ts
index 96f6e638a0..7a971e3457 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone.element.ts
@@ -1,73 +1,19 @@
-import { UmbMediaDetailRepository } from '../repository/index.js';
-import type { UmbMediaDetailModel } from '../types.js';
import { UmbDropzoneManager } from './dropzone-manager.class.js';
-import { css, html, customElement, state, property } from '@umbraco-cms/backoffice/external/lit';
+import { UmbChangeEvent, 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 { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
-import {
- type UmbAllowedMediaTypeModel,
- UmbMediaTypeStructureRepository,
- UmbMediaTypeDetailRepository,
-} from '@umbraco-cms/backoffice/media-type';
-import {
- UmbTemporaryFileManager,
- type UmbTemporaryFileQueueModel,
- type UmbTemporaryFileModel,
-} from '@umbraco-cms/backoffice/temporary-file';
-import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
-
-interface MediaTypeOptions {
- fileExtension: string;
- mediaTypes: Array;
-}
-
-interface UploadableFile {
- file: File;
- mediaType: UmbAllowedMediaTypeModel;
- regularUploadField?: boolean;
- temporaryUnique?: string;
-}
@customElement('umb-dropzone')
export class UmbDropzoneElement extends UmbLitElement {
- #fileManager = new UmbTemporaryFileManager(this);
- #mediaTypeStructure = new UmbMediaTypeStructureRepository(this);
- #mediaDetailRepository = new UmbMediaDetailRepository(this);
- #mediaTypeDetailRepository = new UmbMediaTypeDetailRepository(this);
-
- #parentUnique: string | null = null;
- #dropzoneManager = new UmbDropzoneManager(this, null);
-
- #allowedMediaTypes: Array = [];
-
- @state()
- private queue: Array = [];
-
@property({ attribute: false })
- public set parentUnique(value: string | null) {
- this.#parentUnique = value;
- //this.#dropzoneManager.setParentUnique(value);
- }
- public get parentUnique(): string | null {
- return this.#parentUnique;
- }
+ parentUnique: string | null = null;
public browse() {
const element = this.shadowRoot?.querySelector('#dropzone') as UUIFileDropzoneElement;
return element.browse();
}
- async #getAllowedMediaTypes(): Promise {
- const { data } = await this.#mediaTypeStructure.requestAllowedChildrenOf(this.parentUnique);
- return data?.items ?? [];
- }
-
- async #getAllowedMediaTypesOf(fileExtension: string): Promise {
- const options = await this.#mediaTypeStructure.requestMediaTypesOf({ fileExtension });
- const mediaTypes = options.filter((option) => this.#allowedMediaTypes.includes(option));
- return { fileExtension, mediaTypes };
- }
-
constructor() {
super();
document.addEventListener('dragenter', this.#handleDragEnter.bind(this));
@@ -101,159 +47,22 @@ export class UmbDropzoneElement extends UmbLitElement {
const files: Array = event.detail.files;
if (!files.length) return;
- if (files.length === 1) {
- this.#dropzoneManager.dropOneFile(files[0]);
- } else {
- this.#dropzoneManager.dropFiles(files);
- }
- }
- /*
- async #handleOneFile(file: File) {
- const extension = getExtensionFromMime(file.type);
- if (!extension) return; // Extension doesn't exist.
-
- const options = await this.#getAllowedMediaTypesOf(extension);
- if (!options.mediaTypes.length) return; // File type not allowed in current dropzone.
-
- if (options.mediaTypes.length === 1) {
- this.#uploadFiles([{ file, mediaType: options.mediaTypes[0] }]);
- return;
- }
-
- // Multiple options, show a dialog to the user to pick one.
- //TODO: Implement dialog.
- }
-
- async #handleMultipleFiles(files: Array) {
- // removes duplicate file types so we don't call the endpoint unnecessarily for every file.
- const types = [...new Set(files.map((file) => file.type))];
- const options: Array