Merge pull request #18108 from umbraco/v15/bugfix/17372

Fix: Mandatory for Image Cropper (17372)
This commit is contained in:
Niels Lyngsø
2025-01-28 10:13:41 +01:00
committed by GitHub
2 changed files with 75 additions and 48 deletions

View File

@@ -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.required && (!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());
}

View File

@@ -1,59 +1,62 @@
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,
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<UmbImageCropperPropertyEditorValue | undefined, typeof UmbLitElement, undefined>(
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<string | number | symbol, unknown>) {
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<UmbImageCropperPropertyEditorValue['crops']>('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`<umb-input-image-cropper
@change=${this.#onChange}
.value=${this.value}
.crops=${this.crops}></umb-input-image-cropper>`;
.crops=${this.crops}
.required=${this.mandatory}
.requiredMessage=${this.mandatoryMessage}></umb-input-image-cropper>`;
}
}