From d5a021d7d47e390e05738573b36cb460f350e231 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 28 Jan 2025 11:06:49 +0100 Subject: [PATCH 01/25] feat: shows notification when no suitable media type is found --- .../media/media/dropzone/dropzone-manager.class.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 839d4c39d35a..6d48da923f9a 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 @@ -19,6 +19,7 @@ import { UmbMediaTypeStructureRepository } from '@umbraco-cms/backoffice/media-t import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import type { UmbAllowedMediaTypeModel } from '@umbraco-cms/backoffice/media-type'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'; /** * Manages the dropzone and uploads folders and files to the server. @@ -48,9 +49,15 @@ export class UmbDropzoneManager extends UmbControllerBase { readonly #progressItems = new UmbArrayState([], (x) => x.unique); public readonly progressItems = this.#progressItems.asObservable(); + #notificationContext?: typeof UMB_NOTIFICATION_CONTEXT.TYPE; + constructor(host: UmbControllerHost) { super(host); this.#host = host; + + this.consumeContext(UMB_NOTIFICATION_CONTEXT, (context) => { + this.#notificationContext = context; + }); } public setIsFoldersAllowed(isAllowed: boolean) { @@ -134,6 +141,11 @@ export class UmbDropzoneManager extends UmbControllerBase { async #createOneMediaItem(item: UmbUploadableItem) { const options = await this.#getMediaTypeOptions(item); if (!options.length) { + this.#notificationContext?.peek('warning', { + data: { + message: `No media types are allowed for ${item.temporaryFile?.file.name}.`, + }, + }); return this.#updateProgress(item, UmbFileDropzoneItemStatus.NOT_ALLOWED); } From 455992a9d1020ad70449f91ad08a558e078a6e70 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 28 Jan 2025 11:07:41 +0100 Subject: [PATCH 02/25] chore: rearrange imports --- .../src/packages/media/media/dropzone/dropzone.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 adfcf191f475..f90199879d40 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,9 +1,9 @@ import { UmbDropzoneManager } from './dropzone-manager.class.js'; +import { UmbDropzoneSubmittedEvent } from './dropzone-submitted.event.js'; import { UmbFileDropzoneItemStatus, type UmbUploadableItem } from './types.js'; import { css, customElement, html, ifDefined, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UUIFileDropzoneElement, UUIFileDropzoneEvent } from '@umbraco-cms/backoffice/external/uui'; -import { UmbDropzoneSubmittedEvent } from './dropzone-submitted.event.js'; @customElement('umb-dropzone') export class UmbDropzoneElement extends UmbLitElement { From 4f6b4b73e5d884abef89bfcc693cd8881890d462 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 28 Jan 2025 11:07:52 +0100 Subject: [PATCH 03/25] feat: use a forward ref to find the dropzone --- .../media/collection/media-collection.element.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) 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 31a89e174465..d8f1b6945e0e 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 @@ -3,7 +3,7 @@ import { UMB_MEDIA_WORKSPACE_CONTEXT } from '../workspace/media-workspace.contex import type { UmbDropzoneSubmittedEvent } from '../dropzone/dropzone-submitted.event.js'; import type { UmbDropzoneElement } from '../dropzone/dropzone.element.js'; import { UMB_MEDIA_COLLECTION_CONTEXT } from './media-collection.context-token.js'; -import { customElement, html, query, state, when } from '@umbraco-cms/backoffice/external/lit'; +import { customElement, html, ref, state, when, type Ref } from '@umbraco-cms/backoffice/external/lit'; import { UmbCollectionDefaultElement } from '@umbraco-cms/backoffice/collection'; import { UmbRequestReloadChildrenOfEntityEvent } from '@umbraco-cms/backoffice/entity-action'; import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; @@ -18,9 +18,6 @@ export class UmbMediaCollectionElement extends UmbCollectionDefaultElement { @state() private _unique: string | null = null; - @query('#dropzone') - private _dropzone!: UmbDropzoneElement; - constructor() { super(); @@ -35,9 +32,10 @@ export class UmbMediaCollectionElement extends UmbCollectionDefaultElement { }); } - #observeProgressItems() { + #observeProgressItems(dropzone?: Element) { + if (!dropzone) return; this.observe( - this._dropzone.progressItems(), + (dropzone as UmbDropzoneElement).progressItems(), (progressItems) => { progressItems.forEach((item) => { // We do not update folders as it may have children still being uploaded. @@ -57,7 +55,6 @@ export class UmbMediaCollectionElement extends UmbCollectionDefaultElement { .map((p) => ({ unique: p.unique, status: p.status, name: p.temporaryFile?.file.name ?? p.folder?.name })); this.#collectionContext?.setPlaceholders(placeholders); - this.#observeProgressItems(); } async #onComplete(event: Event) { @@ -89,6 +86,7 @@ export class UmbMediaCollectionElement extends UmbCollectionDefaultElement { ${when(this._progress >= 0, () => html``)} Date: Tue, 28 Jan 2025 11:08:09 +0100 Subject: [PATCH 04/25] chore: rearrange imports --- .../packages/media/media/collection/media-collection.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d8f1b6945e0e..cd0081de34ff 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 @@ -3,7 +3,7 @@ import { UMB_MEDIA_WORKSPACE_CONTEXT } from '../workspace/media-workspace.contex import type { UmbDropzoneSubmittedEvent } from '../dropzone/dropzone-submitted.event.js'; import type { UmbDropzoneElement } from '../dropzone/dropzone.element.js'; import { UMB_MEDIA_COLLECTION_CONTEXT } from './media-collection.context-token.js'; -import { customElement, html, ref, state, when, type Ref } from '@umbraco-cms/backoffice/external/lit'; +import { customElement, html, ref, state, when } from '@umbraco-cms/backoffice/external/lit'; import { UmbCollectionDefaultElement } from '@umbraco-cms/backoffice/collection'; import { UmbRequestReloadChildrenOfEntityEvent } from '@umbraco-cms/backoffice/entity-action'; import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; From 11976a03c63b10f5f66fc45a2aa17a07135de814 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 28 Jan 2025 11:20:53 +0100 Subject: [PATCH 05/25] chore(mock): send back correct header --- .../handlers/temporary-file/temporary-file.handlers.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/temporary-file/temporary-file.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/temporary-file/temporary-file.handlers.ts index 07f3e8bcef0d..0cd260c77da5 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/temporary-file/temporary-file.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/temporary-file/temporary-file.handlers.ts @@ -7,6 +7,12 @@ const UMB_SLUG = 'temporary-file'; export const handlers = [ rest.post(umbracoPath(`/${UMB_SLUG}`), async (_req, res, ctx) => { - return res(ctx.delay(), ctx.status(201), ctx.text(UmbId.new())); + const guid = UmbId.new(); + return res( + ctx.delay(), + ctx.status(201), + ctx.set('Umb-Generated-Resource', guid), + ctx.text(guid), + ); }), ]; From 37b3c9474d85a1b56f2c83e8399651e8dc7b7c21 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 28 Jan 2025 11:21:32 +0100 Subject: [PATCH 06/25] feat: avoid using the context consumer to get a token, but instead mimick the OpenAPI generator --- .../core/resources/tryXhrRequest.function.ts | 15 ++++----------- .../temporary-file.server.data-source.ts | 2 +- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/tryXhrRequest.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/tryXhrRequest.function.ts index 33b524e6966d..749fafc7296e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/resources/tryXhrRequest.function.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/tryXhrRequest.function.ts @@ -1,23 +1,16 @@ -import { UMB_AUTH_CONTEXT } from '../auth/auth.context.token.js'; import type { XhrRequestOptions } from './types.js'; import { UmbResourceController } from './resource.controller.js'; -import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { OpenAPI, type CancelablePromise } from '@umbraco-cms/backoffice/external/backend-api'; /** * Make an XHR request. - * @param host The controller host for this controller to be appended to. - * @param options The options for the XHR request. + * @param {XhrRequestOptions} options The options for the XHR request. + * @returns {CancelablePromise} A promise that can be cancelled. */ -export function tryXhrRequest(host: UmbControllerHost, options: XhrRequestOptions): CancelablePromise { +export function tryXhrRequest(options: XhrRequestOptions): CancelablePromise { return UmbResourceController.xhrRequest({ ...options, baseUrl: OpenAPI.BASE, - async token() { - const contextConsumer = new UmbContextConsumerController(host, UMB_AUTH_CONTEXT).asPromise(); - const authContext = await contextConsumer; - return authContext.getLatestToken(); - }, + token: OpenAPI.TOKEN as never, }); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/temporary-file.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/temporary-file.server.data-source.ts index 29f33653da75..644eaa7dcdab 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/temporary-file.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/temporary-file.server.data-source.ts @@ -35,7 +35,7 @@ export class UmbTemporaryFileServerDataSource { const body = new FormData(); body.append('Id', id); body.append('File', file); - const xhrRequest = tryXhrRequest(this.#host, { + const xhrRequest = tryXhrRequest({ url: '/umbraco/management/api/v1/temporary-file', method: 'POST', responseHeader: 'Umb-Generated-Resource', From badf18f514c2c3536ad5fdc67481e45e7f9c4d1f Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 28 Jan 2025 11:21:48 +0100 Subject: [PATCH 07/25] chore(mock): allow more file types --- .../src/mocks/data/data-type/data-type.data.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts index 269e56f6271f..54263d68b1ec 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts @@ -722,7 +722,7 @@ export const data: Array = [ values: [ { alias: 'fileExtensions', - value: ['jpg', 'jpeg', 'png', 'pdf'], + value: ['jpg', 'jpeg', 'png', 'pdf', 'mov', 'iso'], }, { alias: 'multiple', From a79aa2c5fd8feb9ef70cda018c8d609a1e3b6efe Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 28 Jan 2025 11:38:27 +0100 Subject: [PATCH 08/25] chore(mock): create more upload fields --- .../mocks/data/data-type/data-type.data.ts | 65 ++++++++++++++++++- .../mocks/data/media-type/media-type.data.ts | 16 ++--- 2 files changed, 72 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts index 54263d68b1ec..119c0cc6e2e5 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts @@ -722,7 +722,70 @@ export const data: Array = [ values: [ { alias: 'fileExtensions', - value: ['jpg', 'jpeg', 'png', 'pdf', 'mov', 'iso'], + value: ['jpg', 'jpeg', 'png', 'svg'], + }, + { + alias: 'multiple', + value: true, + }, + ], + }, + { + name: 'Upload Field (Files)', + id: 'dt-uploadFieldFiles', + parent: null, + editorAlias: 'Umbraco.UploadField', + editorUiAlias: 'Umb.PropertyEditorUi.UploadField', + hasChildren: false, + isFolder: false, + isDeletable: true, + canIgnoreStartNodes: false, + values: [ + { + alias: 'fileExtensions', + value: ['pdf', 'iso'], + }, + { + alias: 'multiple', + value: true, + }, + ], + }, + { + name: 'Upload Field (Movies)', + id: 'dt-uploadFieldMovies', + parent: null, + editorAlias: 'Umbraco.UploadField', + editorUiAlias: 'Umb.PropertyEditorUi.UploadField', + hasChildren: false, + isFolder: false, + isDeletable: true, + canIgnoreStartNodes: false, + values: [ + { + alias: 'fileExtensions', + value: ['mp4', 'mov'], + }, + { + alias: 'multiple', + value: true, + }, + ], + }, + { + name: 'Upload Field (Vector)', + id: 'dt-uploadFieldVector', + parent: null, + editorAlias: 'Umbraco.UploadField', + editorUiAlias: 'Umb.PropertyEditorUi.UploadField', + hasChildren: false, + isFolder: false, + isDeletable: true, + canIgnoreStartNodes: false, + values: [ + { + alias: 'fileExtensions', + value: ['svg'], }, { alias: 'multiple', diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.data.ts index 34688601f76f..1614c94fc1ca 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.data.ts @@ -15,7 +15,7 @@ export type UmbMockMediaTypeUnionModel = export const data: Array = [ { - name: 'Media Type 1', + name: 'Image', id: 'media-type-1-id', parent: null, description: 'Media type 1 description', @@ -105,7 +105,7 @@ export const data: Array = [ aliasCanBeChanged: false, }, { - name: 'Media Type 2', + name: 'Audio', id: 'media-type-2-id', parent: null, description: 'Media type 2 description', @@ -118,7 +118,7 @@ export const data: Array = [ alias: 'umbracoFile', name: 'File', description: '', - dataType: { id: 'dt-uploadField' }, + dataType: { id: 'dt-uploadFieldFiles' }, variesByCulture: false, variesBySegment: false, sortOrder: 0, @@ -155,7 +155,7 @@ export const data: Array = [ aliasCanBeChanged: false, }, { - name: 'Media Type 3', + name: 'Vector Graphics', id: 'media-type-3-id', parent: null, description: 'Media type 3 description', @@ -168,7 +168,7 @@ export const data: Array = [ alias: 'umbracoFile', name: 'File', description: '', - dataType: { id: 'dt-uploadField' }, + dataType: { id: 'dt-uploadFieldVector' }, variesByCulture: false, variesBySegment: false, sortOrder: 0, @@ -205,7 +205,7 @@ export const data: Array = [ aliasCanBeChanged: false, }, { - name: 'Media Type 4', + name: 'Movie', id: 'media-type-4-id', parent: null, description: 'Media type 4 description', @@ -218,7 +218,7 @@ export const data: Array = [ alias: 'umbracoFile', name: 'File', description: '', - dataType: { id: 'dt-uploadField' }, + dataType: { id: 'dt-uploadFieldMovies' }, variesByCulture: false, variesBySegment: false, sortOrder: 0, @@ -268,7 +268,7 @@ export const data: Array = [ alias: 'umbracoFile', name: 'File', description: '', - dataType: { id: 'dt-uploadField' }, + dataType: { id: 'dt-uploadFieldFiles' }, variesByCulture: false, variesBySegment: false, sortOrder: 0, From 61abee47495095a441da3464f39a4744696e6c01 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 28 Jan 2025 11:38:39 +0100 Subject: [PATCH 09/25] chore(mock): also look for mediaPicker fields --- .../src/mocks/data/media-type/media-type.db.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.db.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.db.ts index 951f9cf26093..6f271a453df0 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.db.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.db.ts @@ -52,7 +52,7 @@ class UmbMediaTypeMockDB extends UmbEntityMockDbBase { const allowedTypes = this.data.filter((field) => { const allProperties = field.properties.flat(); - const fileUploadType = allProperties.find((prop) => prop.alias === 'umbracoFile'); + const fileUploadType = allProperties.find((prop) => prop.alias === 'umbracoFile' || prop.alias === 'mediaPicker'); if (!fileUploadType) return false; const dataType = umbDataTypeMockDb.read(fileUploadType.dataType.id); From 4990a8446550b6129edc7bd0af67810d3beceb52 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 28 Jan 2025 12:02:00 +0100 Subject: [PATCH 10/25] chore(mock): improve media mock db --- .../src/mocks/data/media/media.data.ts | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/media/media.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/media/media.data.ts index 01d0cdb071e6..86eb0740dbf8 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/media/media.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/media/media.data.ts @@ -16,19 +16,23 @@ export const data: Array = [ isTrashed: false, mediaType: { id: 'media-type-1-id', - icon: 'icon-bug', + icon: 'icon-picture', }, values: [ + { + editorAlias: 'Umbraco.UploadField', + alias: 'dt-uploadField', + }, { editorAlias: 'Umbraco.TextBox', - alias: 'myMediaHeadline', + alias: 'mediaType1Property1', value: 'The daily life at Umbraco HQ', }, ], variants: [ { publishDate: '2023-02-06T15:31:51.354764', - culture: 'en-us', + culture: null, segment: null, name: 'Flipped Car', createDate: '2023-02-06T15:31:46.876902', @@ -51,14 +55,14 @@ export const data: Array = [ values: [ { editorAlias: 'Umbraco.TextBox', - alias: 'myMediaDescription', + alias: 'mediaType1Property1', value: 'Every day, a rabbit in a military costume greets me at the front door', }, ], variants: [ { publishDate: '2023-02-06T15:31:51.354764', - culture: 'en-us', + culture: null, segment: null, name: 'Umbracoffee', createDate: '2023-02-06T15:31:46.876902', @@ -83,7 +87,7 @@ export const data: Array = [ variants: [ { publishDate: '2023-02-06T15:31:51.354764', - culture: 'en-us', + culture: null, segment: null, name: 'People', createDate: '2023-02-06T15:31:46.876902', @@ -108,7 +112,7 @@ export const data: Array = [ variants: [ { publishDate: '2023-02-06T15:31:51.354764', - culture: 'en-us', + culture: null, segment: null, name: 'John Smith', createDate: '2023-02-06T15:31:46.876902', @@ -131,14 +135,14 @@ export const data: Array = [ values: [ { editorAlias: 'Umbraco.TextBox', - alias: 'myMediaDescription', + alias: 'mediaType1Property1', value: 'Every day, a rabbit in a military costume greets me at the front door', }, ], variants: [ { publishDate: '2023-02-06T15:31:51.354764', - culture: 'en-us', + culture: null, segment: null, name: 'Jane Doe', createDate: '2023-02-06T15:31:46.876902', @@ -161,14 +165,14 @@ export const data: Array = [ values: [ { editorAlias: 'Umbraco.TextBox', - alias: 'myMediaDescription', + alias: 'mediaType1Property1', value: 'Every day, a rabbit in a military costume greets me at the front door', }, ], variants: [ { publishDate: '2023-02-06T15:31:51.354764', - culture: 'en-us', + culture: null, segment: null, name: 'John Doe', createDate: '2023-02-06T15:31:46.876902', @@ -191,14 +195,14 @@ export const data: Array = [ values: [ { editorAlias: 'Umbraco.TextBox', - alias: 'myMediaDescription', + alias: 'mediaType1Property1', value: 'Every day, a rabbit in a military costume greets me at the front door', }, ], variants: [ { publishDate: '2023-02-06T15:31:51.354764', - culture: 'en-us', + culture: null, segment: null, name: 'A very nice hat', createDate: '2023-02-06T15:31:46.876902', @@ -221,14 +225,14 @@ export const data: Array = [ values: [ { editorAlias: 'Umbraco.TextBox', - alias: 'myMediaDescription', + alias: 'mediaType1Property1', value: 'Every day, a rabbit in a military costume greets me at the front door', }, ], variants: [ { publishDate: '2023-02-06T15:31:51.354764', - culture: 'en-us', + culture: null, segment: null, name: 'Fancy old chair', createDate: '2023-02-06T15:31:46.876902', From 9f821e5ec6da6457f0480fbaff9e6229dcf1bd22 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 28 Jan 2025 12:02:13 +0100 Subject: [PATCH 11/25] chore(mock): add missing endpoints --- .../mocks/handlers/media/detail.handlers.ts | 18 ++++++++++++++++++ .../src/mocks/handlers/media/tree.handlers.ts | 7 +++++++ 2 files changed, 25 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/media/detail.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/media/detail.handlers.ts index 96cb5a1ffcfd..fe52ac9a25fe 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/media/detail.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/media/detail.handlers.ts @@ -8,6 +8,7 @@ import type { UpdateMediaRequestModel, } from '@umbraco-cms/backoffice/external/backend-api'; import { umbracoPath } from '@umbraco-cms/backoffice/utils'; +import type { UmbMediaDetailModel } from '@umbraco-cms/backoffice/media'; export const detailHandlers = [ rest.post(umbracoPath(`${UMB_SLUG}`), async (req, res, ctx) => { @@ -44,6 +45,23 @@ export const detailHandlers = [ return res(ctx.status(200), ctx.json(response)); }), + rest.put(umbracoPath(`${UMB_SLUG}/:id/validate`), async (req, res, ctx) => { + const id = req.params.id as string; + if (!id) return res(ctx.status(400)); + const model = await req.json(); + if (!model) return res(ctx.status(400)); + + const hasMediaPickerOrFileUploadValue = model.values.some((v) => { + return v.editorAlias === 'Umbraco.UploadField' && v.value; + }); + + if (!hasMediaPickerOrFileUploadValue) { + return res(ctx.status(400, 'No media picker or file upload value found')); + } + + return res(ctx.status(200)); + }), + rest.put(umbracoPath(`${UMB_SLUG}/:id`), async (req, res, ctx) => { const id = req.params.id as string; if (!id) return res(ctx.status(400)); diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/media/tree.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/media/tree.handlers.ts index 308c04be5274..53e112cba419 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/media/tree.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/media/tree.handlers.ts @@ -19,4 +19,11 @@ export const treeHandlers = [ const response = umbMediaMockDb.tree.getChildrenOf({ parentId, skip, take }); return res(ctx.status(200), ctx.json(response)); }), + + rest.get(umbracoPath(`/tree${UMB_SLUG}/ancestors`), (req, res, ctx) => { + const descendantId = req.url.searchParams.get('descendantId'); + if (!descendantId) return; + const response = umbMediaMockDb.tree.getAncestorsOf({ descendantId }); + return res(ctx.status(200), ctx.json(response)); + }), ]; From 38720c088b7b6a55e1c65cf7e7ee46130d3d78ec Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 28 Jan 2025 13:02:15 +0100 Subject: [PATCH 12/25] chore(mock): update media data --- src/Umbraco.Web.UI.Client/src/mocks/data/media/media.data.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/media/media.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/media/media.data.ts index 86eb0740dbf8..dc4e3507dd1c 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/media/media.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/media/media.data.ts @@ -21,7 +21,10 @@ export const data: Array = [ values: [ { editorAlias: 'Umbraco.UploadField', - alias: 'dt-uploadField', + alias: 'mediaPicker', + value: { + src: '/umbraco/backoffice/assets/installer-illustration.svg', + }, }, { editorAlias: 'Umbraco.TextBox', From 9addb3e7fb8b2a6ae76ef2f00c525943b3825130 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 28 Jan 2025 14:30:57 +0100 Subject: [PATCH 13/25] chore(mock): fix aliases for media grid and table --- .../src/mocks/data/data-type/data-type.data.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts index 119c0cc6e2e5..0e8b63935a5e 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts @@ -975,8 +975,16 @@ export const data: Array = [ { alias: 'layouts', value: [ - { icon: 'icon-grid', isSystem: true, name: 'Grid', path: '', selected: true }, - { icon: 'icon-list', isSystem: true, name: 'Table', path: '', selected: true }, + { + icon: 'icon-grid', + name: 'Media Grid Collection View', + collectionView: 'Umb.CollectionView.Media.Grid', + }, + { + icon: 'icon-list', + name: 'Media Table Collection View', + collectionView: 'Umb.CollectionView.Media.Table', + }, ], }, { alias: 'icon', value: 'icon-layers' }, From 7cf4d59b48fd93213cbf8497c3d0ad98d165fa2a Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 28 Jan 2025 14:48:20 +0100 Subject: [PATCH 14/25] chore(mock): add urls to media --- .../src/mocks/data/media/media.data.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/media/media.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/media/media.data.ts index dc4e3507dd1c..f3a1ad9f9bd5 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/media/media.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/media/media.data.ts @@ -42,7 +42,12 @@ export const data: Array = [ updateDate: '2023-02-06T15:31:51.354764', }, ], - urls: [], + urls: [ + { + culture: null, + url: '/umbraco/backoffice/assets/installer-illustration.svg', + }, + ], }, { hasChildren: false, From dda7b798d7caf1391b7411a82a086621b068561f Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 28 Jan 2025 14:48:33 +0100 Subject: [PATCH 15/25] chore(mock): adds missing endpoint for imaging --- .../mocks/handlers/media/imaging.handlers.ts | 24 +++++++++++++++++++ .../src/mocks/handlers/media/index.ts | 2 ++ 2 files changed, 26 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/src/mocks/handlers/media/imaging.handlers.ts diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/media/imaging.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/media/imaging.handlers.ts new file mode 100644 index 000000000000..79ed72a98e22 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/media/imaging.handlers.ts @@ -0,0 +1,24 @@ +const { rest } = window.MockServiceWorker; +import { umbMediaMockDb } from '../../data/media/media.db.js'; +import type { GetImagingResizeUrlsResponse } from '@umbraco-cms/backoffice/external/backend-api'; +import { umbracoPath } from '@umbraco-cms/backoffice/utils'; + +export const imagingHandlers = [ + rest.get(umbracoPath('/imaging/resize/urls'), (req, res, ctx) => { + const ids = req.url.searchParams.getAll('id'); + if (!ids) return res(ctx.status(404)); + + const media = umbMediaMockDb.getAll().filter((item) => ids.includes(item.id)); + + const response: GetImagingResizeUrlsResponse = media.map((item) => ({ + id: item.id, + urlInfos: item.urls, + })); + + return res( + // Respond with a 200 status code + ctx.status(200), + ctx.json(response), + ); + }), +]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/media/index.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/media/index.ts index e8034da84d40..3c5545f5f71b 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/media/index.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/media/index.ts @@ -3,6 +3,7 @@ import { treeHandlers } from './tree.handlers.js'; import { itemHandlers } from './item.handlers.js'; import { detailHandlers } from './detail.handlers.js'; import { collectionHandlers } from './collection.handlers.js'; +import { imagingHandlers } from './imaging.handlers.js'; export const handlers = [ ...recycleBinHandlers, @@ -10,4 +11,5 @@ export const handlers = [ ...itemHandlers, ...detailHandlers, ...collectionHandlers, + ...imagingHandlers, ]; From a12ec271cd236ed580dd5d73a49a3273d29ac701 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 28 Jan 2025 16:15:36 +0100 Subject: [PATCH 16/25] fix: reverse order of properties to overwrite existing status --- .../core/temporary-file/temporary-file-manager.class.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 92c10f37fab7..0651bd58c4ad 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 @@ -34,7 +34,7 @@ export class UmbTemporaryFileManager< ): Promise> { this.#queue.setValue([]); - const items = queueItems.map((item): UploadableItem => ({ status: TemporaryFileStatus.WAITING, ...item })); + const items = queueItems.map((item): UploadableItem => ({ ...item, status: TemporaryFileStatus.WAITING })); this.#queue.append(items); return this.#handleQueue({ ...options }); } From ddbe41586650daaed68d1e7fe9ba5db91dc0e0c2 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 28 Jan 2025 16:16:25 +0100 Subject: [PATCH 17/25] feat: listen to progress updates on upload and update the `progress` property --- .../temporary-file-manager.class.ts | 5 +++- .../src/packages/core/temporary-file/types.ts | 1 + .../media/dropzone/dropzone-manager.class.ts | 25 ++++++++++++------- .../packages/media/media/dropzone/types.ts | 1 + 4 files changed, 22 insertions(+), 10 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 0651bd58c4ad..eecb4e62b2d3 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 @@ -74,7 +74,10 @@ export class UmbTemporaryFileManager< async #handleUpload(item: UploadableItem) { if (!item.temporaryUnique) throw new Error(`Unique is missing for item ${item}`); - const { error } = await this.#temporaryFileRepository.upload(item.temporaryUnique, item.file); + const { error } = await this.#temporaryFileRepository.upload(item.temporaryUnique, item.file, (evt) => { + // Update progress in percent if a callback is provided + if (item.onProgress) item.onProgress((evt.loaded / evt.total) * 100); + }); const status = error ? TemporaryFileStatus.ERROR : TemporaryFileStatus.SUCCESS; this.#queue.updateOne(item.temporaryUnique, { ...item, status }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/types.ts index 2c28850f47d3..e9c1697ab8e9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/types.ts @@ -8,6 +8,7 @@ export interface UmbTemporaryFileModel { file: File; temporaryUnique: string; status?: TemporaryFileStatus; + onProgress?: (progress: number) => void; } export type UmbQueueHandlerCallback = (item: TItem) => Promise; 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 6d48da923f9a..17b5d23e61e2 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 @@ -146,13 +146,13 @@ export class UmbDropzoneManager extends UmbControllerBase { message: `No media types are allowed for ${item.temporaryFile?.file.name}.`, }, }); - return this.#updateProgress(item, UmbFileDropzoneItemStatus.NOT_ALLOWED); + return this.#updateStatus(item, UmbFileDropzoneItemStatus.NOT_ALLOWED); } const mediaTypeUnique = options.length > 1 ? await this.#showDialogMediaTypePicker(options) : options[0].unique; if (!mediaTypeUnique) { - return this.#updateProgress(item, UmbFileDropzoneItemStatus.CANCELLED); + return this.#updateStatus(item, UmbFileDropzoneItemStatus.CANCELLED); } if (item.temporaryFile) { @@ -166,7 +166,7 @@ export class UmbDropzoneManager extends UmbControllerBase { for (const item of uploadableItems) { const options = await this.#getMediaTypeOptions(item); if (!options.length) { - this.#updateProgress(item, UmbFileDropzoneItemStatus.NOT_ALLOWED); + this.#updateStatus(item, UmbFileDropzoneItemStatus.NOT_ALLOWED); continue; } @@ -189,7 +189,7 @@ export class UmbDropzoneManager extends UmbControllerBase { // Upload the file as a temporary file and update progress. const temporaryFile = await this.#uploadAsTemporaryFile(item); if (temporaryFile.status !== TemporaryFileStatus.SUCCESS) { - this.#updateProgress(item, UmbFileDropzoneItemStatus.ERROR); + this.#updateStatus(item, UmbFileDropzoneItemStatus.ERROR); return; } @@ -198,9 +198,9 @@ export class UmbDropzoneManager extends UmbControllerBase { const { data } = await this.#mediaDetailRepository.create(scaffold, item.parentUnique); if (data) { - this.#updateProgress(item, UmbFileDropzoneItemStatus.COMPLETE); + this.#updateStatus(item, UmbFileDropzoneItemStatus.COMPLETE); } else { - this.#updateProgress(item, UmbFileDropzoneItemStatus.ERROR); + this.#updateStatus(item, UmbFileDropzoneItemStatus.ERROR); } } @@ -208,9 +208,9 @@ export class UmbDropzoneManager extends UmbControllerBase { const scaffold = await this.#getItemScaffold(item, mediaTypeUnique); const { data } = await this.#mediaDetailRepository.create(scaffold, item.parentUnique); if (data) { - this.#updateProgress(item, UmbFileDropzoneItemStatus.COMPLETE); + this.#updateStatus(item, UmbFileDropzoneItemStatus.COMPLETE); } else { - this.#updateProgress(item, UmbFileDropzoneItemStatus.ERROR); + this.#updateStatus(item, UmbFileDropzoneItemStatus.ERROR); } } @@ -218,6 +218,7 @@ export class UmbDropzoneManager extends UmbControllerBase { return this.#tempFileManager.uploadOne({ temporaryUnique: item.temporaryFile.temporaryUnique, file: item.temporaryFile.file, + onProgress: (progress) => this.#updateProgress(item, progress), }); } @@ -304,12 +305,16 @@ export class UmbDropzoneManager extends UmbControllerBase { return uploadableItems; } - #updateProgress(item: UmbUploadableItem, status: UmbFileDropzoneItemStatus) { + #updateStatus(item: UmbUploadableItem, status: UmbFileDropzoneItemStatus) { this.#progressItems.updateOne(item.unique, { status }); const progress = this.#progress.getValue(); this.#progress.update({ completed: progress.completed + 1 }); } + #updateProgress(item: UmbUploadableItem, progress: number) { + this.#progressItems.updateOne(item.unique, { progress }); + } + readonly #prepareItemsAsUploadable = ( { folders, files }: UmbFileDropzoneDroppedItems, parentUnique: string | null, @@ -321,6 +326,7 @@ export class UmbDropzoneManager extends UmbControllerBase { unique: UmbId.new(), parentUnique, status: UmbFileDropzoneItemStatus.WAITING, + progress: 0, temporaryFile: { file, temporaryUnique: UmbId.new() }, }); } @@ -331,6 +337,7 @@ export class UmbDropzoneManager extends UmbControllerBase { unique, parentUnique, status: UmbFileDropzoneItemStatus.WAITING, + progress: 100, // Folders are created instantly. folder: { name: subfolder.folderName }, }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/types.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/types.ts index 0e99dbb2d709..f1b90a32e885 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/types.ts @@ -11,6 +11,7 @@ export interface UmbUploadableItem { unique: string; parentUnique: string | null; status: UmbFileDropzoneItemStatus; + progress: number; folder?: { name: string }; temporaryFile?: UmbTemporaryFileModel; } From 900f57c648cb3ff78025761e10a0ec33d0731221 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 28 Jan 2025 16:16:56 +0100 Subject: [PATCH 18/25] feat: adds tracking of upload progress to placeholders --- .../media/media/collection/media-collection.context.ts | 6 ++++++ .../media/media/collection/media-collection.element.ts | 1 + .../src/packages/media/media/collection/types.ts | 1 + 3 files changed, 8 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts index ff7cf3939db9..e1ded4959298 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts @@ -33,6 +33,7 @@ export class UmbMediaCollectionContext extends UmbDefaultCollectionContext< updateDate: date, createDate: date, entityType: UMB_MEDIA_PLACEHOLDER_ENTITY_TYPE, + progress: 0, ...placeholder, })) .reverse(); @@ -48,6 +49,11 @@ export class UmbMediaCollectionContext extends UmbDefaultCollectionContext< this.#placeholders.updateOne(unique, { status }); } + updatePlaceholderProgress(unique: string, progress: number) { + this._items.updateOne(unique, { progress }); + this.#placeholders.updateOne(unique, { progress }); + } + /** * Requests the collection from the repository. * @returns {Promise} 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 cd0081de34ff..e5deb42fa9c0 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 @@ -42,6 +42,7 @@ export class UmbMediaCollectionElement extends UmbCollectionDefaultElement { if (item.folder?.name) return; this.#collectionContext?.updatePlaceholderStatus(item.unique, item.status); + this.#collectionContext?.updatePlaceholderProgress(item.unique, item.progress); }); }, '_observeProgressItems', diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/types.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/types.ts index 1a18e4ba71e0..13a2418271f2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/types.ts @@ -23,6 +23,7 @@ export interface UmbMediaCollectionItemModel { values?: Array<{ alias: string; value: string }>; url?: string; status?: UmbFileDropzoneItemStatus; + progress: number; } export interface UmbEditableMediaCollectionItemModel { From 647232a416eb8b07992e46d8911ffcb5b94e959d Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 28 Jan 2025 16:18:20 +0100 Subject: [PATCH 19/25] feat: bind the progress number up on the temporary file badge to indicate upload status --- .../views/grid/media-grid-collection-view.element.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts index 77fb522a72ea..91a8ca289012 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts @@ -115,7 +115,10 @@ export class UmbMediaGridCollectionViewElement extends UmbLitElement { const complete = item.status === UmbFileDropzoneItemStatus.COMPLETE; const error = item.status === UmbFileDropzoneItemStatus.ERROR; return html` - + `; } From c0d67e52147a6a9925cd6569a4d49912abf6f6e0 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 28 Jan 2025 17:01:36 +0100 Subject: [PATCH 20/25] feat: optimises progress calculation and makes the badge bigger to be able to show the progress in percent --- .../temporary-file-badge.element.ts | 65 +++++++------------ 1 file changed, 24 insertions(+), 41 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/components/temporary-file-badge.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/components/temporary-file-badge.element.ts index be3c318bc993..5c386e72396e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/components/temporary-file-badge.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/components/temporary-file-badge.element.ts @@ -4,19 +4,15 @@ import { clamp } from '@umbraco-cms/backoffice/utils'; @customElement('umb-temporary-file-badge') export class UmbTemporaryFileBadgeElement extends UmbLitElement { - private _progress = 0; + #progress = 0; @property({ type: Number }) public set progress(v: number) { - const oldVal = this._progress; - - const p = clamp(v, 0, 100); - this._progress = p; - - this.requestUpdate('progress', oldVal); + const p = clamp(Math.ceil(v), 0, 100); + this.#progress = p; } public get progress(): number { - return this._progress; + return this.#progress; } @property({ type: Boolean, reflect: true }) @@ -26,12 +22,10 @@ export class UmbTemporaryFileBadgeElement extends UmbLitElement { public error = false; override render() { - return html` -
- - ${this.#renderIcon()} -
-
`; + return html`
+ +
${this.#renderIcon()}
+
`; } #renderIcon() { @@ -43,50 +37,39 @@ export class UmbTemporaryFileBadgeElement extends UmbLitElement { return html``; } - return html``; + return `${this.progress}%`; } static override readonly styles = css` - :host { - display: block; - } - #wrapper { - box-sizing: border-box; - box-shadow: inset 0px 0px 0px 6px var(--uui-color-surface); - background-color: var(--uui-color-selected); position: relative; - border-radius: 100%; - font-size: var(--uui-size-6); + height: 75%; } - :host([complete]) #wrapper { - background-color: var(--uui-color-positive); + :host([complete]) { + uui-loader-circle, + #icon { + color: var(--uui-color-positive); + } } - :host([complete]) uui-loader-circle { - color: var(--uui-color-positive); - } - :host([error]) #wrapper { - background-color: var(--uui-color-danger); - } - :host([error]) uui-loader-circle { - color: var(--uui-color-danger); + :host([error]) { + uui-loader-circle, + #icon { + color: var(--uui-color-danger); + } } uui-loader-circle { - display: absolute; z-index: 2; inset: 0; color: var(--uui-color-focus); font-size: var(--uui-size-12); + width: 100%; + height: 100%; } - uui-badge { - padding: 0; - background-color: transparent; - } - - uui-icon { + #icon { + color: var(--uui-color-text); font-size: var(--uui-size-6); position: absolute; top: 50%; From f57d1ae87b8dd3e7f64c3bbafe4ed9403e1a6973 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 28 Jan 2025 17:17:31 +0100 Subject: [PATCH 21/25] feat: allow text to be normal --- .../views/grid/media-grid-collection-view.element.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts index 91a8ca289012..e62f3ec2b883 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts @@ -136,10 +136,6 @@ export class UmbMediaGridCollectionViewElement extends UmbLitElement { align-items: center; } - .media-placeholder-item { - font-style: italic; - } - /** TODO: Remove this fix when UUI gets upgrade to 1.3 */ umb-imaging-thumbnail { pointer-events: none; From e1e5408daf1cb9fb680dd5d1f80d6ce829c2b60b Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 28 Jan 2025 17:58:34 +0100 Subject: [PATCH 22/25] chore: use correct localization --- .../packages/media/media/dropzone/dropzone-manager.class.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 17b5d23e61e2..7733679150e8 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 @@ -20,6 +20,7 @@ import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import type { UmbAllowedMediaTypeModel } from '@umbraco-cms/backoffice/media-type'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'; +import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api'; /** * Manages the dropzone and uploads folders and files to the server. @@ -50,6 +51,7 @@ export class UmbDropzoneManager extends UmbControllerBase { public readonly progressItems = this.#progressItems.asObservable(); #notificationContext?: typeof UMB_NOTIFICATION_CONTEXT.TYPE; + #localization = new UmbLocalizationController(this); constructor(host: UmbControllerHost) { super(host); @@ -143,7 +145,7 @@ export class UmbDropzoneManager extends UmbControllerBase { if (!options.length) { this.#notificationContext?.peek('warning', { data: { - message: `No media types are allowed for ${item.temporaryFile?.file.name}.`, + message: `${this.#localization.term('media_disallowedFileType')}: ${item.temporaryFile?.file.name}.`, }, }); return this.#updateStatus(item, UmbFileDropzoneItemStatus.NOT_ALLOWED); From f8a979162d657ad5ab2159a6dd4ff72e97ccd403 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 28 Jan 2025 17:58:46 +0100 Subject: [PATCH 23/25] feat: shows error status for anything that isn't waiting or complete --- .../collection/views/grid/media-grid-collection-view.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts index e62f3ec2b883..f196150457f7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts @@ -113,7 +113,7 @@ export class UmbMediaGridCollectionViewElement extends UmbLitElement { #renderPlaceholder(item: UmbMediaCollectionItemModel) { const complete = item.status === UmbFileDropzoneItemStatus.COMPLETE; - const error = item.status === UmbFileDropzoneItemStatus.ERROR; + const error = item.status !== UmbFileDropzoneItemStatus.WAITING && !complete; return html` Date: Tue, 28 Jan 2025 18:06:33 +0100 Subject: [PATCH 24/25] feat: makes `progress` optional --- .../media/media/collection/media-collection.context.ts | 1 - .../src/packages/media/media/collection/types.ts | 5 ++++- .../views/grid/media-grid-collection-view.element.ts | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts index e1ded4959298..8353b3579e1d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts @@ -33,7 +33,6 @@ export class UmbMediaCollectionContext extends UmbDefaultCollectionContext< updateDate: date, createDate: date, entityType: UMB_MEDIA_PLACEHOLDER_ENTITY_TYPE, - progress: 0, ...placeholder, })) .reverse(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/types.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/types.ts index 13a2418271f2..78d0b0a566c2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/types.ts @@ -23,7 +23,10 @@ export interface UmbMediaCollectionItemModel { values?: Array<{ alias: string; value: string }>; url?: string; status?: UmbFileDropzoneItemStatus; - progress: number; + /** + * The progress of the item in percentage. + */ + progress?: number; } export interface UmbEditableMediaCollectionItemModel { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts index f196150457f7..3a5e187522b2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts @@ -116,7 +116,7 @@ export class UmbMediaGridCollectionViewElement extends UmbLitElement { const error = item.status !== UmbFileDropzoneItemStatus.WAITING && !complete; return html` `; From b1a61250ebc13a6e35c4a12a9181f047e7f6fece Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 29 Jan 2025 10:45:32 +0100 Subject: [PATCH 25/25] feat: set progress for createTemporaryFiles --- .../media/media/dropzone/dropzone-manager.class.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 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 7733679150e8..4a1bc9e3c610 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 @@ -114,16 +114,14 @@ export class UmbDropzoneManager extends UmbControllerBase { const uploaded = await this.#tempFileManager.uploadOne({ temporaryUnique: item.temporaryFile.temporaryUnique, file: item.temporaryFile.file, + onProgress: (progress) => this.#updateProgress(item, progress), }); // Update progress - const progress = this.#progress.getValue(); - this.#progress.update({ completed: progress.completed + 1 }); - if (uploaded.status === TemporaryFileStatus.SUCCESS) { - this.#progressItems.updateOne(item.unique, { status: UmbFileDropzoneItemStatus.COMPLETE }); + this.#updateStatus(item, UmbFileDropzoneItemStatus.COMPLETE); } else { - this.#progressItems.updateOne(item.unique, { status: UmbFileDropzoneItemStatus.ERROR }); + this.#updateStatus(item, UmbFileDropzoneItemStatus.ERROR); } // Add to return value