From 6fb7e1045a5321bed5d4da3a0b07b10d4adcc937 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 10 Jul 2024 10:54:48 +0200 Subject: [PATCH 01/12] feat: add thumbnail component that can request a media key and show it or fallback to an icon --- .../components/imaging-thumbnail.element.ts | 144 ++++++++++++++++++ .../media/imaging/components/index.ts | 1 + .../media/imaging/imaging.repository.ts | 5 +- src/packages/media/imaging/index.ts | 1 + src/packages/media/imaging/types.ts | 6 +- 5 files changed, 152 insertions(+), 5 deletions(-) create mode 100644 src/packages/media/imaging/components/imaging-thumbnail.element.ts create mode 100644 src/packages/media/imaging/components/index.ts diff --git a/src/packages/media/imaging/components/imaging-thumbnail.element.ts b/src/packages/media/imaging/components/imaging-thumbnail.element.ts new file mode 100644 index 0000000000..caf6261772 --- /dev/null +++ b/src/packages/media/imaging/components/imaging-thumbnail.element.ts @@ -0,0 +1,144 @@ +import { UmbImagingCropMode } from '../types.js'; +import { UmbImagingRepository } from '../imaging.repository.js'; +import { css, customElement, html, nothing, property, state, when } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; + +const ELEMENT_NAME = 'umb-imaging-thumbnail'; + +@customElement(ELEMENT_NAME) +export class UmbImagingThumbnailElement extends UmbLitElement { + /** + * The unique identifier for the media item. + * @remark This is also known as the media key and is used to fetch the resource. + */ + @property() + unique = ''; + + /** + * The width of the thumbnail. + */ + @property({ type: Number }) + width = 400; + + /** + * The height of the thumbnail. + */ + @property({ type: Number }) + height = 400; + + /** + * The mode of the thumbnail. + * @remark The mode determines how the image is cropped. + * @enum {UmbImagingCropMode} + */ + @property() + mode: UmbImagingCropMode = UmbImagingCropMode.MIN; + + /** + * The alt text for the thumbnail. + */ + @property() + alt = ''; + + /** + * The fallback icon for the thumbnail. + */ + @property() + icon = 'icon-picture'; + + /** + * The `loading` state of the thumbnail. + * @enum {'lazy' | 'eager'} + * @default 'lazy' + */ + @property() + loading: 'lazy' | 'eager' = 'lazy'; + + @state() + private _isLoading = true; + + @state() + private _thumbnailUrl = ''; + + #imagingRepository = new UmbImagingRepository(this); + + protected override async firstUpdated() { + await this.#generateThumbnailUrl(); + this._isLoading = false; + } + + override render() { + return html` ${this.#renderThumbnail()} ${when(this._isLoading, () => this.#renderLoading())} `; + } + + #renderLoading() { + return html`
`; + } + + #renderThumbnail() { + if (this._isLoading) return nothing; + + return when( + this._thumbnailUrl, + () => + html`${this.alt}`, + () => html``, + ); + } + + async #generateThumbnailUrl() { + const { data } = await this.#imagingRepository.requestThumbnailUrls( + [this.unique], + this.height, + this.width, + this.mode, + ); + this._thumbnailUrl = data[0]?.url ?? ''; + } + + static override styles = [ + UmbTextStyles, + css` + :host { + display: block; + position: relative; + overflow: hidden; + } + + .container { + display: flex; + justify-content: center; + align-items: center; + } + + #figure { + display: block; + width: 100%; + height: 100%; + object-fit: cover; + + background-image: url('data:image/svg+xml;charset=utf-8,'); + background-size: 10px 10px; + background-repeat: repeat; + } + + #icon { + width: 100%; + height: 100%; + font-size: var(--uui-size-8); + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + [ELEMENT_NAME]: UmbImagingThumbnailElement; + } +} diff --git a/src/packages/media/imaging/components/index.ts b/src/packages/media/imaging/components/index.ts new file mode 100644 index 0000000000..60818ea906 --- /dev/null +++ b/src/packages/media/imaging/components/index.ts @@ -0,0 +1 @@ +export * from './imaging-thumbnail.element.js'; diff --git a/src/packages/media/imaging/imaging.repository.ts b/src/packages/media/imaging/imaging.repository.ts index d6f7935283..c602078a52 100644 --- a/src/packages/media/imaging/imaging.repository.ts +++ b/src/packages/media/imaging/imaging.repository.ts @@ -1,7 +1,6 @@ -import type { UmbImagingModel } from './types.js'; +import { UmbImagingCropMode, type UmbImagingModel } from './types.js'; import { UmbImagingServerDataSource } from './imaging.server.data.js'; import { UMB_IMAGING_STORE_CONTEXT } from './imaging.store.token.js'; -import { ImageCropModeModel } from '@umbraco-cms/backoffice/external/backend-api'; import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; @@ -68,7 +67,7 @@ export class UmbImagingRepository extends UmbRepositoryBase implements UmbApi { * @param {ImageCropModeModel} mode - The crop mode * @memberof UmbImagingRepository */ - async requestThumbnailUrls(uniques: Array, height: number, width: number, mode = ImageCropModeModel.MIN) { + async requestThumbnailUrls(uniques: Array, height: number, width: number, mode = UmbImagingCropMode.MIN) { const imagingModel: UmbImagingModel = { height, width, mode }; return this.requestResizedItems(uniques, imagingModel); } diff --git a/src/packages/media/imaging/index.ts b/src/packages/media/imaging/index.ts index afd4abe3b5..d05152d5dc 100644 --- a/src/packages/media/imaging/index.ts +++ b/src/packages/media/imaging/index.ts @@ -1,2 +1,3 @@ +export * from './components/index.js'; export { UmbImagingRepository } from './imaging.repository.js'; export { UMB_IMAGING_REPOSITORY_ALIAS } from './constants.js'; diff --git a/src/packages/media/imaging/types.ts b/src/packages/media/imaging/types.ts index 41df2f5b95..0411433515 100644 --- a/src/packages/media/imaging/types.ts +++ b/src/packages/media/imaging/types.ts @@ -1,7 +1,9 @@ -import type { ImageCropModeModel } from '@umbraco-cms/backoffice/external/backend-api'; +import { ImageCropModeModel as UmbImagingCropMode } from '@umbraco-cms/backoffice/external/backend-api'; + +export { UmbImagingCropMode }; export interface UmbImagingModel { height?: number; width?: number; - mode?: ImageCropModeModel; + mode?: UmbImagingCropMode; } From 83e2785b782efae1e40f421c16f7b5c107332b17 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 10 Jul 2024 10:55:02 +0200 Subject: [PATCH 02/12] feat: use the `` element in media collections --- .../collection/media-collection.context.ts | 29 ++++--------------- .../media-grid-collection-view.element.ts | 20 ++----------- 2 files changed, 7 insertions(+), 42 deletions(-) diff --git a/src/packages/media/media/collection/media-collection.context.ts b/src/packages/media/media/collection/media-collection.context.ts index 60ca5a16f5..4785fc9b8c 100644 --- a/src/packages/media/media/collection/media-collection.context.ts +++ b/src/packages/media/media/collection/media-collection.context.ts @@ -1,7 +1,5 @@ import type { UmbMediaCollectionFilterModel, UmbMediaCollectionItemModel } from './types.js'; import { UMB_MEDIA_GRID_COLLECTION_VIEW_ALIAS } from './views/index.js'; -import { UmbImagingRepository } from '@umbraco-cms/backoffice/imaging'; -import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; import { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; @@ -9,31 +7,14 @@ export class UmbMediaCollectionContext extends UmbDefaultCollectionContext< UmbMediaCollectionItemModel, UmbMediaCollectionFilterModel > { - #imagingRepository: UmbImagingRepository; - - #thumbnailItems = new UmbArrayState([], (x) => x.unique); - public readonly thumbnailItems = this.#thumbnailItems.asObservable(); + /** + * The thumbnail items that are currently displayed in the collection. + * @deprecated Use the `` element instead. + */ + public readonly thumbnailItems = this.items; constructor(host: UmbControllerHost) { super(host, UMB_MEDIA_GRID_COLLECTION_VIEW_ALIAS); - this.#imagingRepository = new UmbImagingRepository(host); - - this.observe(this.items, async (items) => { - if (!items?.length) return; - - const { data } = await this.#imagingRepository.requestThumbnailUrls( - items.map((m) => m.unique), - 400, - 400, - ); - - this.#thumbnailItems.setValue( - items.map((item) => { - const thumbnail = data?.find((m) => m.unique === item.unique)?.url; - return { ...item, url: thumbnail }; - }), - ); - }); } } diff --git a/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts b/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts index 2345df4307..b4d6909278 100644 --- a/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts +++ b/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts @@ -52,7 +52,7 @@ export class UmbMediaGridCollectionViewElement extends UmbLitElement { this.observe(this.#collectionContext.loading, (loading) => (this._loading = loading), '_observeLoading'); - this.observe(this.#collectionContext.thumbnailItems, (items) => (this._items = items), '_observeItems'); + this.observe(this.#collectionContext.items, (items) => (this._items = items), '_observeItems'); this.observe( this.#collectionContext.selection.selection, @@ -127,13 +127,7 @@ export class UmbMediaGridCollectionViewElement extends UmbLitElement { @selected=${() => this.#onSelect(item)} @deselected=${() => this.#onDeselect(item)} class="media-item"> - ${when( - item.url, - () => html`${item.name}`, - () => html``, - )} - - + `; } @@ -158,16 +152,6 @@ export class UmbMediaGridCollectionViewElement extends UmbLitElement { grid-auto-rows: 200px; gap: var(--uui-size-space-5); } - - img { - background-image: url('data:image/svg+xml;charset=utf-8,'); - background-size: 10px 10px; - background-repeat: repeat; - } - - umb-icon { - font-size: var(--uui-size-8); - } `, ]; } From a60b177b3b0305dbf247716c301baff3a11775b0 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 10 Jul 2024 11:00:39 +0200 Subject: [PATCH 03/12] feat: use the `` element in the media picker modal --- .../media-picker-modal.element.ts | 35 ++++--------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts b/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts index 97c3f20f4f..bf346bbeec 100644 --- a/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts +++ b/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts @@ -2,13 +2,11 @@ import { UmbMediaItemRepository } from '../../repository/index.js'; import { UmbMediaTreeRepository } from '../../tree/media-tree.repository.js'; import { UMB_MEDIA_ROOT_ENTITY_TYPE } from '../../entity.js'; import type { UmbDropzoneElement } from '../../dropzone/dropzone.element.js'; -import type { UmbMediaItemModel } from '../../repository/index.js'; import type { UmbMediaCardItemModel, UmbMediaPathModel } from './types.js'; import type { UmbMediaPickerFolderPathElement } from './components/media-picker-folder-path.element.js'; import type { UmbMediaPickerModalData, UmbMediaPickerModalValue } from './media-picker-modal.token.js'; import { css, html, customElement, state, repeat, ifDefined, query } from '@umbraco-cms/backoffice/external/lit'; import { debounce } from '@umbraco-cms/backoffice/utils'; -import { UmbImagingRepository } from '@umbraco-cms/backoffice/imaging'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; import { UMB_CONTENT_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/content'; import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; @@ -22,13 +20,9 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement< > { #mediaTreeRepository = new UmbMediaTreeRepository(this); // used to get file structure #mediaItemRepository = new UmbMediaItemRepository(this); // used to search - #imagingRepository = new UmbImagingRepository(this); // used to get image renditions #dataType?: { unique: string }; - @state() - private _filter: (item: UmbMediaCardItemModel) => boolean = () => true; - @state() private _selectableFilter: (item: UmbMediaCardItemModel) => boolean = () => true; @@ -62,7 +56,6 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement< override async connectedCallback(): Promise { super.connectedCallback(); - if (this.data?.filter) this._filter = this.data?.filter; if (this.data?.pickableFilter) this._selectableFilter = this.data?.pickableFilter; if (this.data?.startNode) { @@ -87,27 +80,10 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement< take: 100, }); - this.#mediaItemsCurrentFolder = await this.#mapMediaUrls(data?.items ?? []); + this.#mediaItemsCurrentFolder = data?.items ?? []; this.#filterMediaItems(); } - async #mapMediaUrls(items: Array): Promise> { - if (!items.length) return []; - - const { data } = await this.#imagingRepository.requestThumbnailUrls( - items.map((item) => item.unique), - 400, - 400, - ); - - return items - .map((item): UmbMediaCardItemModel => { - const src = data?.find((media) => media.unique === item.unique)?.url; - return { ...item, src }; - }) - .filter((item) => this._filter(item)); - } - #onOpen(item: UmbMediaCardItemModel) { this._currentMediaEntity = { name: item.name, @@ -152,7 +128,7 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement< } // Map urls for search results as we are going to show for all folders (as long they aren't trashed). - this._mediaFilteredList = await this.#mapMediaUrls(data.filter((found) => found.isTrashed === false)); + this._mediaFilteredList = data.filter((found) => found.isTrashed === false); } #debouncedSearch = debounce(() => { @@ -240,9 +216,10 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement< @deselected=${() => this.#onDeselected(item)} ?selected=${this.value?.selection?.find((value) => value === item.unique)} ?selectable=${!disabled}> - ${item.src - ? html`${ifDefined(item.name)}` - : html``} + `; } From 3b8dd65dc34d4c18eff0b304c60c9e7e43d1100c Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 10 Jul 2024 11:00:56 +0200 Subject: [PATCH 04/12] feat: use the `` element in the media picker property editor --- .../input-rich-media/input-rich-media.element.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/packages/media/media/components/input-rich-media/input-rich-media.element.ts b/src/packages/media/media/components/input-rich-media/input-rich-media.element.ts index 82bda097f4..2fa650211e 100644 --- a/src/packages/media/media/components/input-rich-media/input-rich-media.element.ts +++ b/src/packages/media/media/components/input-rich-media/input-rich-media.element.ts @@ -4,11 +4,10 @@ import { UMB_IMAGE_CROPPER_EDITOR_MODAL, UMB_MEDIA_PICKER_MODAL } from '../../mo import type { UmbCropModel, UmbMediaPickerPropertyValue } from '../../property-editors/index.js'; import type { UmbMediaItemModel } from '../../repository/index.js'; import type { UmbUploadableFileModel } from '../../dropzone/index.js'; -import { customElement, html, ifDefined, nothing, property, repeat, state } from '@umbraco-cms/backoffice/external/lit'; +import { customElement, html, nothing, property, repeat, state } from '@umbraco-cms/backoffice/external/lit'; import { umbConfirmModal, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbId } from '@umbraco-cms/backoffice/id'; -import { UmbImagingRepository } from '@umbraco-cms/backoffice/imaging'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; @@ -162,8 +161,6 @@ export class UmbInputRichMediaElement extends UUIFormControlMixin(UmbLitElement, #itemRepository = new UmbMediaItemRepository(this); - #imagingRepository = new UmbImagingRepository(this); - #modalRouter: UmbModalRouteRegistrationController; #modalManager?: UmbModalManagerContext; @@ -249,16 +246,13 @@ export class UmbInputRichMediaElement extends UUIFormControlMixin(UmbLitElement, const uniques = this.items.map((item) => item.mediaKey); const { data: items } = await this.#itemRepository.requestItems(uniques); - const { data: thumbnails } = await this.#imagingRepository.requestThumbnailUrls(uniques, 400, 400); this._cards = this.items.map((item) => { const media = items?.find((x) => x.unique === item.mediaKey); - const thumbnail = thumbnails?.find((x) => x.unique === item.mediaKey); return { unique: item.key, media: item.mediaKey, name: media?.name ?? '', - src: thumbnail?.url, icon: media?.mediaType?.icon, isTrashed: media?.isTrashed ?? false, }; @@ -366,9 +360,10 @@ export class UmbInputRichMediaElement extends UUIFormControlMixin(UmbLitElement, const href = this._routeBuilder?.({ key: item.unique }); return html` - ${item.src - ? html`${item.name}` - : html``} + ${this.#renderIsTrashed(item)} Date: Wed, 10 Jul 2024 11:01:11 +0200 Subject: [PATCH 05/12] feat: set defaults to 300x300 thumbnails --- .../imaging/components/imaging-thumbnail.element.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/packages/media/imaging/components/imaging-thumbnail.element.ts b/src/packages/media/imaging/components/imaging-thumbnail.element.ts index caf6261772..c1c3a59275 100644 --- a/src/packages/media/imaging/components/imaging-thumbnail.element.ts +++ b/src/packages/media/imaging/components/imaging-thumbnail.element.ts @@ -16,16 +16,18 @@ export class UmbImagingThumbnailElement extends UmbLitElement { unique = ''; /** - * The width of the thumbnail. + * The width of the thumbnail in pixels. + * @default 300 */ @property({ type: Number }) - width = 400; + width = 300; /** - * The height of the thumbnail. + * The height of the thumbnail in pixels. + * @default 300 */ @property({ type: Number }) - height = 400; + height = 300; /** * The mode of the thumbnail. From a4496f9a172ae34ae4ed748303a00a0c52c8e81d Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 10 Jul 2024 11:06:52 +0200 Subject: [PATCH 06/12] feat: use the `` element in the `` element --- .../input-media/input-media.element.ts | 29 +++++-------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/src/packages/media/media/components/input-media/input-media.element.ts b/src/packages/media/media/components/input-media/input-media.element.ts index 7affdb7650..8377fa87d1 100644 --- a/src/packages/media/media/components/input-media/input-media.element.ts +++ b/src/packages/media/media/components/input-media/input-media.element.ts @@ -1,7 +1,6 @@ import type { UmbMediaCardItemModel } from '../../modals/index.js'; import type { UmbMediaItemModel } from '../../repository/index.js'; import { UmbMediaPickerContext } from './input-media.context.js'; -import { UmbImagingRepository } from '@umbraco-cms/backoffice/imaging'; import { css, customElement, html, ifDefined, property, repeat, state } from '@umbraco-cms/backoffice/external/lit'; import { splitStringToArray } from '@umbraco-cms/backoffice/utils'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; @@ -11,6 +10,8 @@ import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/modal'; import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; +import '@umbraco-cms/backoffice/imaging'; + const elementName = 'umb-input-media'; @customElement(elementName) @@ -123,8 +124,6 @@ export class UmbInputMediaElement extends UmbFormControlMixin !this._cards.find((card) => card.unique === item.unique)); if (selectedItems?.length && !missingCards.length) return; - if (!selectedItems?.length) { - this._cards = []; - return; - } - - const uniques = selectedItems.map((x) => x.unique); - - const { data: thumbnails } = await this.#imagingRepository.requestThumbnailUrls(uniques, 400, 400); - - this._cards = selectedItems.map((item) => { - const thumbnail = thumbnails?.find((x) => x.unique === item.unique); - return { - ...item, - src: thumbnail?.url, - }; - }); + this._cards = selectedItems ?? []; }); this.addValidator( @@ -228,9 +212,10 @@ export class UmbInputMediaElement extends UmbFormControlMixin - ${item.src - ? html`${item.name}` - : html``} + ${this.#renderIsTrashed(item)} Date: Wed, 10 Jul 2024 11:07:11 +0200 Subject: [PATCH 07/12] chore: add imports in case the elements are shown outside Content or Media --- .../collection/views/grid/media-grid-collection-view.element.ts | 2 ++ .../components/input-rich-media/input-rich-media.element.ts | 2 ++ .../media/modals/media-picker/media-picker-modal.element.ts | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts b/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts index b4d6909278..27042a9738 100644 --- a/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts +++ b/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts @@ -8,6 +8,8 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/modal'; import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; +import '@umbraco-cms/backoffice/imaging'; + @customElement('umb-media-grid-collection-view') export class UmbMediaGridCollectionViewElement extends UmbLitElement { @state() diff --git a/src/packages/media/media/components/input-rich-media/input-rich-media.element.ts b/src/packages/media/media/components/input-rich-media/input-rich-media.element.ts index 2fa650211e..43d8a51bc7 100644 --- a/src/packages/media/media/components/input-rich-media/input-rich-media.element.ts +++ b/src/packages/media/media/components/input-rich-media/input-rich-media.element.ts @@ -16,6 +16,8 @@ import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; import type { UmbModalRouteBuilder } from '@umbraco-cms/backoffice/router'; import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import '@umbraco-cms/backoffice/imaging'; + type UmbRichMediaCardModel = { unique: string; media: string; diff --git a/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts b/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts index bf346bbeec..b83579178f 100644 --- a/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts +++ b/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts @@ -11,6 +11,8 @@ import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; import { UMB_CONTENT_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/content'; import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; +import '@umbraco-cms/backoffice/imaging'; + const root: UmbMediaPathModel = { name: 'Media', unique: null, entityType: UMB_MEDIA_ROOT_ENTITY_TYPE }; @customElement('umb-media-picker-modal') From 0459403e14597e16dac47dc800083ab6a46f2c0c Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 10 Jul 2024 11:18:58 +0200 Subject: [PATCH 08/12] chore: avoid having firstUpdated async --- .../imaging/components/imaging-thumbnail.element.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/packages/media/imaging/components/imaging-thumbnail.element.ts b/src/packages/media/imaging/components/imaging-thumbnail.element.ts index c1c3a59275..f2e8cf5482 100644 --- a/src/packages/media/imaging/components/imaging-thumbnail.element.ts +++ b/src/packages/media/imaging/components/imaging-thumbnail.element.ts @@ -65,9 +65,8 @@ export class UmbImagingThumbnailElement extends UmbLitElement { #imagingRepository = new UmbImagingRepository(this); - protected override async firstUpdated() { - await this.#generateThumbnailUrl(); - this._isLoading = false; + protected override firstUpdated() { + this.#generateThumbnailUrl(); } override render() { @@ -101,7 +100,9 @@ export class UmbImagingThumbnailElement extends UmbLitElement { this.width, this.mode, ); - this._thumbnailUrl = data[0]?.url ?? ''; + + this._thumbnailUrl = data[0].url ?? ''; + this._isLoading = false; } static override styles = [ From 39f5a2399c363acd7bbf136005a2449938bffe6b Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 10 Jul 2024 11:19:39 +0200 Subject: [PATCH 09/12] fix: check for undefined --- .../media/imaging/components/imaging-thumbnail.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/media/imaging/components/imaging-thumbnail.element.ts b/src/packages/media/imaging/components/imaging-thumbnail.element.ts index f2e8cf5482..9450c74f76 100644 --- a/src/packages/media/imaging/components/imaging-thumbnail.element.ts +++ b/src/packages/media/imaging/components/imaging-thumbnail.element.ts @@ -101,7 +101,7 @@ export class UmbImagingThumbnailElement extends UmbLitElement { this.mode, ); - this._thumbnailUrl = data[0].url ?? ''; + this._thumbnailUrl = data?.[0].url ?? ''; this._isLoading = false; } From 844fdb6bab42b4b55e4b4b8e8e6b3a5d2aa385ea Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 10 Jul 2024 11:20:20 +0200 Subject: [PATCH 10/12] fix: check for undefined --- .../media/imaging/components/imaging-thumbnail.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/media/imaging/components/imaging-thumbnail.element.ts b/src/packages/media/imaging/components/imaging-thumbnail.element.ts index 9450c74f76..1407b2f15b 100644 --- a/src/packages/media/imaging/components/imaging-thumbnail.element.ts +++ b/src/packages/media/imaging/components/imaging-thumbnail.element.ts @@ -101,7 +101,7 @@ export class UmbImagingThumbnailElement extends UmbLitElement { this.mode, ); - this._thumbnailUrl = data?.[0].url ?? ''; + this._thumbnailUrl = data?.[0]?.url ?? ''; this._isLoading = false; } From afa2cdefcfba8092d20ba1305116026b1e64af21 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 10 Jul 2024 11:32:44 +0200 Subject: [PATCH 11/12] feat: add support for an IntersectionObserver in case the images are lazily loaded --- .../components/imaging-thumbnail.element.ts | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/packages/media/imaging/components/imaging-thumbnail.element.ts b/src/packages/media/imaging/components/imaging-thumbnail.element.ts index 1407b2f15b..cdcb474deb 100644 --- a/src/packages/media/imaging/components/imaging-thumbnail.element.ts +++ b/src/packages/media/imaging/components/imaging-thumbnail.element.ts @@ -65,14 +65,33 @@ export class UmbImagingThumbnailElement extends UmbLitElement { #imagingRepository = new UmbImagingRepository(this); - protected override firstUpdated() { - this.#generateThumbnailUrl(); - } + #intersectionObserver?: IntersectionObserver; override render() { return html` ${this.#renderThumbnail()} ${when(this._isLoading, () => this.#renderLoading())} `; } + override connectedCallback() { + super.connectedCallback(); + + if (this.loading === 'lazy') { + this.#intersectionObserver = new IntersectionObserver((entries) => { + if (entries[0].isIntersecting) { + this.#generateThumbnailUrl(); + this.#intersectionObserver?.disconnect(); + } + }); + this.#intersectionObserver.observe(this); + } else { + this.#generateThumbnailUrl(); + } + } + + override disconnectedCallback() { + super.disconnectedCallback(); + this.#intersectionObserver?.disconnect(); + } + #renderLoading() { return html`
`; } From ff90ca1dbb879a6fdedf3d3065fec82a2fdbc7f3 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 10 Jul 2024 11:33:07 +0200 Subject: [PATCH 12/12] feat: center all items inside whether they be the figure, icon, or loader --- .../imaging/components/imaging-thumbnail.element.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/packages/media/imaging/components/imaging-thumbnail.element.ts b/src/packages/media/imaging/components/imaging-thumbnail.element.ts index cdcb474deb..81ae2ff903 100644 --- a/src/packages/media/imaging/components/imaging-thumbnail.element.ts +++ b/src/packages/media/imaging/components/imaging-thumbnail.element.ts @@ -93,7 +93,7 @@ export class UmbImagingThumbnailElement extends UmbLitElement { } #renderLoading() { - return html`
`; + return html`
`; } #renderThumbnail() { @@ -131,12 +131,19 @@ export class UmbImagingThumbnailElement extends UmbLitElement { display: block; position: relative; overflow: hidden; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; } - .container { + #loader { display: flex; justify-content: center; align-items: center; + height: 100%; + width: 100%; } #figure {