1
- import { UmbMediaItemRepository } from '../../repository/index.js' ;
2
1
import { UMB_IMAGE_CROPPER_EDITOR_MODAL , UMB_MEDIA_PICKER_MODAL } from '../../modals/index.js' ;
3
2
import type { UmbMediaItemModel , UmbCropModel , UmbMediaPickerPropertyValueEntry } from '../../types.js' ;
4
3
import type { UmbUploadableItem } from '../../dropzone/types.js' ;
@@ -9,12 +8,13 @@ import { UmbId } from '@umbraco-cms/backoffice/id';
9
8
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element' ;
10
9
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router' ;
11
10
import { UmbSorterController , UmbSorterResolvePlacementAsGrid } from '@umbraco-cms/backoffice/sorter' ;
12
- import { UUIFormControlMixin } from '@umbraco-cms/backoffice/external/uui' ;
13
- import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal' ;
14
11
import type { UmbModalRouteBuilder } from '@umbraco-cms/backoffice/router' ;
15
12
import type { UmbVariantId } from '@umbraco-cms/backoffice/variant' ;
16
13
17
14
import '@umbraco-cms/backoffice/imaging' ;
15
+ import { UMB_VALIDATION_EMPTY_LOCALIZATION_KEY , UmbFormControlMixin } from '@umbraco-cms/backoffice/validation' ;
16
+ import { UmbRepositoryItemsManager } from '@umbraco-cms/backoffice/repository' ;
17
+ import { UMB_MEDIA_ITEM_REPOSITORY_ALIAS } from '@umbraco-cms/backoffice/media' ;
18
18
19
19
type UmbRichMediaCardModel = {
20
20
unique : string ;
@@ -26,7 +26,11 @@ type UmbRichMediaCardModel = {
26
26
} ;
27
27
28
28
@customElement ( 'umb-input-rich-media' )
29
- export class UmbInputRichMediaElement extends UUIFormControlMixin ( UmbLitElement , '' ) {
29
+ export class UmbInputRichMediaElement extends UmbFormControlMixin <
30
+ Array < UmbMediaPickerPropertyValueEntry > ,
31
+ typeof UmbLitElement ,
32
+ undefined
33
+ > ( UmbLitElement , undefined ) {
30
34
#sorter = new UmbSorterController < UmbMediaPickerPropertyValueEntry > ( this , {
31
35
getUniqueOfElement : ( element ) => {
32
36
return element . id ;
@@ -37,24 +41,22 @@ export class UmbInputRichMediaElement extends UUIFormControlMixin(UmbLitElement,
37
41
identifier : 'Umb.SorterIdentifier.InputRichMedia' ,
38
42
itemSelector : 'uui-card-media' ,
39
43
containerSelector : '.container' ,
40
- //resolvePlacement: (args) => args.pointerX < args.relatedRect.left + args.relatedRect.width * 0.5,
41
44
resolvePlacement : UmbSorterResolvePlacementAsGrid ,
42
45
onChange : ( { model } ) => {
43
- this . #items = model ;
44
- this . #sortCards( model ) ;
46
+ this . value = model ;
45
47
this . dispatchEvent ( new UmbChangeEvent ( ) ) ;
46
48
} ,
47
49
} ) ;
48
50
49
- #sortCards( model : Array < UmbMediaPickerPropertyValueEntry > ) {
50
- const idToIndexMap : { [ unique : string ] : number } = { } ;
51
- model . forEach ( ( item , index ) => {
52
- idToIndexMap [ item . key ] = index ;
53
- } ) ;
51
+ /**
52
+ * Sets the input to required, meaning validation will fail if the value is empty.
53
+ * @type {boolean }
54
+ */
55
+ @property ( { type : Boolean } )
56
+ required ?: boolean ;
54
57
55
- const cards = [ ...this . _cards ] ;
56
- this . _cards = cards . sort ( ( a , b ) => idToIndexMap [ a . unique ] - idToIndexMap [ b . unique ] ) ;
57
- }
58
+ @property ( { type : String } )
59
+ requiredMessage ?: string ;
58
60
59
61
/**
60
62
* This is a minimum amount of selected items in this input.
@@ -93,15 +95,16 @@ export class UmbInputRichMediaElement extends UUIFormControlMixin(UmbLitElement,
93
95
maxMessage = 'This field exceeds the allowed amount of items' ;
94
96
95
97
@property ( { type : Array } )
96
- public set items ( value : Array < UmbMediaPickerPropertyValueEntry > ) {
98
+ public override set value ( value : Array < UmbMediaPickerPropertyValueEntry > | undefined ) {
99
+ super . value = value ;
97
100
this . #sorter. setModel ( value ) ;
98
- this . #items = value ;
101
+ this . #itemManager. setUniques ( value ?. map ( ( x ) => x . mediaKey ) ) ;
102
+ // Maybe the new value is using an existing media, and there we need to update the cards despite no repository update.
99
103
this . #populateCards( ) ;
100
104
}
101
- public get items ( ) : Array < UmbMediaPickerPropertyValueEntry > {
102
- return this . #items ;
105
+ public override get value ( ) : Array < UmbMediaPickerPropertyValueEntry > | undefined {
106
+ return super . value ;
103
107
}
104
- #items: Array < UmbMediaPickerPropertyValueEntry > = [ ] ;
105
108
106
109
@property ( { type : Array } )
107
110
allowedContentTypeIds ?: string [ ] | undefined ;
@@ -112,11 +115,6 @@ export class UmbInputRichMediaElement extends UUIFormControlMixin(UmbLitElement,
112
115
@property ( { type : Boolean } )
113
116
multiple = false ;
114
117
115
- @property ( )
116
- public override get value ( ) {
117
- return this . items ?. map ( ( item ) => item . mediaKey ) . join ( ',' ) ;
118
- }
119
-
120
118
@property ( { type : Array } )
121
119
public preselectedCrops ?: Array < UmbCropModel > ;
122
120
@@ -174,15 +172,17 @@ export class UmbInputRichMediaElement extends UUIFormControlMixin(UmbLitElement,
174
172
@state ( )
175
173
private _routeBuilder ?: UmbModalRouteBuilder ;
176
174
177
- #itemRepository = new UmbMediaItemRepository ( this ) ;
178
-
179
- #modalManager?: UmbModalManagerContext ;
175
+ readonly #itemManager = new UmbRepositoryItemsManager < UmbMediaItemModel > (
176
+ this ,
177
+ UMB_MEDIA_ITEM_REPOSITORY_ALIAS ,
178
+ ( x ) => x . unique ,
179
+ ) ;
180
180
181
181
constructor ( ) {
182
182
super ( ) ;
183
183
184
- this . consumeContext ( UMB_MODAL_MANAGER_CONTEXT , ( instance ) => {
185
- this . #modalManager = instance ;
184
+ this . observe ( this . #itemManager . items , ( ) => {
185
+ this . #populateCards ( ) ;
186
186
} ) ;
187
187
188
188
new UmbModalRouteRegistrationController ( this , UMB_IMAGE_CROPPER_EDITOR_MODAL )
@@ -191,7 +191,7 @@ export class UmbInputRichMediaElement extends UUIFormControlMixin(UmbLitElement,
191
191
const key = params . key ;
192
192
if ( ! key ) return false ;
193
193
194
- const item = this . items . find ( ( item ) => item . key === key ) ;
194
+ const item = this . value ? .find ( ( item ) => item . key === key ) ;
195
195
if ( ! item ) return false ;
196
196
197
197
return {
@@ -212,7 +212,7 @@ export class UmbInputRichMediaElement extends UUIFormControlMixin(UmbLitElement,
212
212
} ;
213
213
} )
214
214
. onSubmit ( ( value ) => {
215
- this . items = this . items . map ( ( item ) => {
215
+ this . value = this . value ? .map ( ( item ) => {
216
216
if ( item . key !== value . key ) return item ;
217
217
218
218
const focalPoint = this . focalPointEnabled ? value . focalPoint : null ;
@@ -231,15 +231,30 @@ export class UmbInputRichMediaElement extends UUIFormControlMixin(UmbLitElement,
231
231
this . _routeBuilder = routeBuilder ;
232
232
} ) ;
233
233
234
+ this . addValidator (
235
+ 'valueMissing' ,
236
+ ( ) => this . requiredMessage ?? UMB_VALIDATION_EMPTY_LOCALIZATION_KEY ,
237
+ ( ) => {
238
+ return ! this . readonly && ! ! this . required && ( ! this . value || this . value . length === 0 ) ;
239
+ } ,
240
+ ) ;
241
+
234
242
this . addValidator (
235
243
'rangeUnderflow' ,
236
244
( ) => this . minMessage ,
237
- ( ) => ! ! this . min && this . items ?. length < this . min ,
245
+ ( ) =>
246
+ ! this . readonly &&
247
+ // Only if min is set:
248
+ ! ! this . min &&
249
+ // if the value is empty and not required, we should not validate the min:
250
+ ! ( this . value ?. length === 0 && this . required == false ) &&
251
+ // Validate the min:
252
+ ( this . value ?. length ?? 0 ) < this . min ,
238
253
) ;
239
254
this . addValidator (
240
255
'rangeOverflow' ,
241
256
( ) => this . maxMessage ,
242
- ( ) => ! ! this . max && this . items ?. length > this . max ,
257
+ ( ) => ! this . readonly && ! ! this . value && ! ! this . max && this . value ?. length > this . max ,
243
258
) ;
244
259
}
245
260
@@ -248,28 +263,29 @@ export class UmbInputRichMediaElement extends UUIFormControlMixin(UmbLitElement,
248
263
}
249
264
250
265
async #populateCards( ) {
251
- const missingCards = this . items . filter ( ( item ) => ! this . _cards . find ( ( card ) => card . unique === item . key ) ) ;
252
- if ( ! missingCards . length ) return ;
266
+ const mediaItems = this . #itemManager. getItems ( ) ;
253
267
254
- if ( ! this . items ? .length ) {
268
+ if ( ! mediaItems . length ) {
255
269
this . _cards = [ ] ;
256
270
return ;
257
271
}
258
-
259
- const uniques = this . items . map ( ( item ) => item . mediaKey ) ;
260
-
261
- const { data : items } = await this . #itemRepository. requestItems ( uniques ) ;
262
-
263
- this . _cards = this . items . map ( ( item ) => {
264
- const media = items ?. find ( ( x ) => x . unique === item . mediaKey ) ;
265
- return {
266
- unique : item . key ,
267
- media : item . mediaKey ,
268
- name : media ?. name ?? '' ,
269
- icon : media ?. mediaType ?. icon ,
270
- isTrashed : media ?. isTrashed ?? false ,
271
- } ;
272
- } ) ;
272
+ // Check if all media items is loaded.
273
+ // But notice, it would be nicer UX if we could show a loading state on the cards that are missing(loading) their items.
274
+ const missingCards = mediaItems . filter ( ( item ) => ! this . _cards . find ( ( card ) => card . unique === item . unique ) ) ;
275
+ const removedCards = this . _cards . filter ( ( card ) => ! mediaItems . find ( ( item ) => card . unique === item . unique ) ) ;
276
+ if ( missingCards . length === 0 && removedCards . length === 0 ) return ;
277
+
278
+ this . _cards =
279
+ this . value ?. map ( ( item ) => {
280
+ const media = mediaItems . find ( ( x ) => x . unique === item . mediaKey ) ;
281
+ return {
282
+ unique : item . key ,
283
+ media : item . mediaKey ,
284
+ name : media ?. name ?? '' ,
285
+ icon : media ?. mediaType ?. icon ,
286
+ isTrashed : media ?. isTrashed ?? false ,
287
+ } ;
288
+ } ) ?? [ ] ;
273
289
}
274
290
275
291
#pickableFilter: ( item : UmbMediaItemModel ) => boolean = ( item ) => {
@@ -290,12 +306,13 @@ export class UmbInputRichMediaElement extends UUIFormControlMixin(UmbLitElement,
290
306
focalPoint : null ,
291
307
} ) ) ;
292
308
293
- this . #items = [ ...this . #items , ...additions ] ;
309
+ this . value = [ ...( this . value ?? [ ] ) , ...additions ] ;
294
310
this . dispatchEvent ( new UmbChangeEvent ( ) ) ;
295
311
}
296
312
297
313
async #openPicker( ) {
298
- const modalHandler = this . #modalManager?. open ( this , UMB_MEDIA_PICKER_MODAL , {
314
+ const modalManager = await this . getContext ( UMB_MODAL_MANAGER_CONTEXT ) ;
315
+ const modalHandler = modalManager ?. open ( this , UMB_MEDIA_PICKER_MODAL , {
299
316
data : {
300
317
multiple : this . multiple ,
301
318
startNode : this . startNode ,
@@ -319,8 +336,7 @@ export class UmbInputRichMediaElement extends UUIFormControlMixin(UmbLitElement,
319
336
confirmLabel : this . localize . term ( 'actions_remove' ) ,
320
337
} ) ;
321
338
322
- this . #items = this . #items. filter ( ( x ) => x . key !== item . unique ) ;
323
- this . _cards = this . _cards . filter ( ( x ) => x . unique !== item . unique ) ;
339
+ this . value = this . value ?. filter ( ( x ) => x . key !== item . unique ) ;
324
340
325
341
this . dispatchEvent ( new UmbChangeEvent ( ) ) ;
326
342
}
@@ -356,15 +372,18 @@ export class UmbInputRichMediaElement extends UUIFormControlMixin(UmbLitElement,
356
372
}
357
373
358
374
#renderAddButton( ) {
359
- // TODO: Stop preventing adding more, instead implement proper validation for user feedback. [NL]
360
- if ( ( this . _cards && this . max && this . _cards . length >= this . max ) || ( this . _cards . length && ! this . multiple ) ) return ;
375
+ if ( this . _cards && this . _cards . length && ! this . multiple ) return ;
361
376
if ( this . readonly && this . _cards . length > 0 ) {
362
377
return nothing ;
363
378
} else {
364
379
return html `
365
380
<uui- butto n
366
381
id= "btn-add"
367
382
look = "placeholder"
383
+ @blur = ${ ( ) => {
384
+ this . pristine = false ;
385
+ this . checkValidity ( ) ;
386
+ } }
368
387
@click = ${ this . #openPicker}
369
388
label= ${ this . localize . term ( 'general_choose' ) }
370
389
?dis abled= ${ this . readonly } >
0 commit comments