From c6f11ab4b2a7999411a3bd9bd3b179ab1abb6211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 24 Jan 2025 11:30:47 +0100 Subject: [PATCH 1/3] implement validation for mandatory --- .../input-image-cropper.element.ts | 68 +++++++++++++------ ...roperty-editor-ui-image-cropper.element.ts | 53 ++++++++------- 2 files changed, 74 insertions(+), 47 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/input-image-cropper.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/input-image-cropper.element.ts index beafa144fc..31f916001b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/input-image-cropper.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/input-image-cropper.element.ts @@ -12,6 +12,7 @@ import './image-cropper.element.js'; import './image-cropper-focus-setter.element.js'; import './image-cropper-preview.element.js'; import './image-cropper-field.element.js'; +import { UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; const DefaultFocalPoint = { left: 0.5, top: 0.5 }; const DefaultValue = { @@ -22,12 +23,23 @@ const DefaultValue = { }; @customElement('umb-input-image-cropper') -export class UmbInputImageCropperElement extends UmbLitElement { +export class UmbInputImageCropperElement extends UmbFormControlMixin< + UmbImageCropperPropertyEditorValue, + typeof UmbLitElement, + undefined +>(UmbLitElement, undefined) { @query('#dropzone') private _dropzone?: UUIFileDropzoneElement; - @property({ attribute: false }) - value: UmbImageCropperPropertyEditorValue = DefaultValue; + /** + * Sets the input to required, meaning validation will fail if the value is empty. + * @type {boolean} + */ + @property({ type: Boolean }) + required?: boolean; + + @property({ type: String }) + requiredMessage?: string; @property({ attribute: false }) crops: UmbImageCropperPropertyEditorValue['crops'] = []; @@ -43,6 +55,14 @@ export class UmbInputImageCropperElement extends UmbLitElement { constructor() { super(); this.#manager = new UmbTemporaryFileManager(this); + + this.addValidator( + 'valueMissing', + () => this.requiredMessage ?? UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, + () => { + return !this.value || (this.value.src === '' && this.value.temporaryFileId == null); + }, + ); } protected override firstUpdated(): void { @@ -57,7 +77,7 @@ export class UmbInputImageCropperElement extends UmbLitElement { this.file = file; this.fileUnique = unique; - this.value = assignToFrozenObject(this.value, { temporaryFileId: unique }); + this.value = assignToFrozenObject(this.value ?? DefaultValue, { temporaryFileId: unique }); this.#manager?.uploadOne({ temporaryUnique: unique, file }); @@ -71,7 +91,7 @@ export class UmbInputImageCropperElement extends UmbLitElement { } #onRemove = () => { - this.value = assignToFrozenObject(this.value, DefaultValue); + this.value = undefined; if (this.fileUnique) { this.#manager?.removeOne(this.fileUnique); } @@ -82,25 +102,27 @@ export class UmbInputImageCropperElement extends UmbLitElement { }; #mergeCrops() { - // Replace crops from the value with the crops from the config while keeping the coordinates from the value if they exist. - const filteredCrops = this.crops.map((crop) => { - const cropFromValue = this.value.crops.find((valueCrop) => valueCrop.alias === crop.alias); - const result = { - ...crop, - coordinates: cropFromValue?.coordinates ?? undefined, + if (this.value) { + // Replace crops from the value with the crops from the config while keeping the coordinates from the value if they exist. + const filteredCrops = this.crops.map((crop) => { + const cropFromValue = this.value!.crops.find((valueCrop) => valueCrop.alias === crop.alias); + const result = { + ...crop, + coordinates: cropFromValue?.coordinates ?? undefined, + }; + + return result; + }); + + this.value = { + ...this.value, + crops: filteredCrops, }; - - return result; - }); - - this.value = { - ...this.value, - crops: filteredCrops, - }; + } } override render() { - if (this.value.src || this.file) { + if (this.value?.src || this.file) { return this.#renderImageCropper(); } @@ -119,7 +141,7 @@ export class UmbInputImageCropperElement extends UmbLitElement { const value = (e.target as UmbInputImageCropperFieldElement).value; if (!value) { - this.value = DefaultValue; + this.value = undefined; this.dispatchEvent(new UmbChangeEvent()); return; } @@ -128,7 +150,9 @@ export class UmbInputImageCropperElement extends UmbLitElement { value.temporaryFileId = this.value.temporaryFileId; } - this.value = value; + if (value.temporaryFileId || value.src !== '') { + this.value = value; + } this.dispatchEvent(new UmbChangeEvent()); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-cropper/property-editor-ui-image-cropper.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-cropper/property-editor-ui-image-cropper.element.ts index b7f7ea2acc..12d589e288 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-cropper/property-editor-ui-image-cropper.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-cropper/property-editor-ui-image-cropper.element.ts @@ -6,54 +6,57 @@ import { type UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; + import '../../components/input-image-cropper/input-image-cropper.element.js'; /** * @element umb-property-editor-ui-image-cropper */ @customElement('umb-property-editor-ui-image-cropper') -export class UmbPropertyEditorUIImageCropperElement extends UmbLitElement implements UmbPropertyEditorUiElement { - @property({ attribute: false }) - value: UmbImageCropperPropertyEditorValue = { - temporaryFileId: null, - src: '', - crops: [], - focalPoint: { left: 0.5, top: 0.5 }, - }; +export class UmbPropertyEditorUIImageCropperElement + extends UmbFormControlMixin( + UmbLitElement, + ) + implements UmbPropertyEditorUiElement +{ + /** + * Sets the input to mandatory, meaning validation will fail if the value is empty. + * @type {boolean} + */ + @property({ type: Boolean }) + mandatory?: boolean; + + @property({ type: String }) + mandatoryMessage = UMB_VALIDATION_EMPTY_LOCALIZATION_KEY; @state() crops: UmbImageCropperPropertyEditorValue['crops'] = []; - override updated(changedProperties: Map) { - super.updated(changedProperties); - if (changedProperties.has('value')) { - if (!this.value) { - this.value = { - temporaryFileId: null, - src: '', - crops: [], - focalPoint: { left: 0.5, top: 0.5 }, - }; - } - } - } - public set config(config: UmbPropertyEditorConfigCollection | undefined) { this.crops = config?.getValueByAlias('crops') ?? []; } + override firstUpdated() { + this.addFormControlElement(this.shadowRoot!.querySelector('umb-input-image-cropper')!); + } + + override focus() { + return this.shadowRoot?.querySelector('umb-input-image-cropper')?.focus(); + } + #onChange(e: Event) { this.value = (e.target as UmbInputImageCropperElement).value; this.dispatchEvent(new UmbPropertyValueChangeEvent()); } override render() { - if (!this.value) return nothing; - return html``; + .crops=${this.crops} + .required=${this.mandatory} + .requiredMessage=${this.mandatoryMessage}>`; } } From 677a8594df33b708625b2ab10377d7c4fe6b97fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 28 Jan 2025 08:15:07 +0100 Subject: [PATCH 2/3] only check valueMissing if required --- .../input-image-cropper/input-image-cropper.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/input-image-cropper.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/input-image-cropper.element.ts index 31f916001b..19823f1b47 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/input-image-cropper.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/input-image-cropper.element.ts @@ -60,7 +60,7 @@ export class UmbInputImageCropperElement extends UmbFormControlMixin< 'valueMissing', () => this.requiredMessage ?? UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, () => { - return !this.value || (this.value.src === '' && this.value.temporaryFileId == null); + return !!this.required && (!this.value || (this.value.src === '' && this.value.temporaryFileId == null)); }, ); } From f7f0891d3b88acd971c6cb71bbef72640720783f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 28 Jan 2025 09:26:07 +0100 Subject: [PATCH 3/3] remove nothing import --- .../image-cropper/property-editor-ui-image-cropper.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-cropper/property-editor-ui-image-cropper.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-cropper/property-editor-ui-image-cropper.element.ts index 12d589e288..dc2eed16e8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-cropper/property-editor-ui-image-cropper.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-cropper/property-editor-ui-image-cropper.element.ts @@ -1,5 +1,5 @@ import type { UmbImageCropperPropertyEditorValue, UmbInputImageCropperElement } from '../../components/index.js'; -import { html, customElement, property, nothing, state } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { type UmbPropertyEditorUiElement, UmbPropertyValueChangeEvent,