Get the media image parameters of height, width and other properties in custom block Grid preview

Hi,
Any help or feedback on this would be most appreciated.

I am now working on a custom Block Grid preview for a block grid to display in the CMS where this block grid is displayed. It is a shame that Umbraco 15 does not, as previous versions have, include a set of defualt block grid views and block grid previews as examples.

Anyway, I have found no documentation in the custom block grid tutorial that includes displaying an image and it’s properties.

I have used this tutorial on Medium as a starting point:
Umbraco 14 custom views

Using this, I first had to change UmbMediaPickerPropertyValue to UmbMediaPickerPropertyValueEntry as it has become deprecated.

Here is the code of the custom block grid view as it stands:

import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { PlTextStyles } from '../../styles/site.pl.styles';
import type { UmbBlockEditorCustomViewElement } from '@umbraco-cms/backoffice/block-custom-view';
import { UmbLitElement } from "@umbraco-cms/backoffice/lit-element";
import { html, customElement, property, css, state} from '@umbraco-cms/backoffice/external/lit';
import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';
import type { UmbBlockDataType } from '@umbraco-cms/backoffice/block';
import { UmbBlockGridTypeModel } from '@umbraco-cms/backoffice/block-grid';
// for images
import { UmbImagingRepository } from '@umbraco-cms/backoffice/imaging'; // Import the repository
import type { UmbMediaPickerPropertyValueEntry } from '@umbraco-cms/backoffice/media';


const elementName = "image-block";

// eslint-disable-next-line local-rules/enforce-umb-prefix-on-element-name
@customElement(elementName)
// eslint-disable-next-line local-rules/umb-class-prefix
export class ImageBlockCustomView
	extends UmbElementMixin(UmbLitElement)
	implements UmbBlockEditorCustomViewElement
{

	@property({ attribute: false })
	content?: UmbBlockDataType;

	@property({ attribute: false })
	settings?: UmbBlockDataType;

	@state()
	blockType?: UmbBlockGridTypeModel;

	@state()
	images: UmbMediaPickerPropertyValueEntry[] = [];

	@state()
	imageUrls: Map<string, string> = new Map();

	#imagingRepository = new UmbImagingRepository(this);


	async updated(changedProperties: Map<string | number | symbol, unknown>)
	{
		super.updated(changedProperties);
		if (changedProperties.has('content') && this.content?.image)
		{
			console.log("Images ITEM IMAGE :: : ", this.content?.image);
			this.images = this.content.image as UmbMediaPickerPropertyValueEntry[];

			console.log("Images ITEMS: ", this.images);
			await this.fetchLogoUrls();
		}
	}

	async fetchLogoUrls()
	{
		for (const imageItem of this.images)
		{
			
			if (imageItem.mediaKey && !this.imageUrls.has(imageItem.mediaKey))
			{
				const url = await this.resolveMediaUrl(imageItem.mediaKey);
				if (url)
				{
					this.imageUrls.set(imageItem.mediaKey, url);
					this.requestUpdate(); // Trigger a re-render to update URLs
				}
			}
		}
	}

	async resolveMediaUrl(mediaKey: string): Promise<string>
	{
		const { data } = await this.#imagingRepository.requestThumbnailUrls([mediaKey], 0, 0);
		console.log("IMAGE DATA::::: ", data?.[0]);
		return data?.[0]?.url ?? '';
	}

	override render()
	{
		console.log("IMAGE content:: ", this.content);
		console.log("IMAGE settings:: ", this.settings);
		return html`
			<h5>My Custom View</h5>
         <div class="logo-container">
             ${this.images.map((imageItem: UmbMediaPickerPropertyValueEntry) => html`
                 <img src="${this.imageUrls.get(imageItem.mediaKey) || ''}" alt="Logo" />
             `)}
         </div>
		`;
	}

	static override styles = [
		UmbTextStyles,
		PlTextStyles,
		css`
			:host {
                display: block;
                height: 100%;
                box-sizing: border-box;
                border-radius: 9px;
                padding: 12px;
            }
		`,
	];
}

export default ImageBlockCustomView;

declare global
{
	interface HTMLElementTagNameMap
	{
		[elementName]: ImageBlockCustomView;
	}
}

What I am having trouble with is accessing the actual height and with properties of the image. I also aim to add an alternative text field to the image media type, and wish to be able to display this as well, if needed in the block grid preview.

Please can any one assist and point me in the right direction with this?

Thank you,

Kind regards,

Pete

Hey @PeatLaur! It’s a little sidestep away from what you’re asking about, but I created a package called BlockPreview that saves you the effort of having to build custom previews for every block. Might be of use to you!

I can recommend using the <umb-imaging-thumbnail /> element to render images. It supports width, height, and crop mode, and it handles the whole requestThumbnailUrls from the repository. I think you can shave most of your block view away:

import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { PlTextStyles } from '../../styles/site.pl.styles';
import type { UmbBlockEditorCustomViewElement } from '@umbraco-cms/backoffice/block-custom-view';
import { UmbLitElement } from "@umbraco-cms/backoffice/lit-element";
import { html, customElement, property, css, state, repeat} from '@umbraco-cms/backoffice/external/lit';
import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';
import type { UmbBlockDataType } from '@umbraco-cms/backoffice/block';
import { UmbBlockGridTypeModel } from '@umbraco-cms/backoffice/block-grid';
// for images
import type { UmbMediaPickerPropertyValueEntry } from '@umbraco-cms/backoffice/media';

interface MyBlockContent extends UmbBlockDataType {
    image?: Array<UmbMediaPickerPropertyValueEntry>;
}

const elementName = "image-block";

// eslint-disable-next-line local-rules/enforce-umb-prefix-on-element-name
@customElement(elementName)
// eslint-disable-next-line local-rules/umb-class-prefix
export class ImageBlockCustomView
    extends UmbElementMixin(UmbLitElement)
    implements UmbBlockEditorCustomViewElement
{

    @property({ attribute: false })
    content?: MyBlockContent;

    @property({ attribute: false })
    settings?: UmbBlockDataType;

    @state()
    blockType?: UmbBlockGridTypeModel;

    override render() {
        return html`
            <h5>My Custom View</h5>
            ${repeat(
                this.content?.image ?? [], 
                (image) => image.key,
                (image) => 
                    html`<umb-imaging-thumbnail class="thumbnail" width="300" height="300" mode="crop" unique=${image.mediaKey} alt="Logo"></umb-imaging-thumbnail>`
            )}
        `;
    }

    static override styles = [
        UmbTextStyles,
        PlTextStyles,
        css`
	    :host {
                display: block;
                height: 100%;
                box-sizing: border-box;
                border-radius: 9px;
                padding: 12px;
            }

            .thumbnail {
                max-width: 300px;
                max-height: 300px;
            }
		`,
    ];
}

export default ImageBlockCustomView;

declare global
{
    interface HTMLElementTagNameMap
    {
        [elementName]: ImageBlockCustomView;
    }
}

Notice how a custom interface resolves the image property and that we are passing different parameters to the imaging element.