Skip to content

Commit c4ab3bd

Browse files
authored
Merge pull request #18108 from umbraco/v15/bugfix/17372
Fix: Mandatory for Image Cropper (17372)
2 parents f1e69dd + f7f0891 commit c4ab3bd

File tree

2 files changed

+75
-48
lines changed

2 files changed

+75
-48
lines changed

src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-image-cropper/input-image-cropper.element.ts

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import './image-cropper.element.js';
1212
import './image-cropper-focus-setter.element.js';
1313
import './image-cropper-preview.element.js';
1414
import './image-cropper-field.element.js';
15+
import { UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
1516

1617
const DefaultFocalPoint = { left: 0.5, top: 0.5 };
1718
const DefaultValue = {
@@ -22,12 +23,23 @@ const DefaultValue = {
2223
};
2324

2425
@customElement('umb-input-image-cropper')
25-
export class UmbInputImageCropperElement extends UmbLitElement {
26+
export class UmbInputImageCropperElement extends UmbFormControlMixin<
27+
UmbImageCropperPropertyEditorValue,
28+
typeof UmbLitElement,
29+
undefined
30+
>(UmbLitElement, undefined) {
2631
@query('#dropzone')
2732
private _dropzone?: UUIFileDropzoneElement;
2833

29-
@property({ attribute: false })
30-
value: UmbImageCropperPropertyEditorValue = DefaultValue;
34+
/**
35+
* Sets the input to required, meaning validation will fail if the value is empty.
36+
* @type {boolean}
37+
*/
38+
@property({ type: Boolean })
39+
required?: boolean;
40+
41+
@property({ type: String })
42+
requiredMessage?: string;
3143

3244
@property({ attribute: false })
3345
crops: UmbImageCropperPropertyEditorValue['crops'] = [];
@@ -43,6 +55,14 @@ export class UmbInputImageCropperElement extends UmbLitElement {
4355
constructor() {
4456
super();
4557
this.#manager = new UmbTemporaryFileManager(this);
58+
59+
this.addValidator(
60+
'valueMissing',
61+
() => this.requiredMessage ?? UMB_VALIDATION_EMPTY_LOCALIZATION_KEY,
62+
() => {
63+
return !!this.required && (!this.value || (this.value.src === '' && this.value.temporaryFileId == null));
64+
},
65+
);
4666
}
4767

4868
protected override firstUpdated(): void {
@@ -57,7 +77,7 @@ export class UmbInputImageCropperElement extends UmbLitElement {
5777
this.file = file;
5878
this.fileUnique = unique;
5979

60-
this.value = assignToFrozenObject(this.value, { temporaryFileId: unique });
80+
this.value = assignToFrozenObject(this.value ?? DefaultValue, { temporaryFileId: unique });
6181

6282
this.#manager?.uploadOne({ temporaryUnique: unique, file });
6383

@@ -71,7 +91,7 @@ export class UmbInputImageCropperElement extends UmbLitElement {
7191
}
7292

7393
#onRemove = () => {
74-
this.value = assignToFrozenObject(this.value, DefaultValue);
94+
this.value = undefined;
7595
if (this.fileUnique) {
7696
this.#manager?.removeOne(this.fileUnique);
7797
}
@@ -82,25 +102,27 @@ export class UmbInputImageCropperElement extends UmbLitElement {
82102
};
83103

84104
#mergeCrops() {
85-
// Replace crops from the value with the crops from the config while keeping the coordinates from the value if they exist.
86-
const filteredCrops = this.crops.map((crop) => {
87-
const cropFromValue = this.value.crops.find((valueCrop) => valueCrop.alias === crop.alias);
88-
const result = {
89-
...crop,
90-
coordinates: cropFromValue?.coordinates ?? undefined,
105+
if (this.value) {
106+
// Replace crops from the value with the crops from the config while keeping the coordinates from the value if they exist.
107+
const filteredCrops = this.crops.map((crop) => {
108+
const cropFromValue = this.value!.crops.find((valueCrop) => valueCrop.alias === crop.alias);
109+
const result = {
110+
...crop,
111+
coordinates: cropFromValue?.coordinates ?? undefined,
112+
};
113+
114+
return result;
115+
});
116+
117+
this.value = {
118+
...this.value,
119+
crops: filteredCrops,
91120
};
92-
93-
return result;
94-
});
95-
96-
this.value = {
97-
...this.value,
98-
crops: filteredCrops,
99-
};
121+
}
100122
}
101123

102124
override render() {
103-
if (this.value.src || this.file) {
125+
if (this.value?.src || this.file) {
104126
return this.#renderImageCropper();
105127
}
106128

@@ -119,7 +141,7 @@ export class UmbInputImageCropperElement extends UmbLitElement {
119141
const value = (e.target as UmbInputImageCropperFieldElement).value;
120142

121143
if (!value) {
122-
this.value = DefaultValue;
144+
this.value = undefined;
123145
this.dispatchEvent(new UmbChangeEvent());
124146
return;
125147
}
@@ -128,7 +150,9 @@ export class UmbInputImageCropperElement extends UmbLitElement {
128150
value.temporaryFileId = this.value.temporaryFileId;
129151
}
130152

131-
this.value = value;
153+
if (value.temporaryFileId || value.src !== '') {
154+
this.value = value;
155+
}
132156
this.dispatchEvent(new UmbChangeEvent());
133157
}
134158

src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-cropper/property-editor-ui-image-cropper.element.ts

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,62 @@
11
import type { UmbImageCropperPropertyEditorValue, UmbInputImageCropperElement } from '../../components/index.js';
2-
import { html, customElement, property, nothing, state } from '@umbraco-cms/backoffice/external/lit';
2+
import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
33
import {
44
type UmbPropertyEditorUiElement,
55
UmbPropertyValueChangeEvent,
66
type UmbPropertyEditorConfigCollection,
77
} from '@umbraco-cms/backoffice/property-editor';
88
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
9+
import { UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
10+
911
import '../../components/input-image-cropper/input-image-cropper.element.js';
1012

1113
/**
1214
* @element umb-property-editor-ui-image-cropper
1315
*/
1416
@customElement('umb-property-editor-ui-image-cropper')
15-
export class UmbPropertyEditorUIImageCropperElement extends UmbLitElement implements UmbPropertyEditorUiElement {
16-
@property({ attribute: false })
17-
value: UmbImageCropperPropertyEditorValue = {
18-
temporaryFileId: null,
19-
src: '',
20-
crops: [],
21-
focalPoint: { left: 0.5, top: 0.5 },
22-
};
17+
export class UmbPropertyEditorUIImageCropperElement
18+
extends UmbFormControlMixin<UmbImageCropperPropertyEditorValue | undefined, typeof UmbLitElement, undefined>(
19+
UmbLitElement,
20+
)
21+
implements UmbPropertyEditorUiElement
22+
{
23+
/**
24+
* Sets the input to mandatory, meaning validation will fail if the value is empty.
25+
* @type {boolean}
26+
*/
27+
@property({ type: Boolean })
28+
mandatory?: boolean;
29+
30+
@property({ type: String })
31+
mandatoryMessage = UMB_VALIDATION_EMPTY_LOCALIZATION_KEY;
2332

2433
@state()
2534
crops: UmbImageCropperPropertyEditorValue['crops'] = [];
2635

27-
override updated(changedProperties: Map<string | number | symbol, unknown>) {
28-
super.updated(changedProperties);
29-
if (changedProperties.has('value')) {
30-
if (!this.value) {
31-
this.value = {
32-
temporaryFileId: null,
33-
src: '',
34-
crops: [],
35-
focalPoint: { left: 0.5, top: 0.5 },
36-
};
37-
}
38-
}
39-
}
40-
4136
public set config(config: UmbPropertyEditorConfigCollection | undefined) {
4237
this.crops = config?.getValueByAlias<UmbImageCropperPropertyEditorValue['crops']>('crops') ?? [];
4338
}
4439

40+
override firstUpdated() {
41+
this.addFormControlElement(this.shadowRoot!.querySelector('umb-input-image-cropper')!);
42+
}
43+
44+
override focus() {
45+
return this.shadowRoot?.querySelector('umb-input-image-cropper')?.focus();
46+
}
47+
4548
#onChange(e: Event) {
4649
this.value = (e.target as UmbInputImageCropperElement).value;
4750
this.dispatchEvent(new UmbPropertyValueChangeEvent());
4851
}
4952

5053
override render() {
51-
if (!this.value) return nothing;
52-
5354
return html`<umb-input-image-cropper
5455
@change=${this.#onChange}
5556
.value=${this.value}
56-
.crops=${this.crops}></umb-input-image-cropper>`;
57+
.crops=${this.crops}
58+
.required=${this.mandatory}
59+
.requiredMessage=${this.mandatoryMessage}></umb-input-image-cropper>`;
5760
}
5861
}
5962

0 commit comments

Comments
 (0)