diff --git a/src/packages/media/imaging/components/imaging-thumbnail.element.ts b/src/packages/media/imaging/components/imaging-thumbnail.element.ts
index 81ae2ff903..b11ceceeb9 100644
--- a/src/packages/media/imaging/components/imaging-thumbnail.element.ts
+++ b/src/packages/media/imaging/components/imaging-thumbnail.element.ts
@@ -4,13 +4,11 @@ import { css, customElement, html, nothing, property, state, when } from '@umbra
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
-const ELEMENT_NAME = 'umb-imaging-thumbnail';
-
-@customElement(ELEMENT_NAME)
+@customElement('umb-imaging-thumbnail')
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.
+ * @description This is also known as the media key and is used to fetch the resource.
*/
@property()
unique = '';
@@ -31,7 +29,7 @@ export class UmbImagingThumbnailElement extends UmbLitElement {
/**
* The mode of the thumbnail.
- * @remark The mode determines how the image is cropped.
+ * @description The mode determines how the image is cropped.
* @enum {UmbImagingCropMode}
*/
@property()
@@ -55,7 +53,7 @@ export class UmbImagingThumbnailElement extends UmbLitElement {
* @default 'lazy'
*/
@property()
- loading: 'lazy' | 'eager' = 'lazy';
+ loading: (typeof HTMLImageElement)['prototype']['loading'] = 'lazy';
@state()
private _isLoading = true;
@@ -168,6 +166,6 @@ export class UmbImagingThumbnailElement extends UmbLitElement {
declare global {
interface HTMLElementTagNameMap {
- [ELEMENT_NAME]: UmbImagingThumbnailElement;
+ 'umb-imaging-thumbnail': UmbImagingThumbnailElement;
}
}
diff --git a/src/packages/media/imaging/components/index.ts b/src/packages/media/imaging/components/index.ts
index 60818ea906..a2ce8114af 100644
--- a/src/packages/media/imaging/components/index.ts
+++ b/src/packages/media/imaging/components/index.ts
@@ -1 +1,2 @@
export * from './imaging-thumbnail.element.js';
+export * from './media-image.element.js';
diff --git a/src/packages/media/imaging/components/media-image.element.ts b/src/packages/media/imaging/components/media-image.element.ts
new file mode 100644
index 0000000000..c81596b112
--- /dev/null
+++ b/src/packages/media/imaging/components/media-image.element.ts
@@ -0,0 +1,126 @@
+import { UmbMediaUrlRepository } from '../../media/repository/index.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';
+
+@customElement('umb-media-image')
+export class UmbMediaImageElement extends UmbLitElement {
+ /**
+ * The unique identifier for the media item.
+ * @description This is also known as the media key and is used to fetch the resource.
+ */
+ @property()
+ unique?: string;
+
+ /**
+ * The alt text for the thumbnail.
+ */
+ @property()
+ alt?: string;
+
+ /**
+ * The fallback icon for the thumbnail.
+ */
+ @property()
+ icon = 'icon-picture';
+
+ /**
+ * The `loading` state of the thumbnail.
+ * @enum {'lazy' | 'eager'}
+ * @default 'lazy'
+ */
+ @property()
+ loading: (typeof HTMLImageElement)['prototype']['loading'] = 'lazy';
+
+ @state()
+ private _isLoading = true;
+
+ @state()
+ private _imageUrl = '';
+
+ #mediaRepository = new UmbMediaUrlRepository(this);
+
+ #intersectionObserver?: IntersectionObserver;
+
+ 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();
+ }
+
+ async #generateThumbnailUrl() {
+ if (!this.unique) throw new Error('Unique is missing');
+ const { data } = await this.#mediaRepository.requestItems([this.unique]);
+
+ this._imageUrl = data?.[0]?.url ?? '';
+ 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._imageUrl,
+ () =>
+ html``,
+ () => html``,
+ );
+ }
+
+ static override styles = [
+ UmbTextStyles,
+ css`
+ :host {
+ display: contents;
+ }
+
+ #loader {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100%;
+ width: 100%;
+ }
+
+ #icon {
+ width: 100%;
+ height: 100%;
+ font-size: var(--uui-size-8);
+ }
+ `,
+ ];
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'umb-media-image': UmbMediaImageElement;
+ }
+}
diff --git a/src/packages/media/imaging/imaging.repository.ts b/src/packages/media/imaging/imaging.repository.ts
index 4672f801d9..ae2d6ff134 100644
--- a/src/packages/media/imaging/imaging.repository.ts
+++ b/src/packages/media/imaging/imaging.repository.ts
@@ -1,4 +1,4 @@
-import { UmbImagingCropMode, type UmbImagingModel } from './types.js';
+import { UmbImagingCropMode, type UmbImagingResizeModel } from './types.js';
import { UmbImagingServerDataSource } from './imaging.server.data.js';
import { UMB_IMAGING_STORE_CONTEXT } from './imaging.store.token.js';
import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository';
@@ -21,13 +21,14 @@ export class UmbImagingRepository extends UmbRepositoryBase implements UmbApi {
/**
* Requests the items for the given uniques
- * @param {Array} uniques
- * @param imagingModel
+ * @param {Array} uniques - The uniques
+ * @param {UmbImagingResizeModel} imagingModel - The imaging model
+ * @returns {Promise<{ data: UmbMediaUrlModel[] }>}
* @memberof UmbImagingRepository
*/
async requestResizedItems(
uniques: Array,
- imagingModel?: UmbImagingModel,
+ imagingModel?: UmbImagingResizeModel,
): Promise<{ data: UmbMediaUrlModel[] }> {
if (!uniques.length) throw new Error('Uniques are missing');
if (!this.#dataStore) throw new Error('Data store is missing');
@@ -69,7 +70,7 @@ export class UmbImagingRepository extends UmbRepositoryBase implements UmbApi {
* @memberof UmbImagingRepository
*/
async requestThumbnailUrls(uniques: Array, height: number, width: number, mode = UmbImagingCropMode.MIN) {
- const imagingModel: UmbImagingModel = { height, width, mode };
+ const imagingModel: UmbImagingResizeModel = { height, width, mode };
return this.requestResizedItems(uniques, imagingModel);
}
}
diff --git a/src/packages/media/imaging/imaging.server.data.ts b/src/packages/media/imaging/imaging.server.data.ts
index 30ec2b411b..e53b9b1ee7 100644
--- a/src/packages/media/imaging/imaging.server.data.ts
+++ b/src/packages/media/imaging/imaging.server.data.ts
@@ -1,4 +1,4 @@
-import type { UmbImagingModel } from './types.js';
+import type { UmbImagingResizeModel } from './types.js';
import { ImagingService, type MediaUrlInfoResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbMediaUrlModel } from '@umbraco-cms/backoffice/media';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
@@ -28,7 +28,7 @@ export class UmbImagingServerDataSource {
* @param imagingModel
* @memberof UmbImagingServerDataSource
*/
- async getItems(uniques: Array, imagingModel?: UmbImagingModel) {
+ async getItems(uniques: Array, imagingModel?: UmbImagingResizeModel) {
if (!uniques.length) throw new Error('Uniques are missing');
const { data, error } = await tryExecuteAndNotify(
diff --git a/src/packages/media/imaging/imaging.store.ts b/src/packages/media/imaging/imaging.store.ts
index 85af357997..5bff323f4f 100644
--- a/src/packages/media/imaging/imaging.store.ts
+++ b/src/packages/media/imaging/imaging.store.ts
@@ -1,5 +1,5 @@
import { UMB_IMAGING_STORE_CONTEXT } from './imaging.store.token.js';
-import type { UmbImagingModel } from './types.js';
+import type { UmbImagingResizeModel } from './types.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
@@ -14,7 +14,8 @@ export class UmbImagingStore extends UmbContextBase implements UmbApi {
/**
* Gets the data from the store.
- * @param unique
+ * @param {string} unique - The media key
+ * @returns {Map | undefined} - The data if it exists
*/
getData(unique: string) {
return this.#data.get(unique);
@@ -22,20 +23,21 @@ export class UmbImagingStore extends UmbContextBase implements UmbApi {
/**
* Gets a specific crop if it exists.
- * @param unique
- * @param data
+ * @param {string} unique - The media key
+ * @param {string} data - The resize configuration
+ * @returns {string | undefined} - The crop if it exists
*/
- getCrop(unique: string, data?: UmbImagingModel) {
+ getCrop(unique: string, data?: UmbImagingResizeModel) {
return this.#data.get(unique)?.get(this.#generateCropKey(data));
}
/**
* Adds a new crop to the store.
- * @param unique
- * @param urlInfo
- * @param data
+ * @param {string} unique - The media key
+ * @param {string} urlInfo - The URL of the crop
+ * @param { | undefined} data - The resize configuration
*/
- addCrop(unique: string, urlInfo: string, data?: UmbImagingModel) {
+ addCrop(unique: string, urlInfo: string, data?: UmbImagingResizeModel) {
if (!this.#data.has(unique)) {
this.#data.set(unique, new Map());
}
@@ -44,9 +46,10 @@ export class UmbImagingStore extends UmbContextBase implements UmbApi {
/**
* Generates a unique key for the crop based on the width, height and mode.
- * @param data
+ * @param {UmbImagingResizeModel} data - The resize configuration
+ * @returns {string} - The crop key
*/
- #generateCropKey(data?: UmbImagingModel) {
+ #generateCropKey(data?: UmbImagingResizeModel) {
return data ? `${data.width}x${data.height};${data.mode}` : 'generic';
}
}
diff --git a/src/packages/media/imaging/types.ts b/src/packages/media/imaging/types.ts
index 0411433515..6d848cca61 100644
--- a/src/packages/media/imaging/types.ts
+++ b/src/packages/media/imaging/types.ts
@@ -2,8 +2,14 @@ import { ImageCropModeModel as UmbImagingCropMode } from '@umbraco-cms/backoffic
export { UmbImagingCropMode };
-export interface UmbImagingModel {
+export interface UmbImagingResizeModel {
height?: number;
width?: number;
mode?: UmbImagingCropMode;
}
+
+/**
+ * @deprecated use `UmbImagingResizeModel` instead
+ */
+// eslint-disable-next-line @typescript-eslint/no-empty-object-type
+export interface UmbImagingModel extends UmbImagingResizeModel {}