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,23 @@ 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 . 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
+ ( ) => ! this . readonly && ! ! this . min && ( this . value ?. length ?? 0 ) < this . min ,
238
246
) ;
239
247
this . addValidator (
240
248
'rangeOverflow' ,
241
249
( ) => this . maxMessage ,
242
- ( ) => ! ! this . max && this . items ?. length > this . max ,
250
+ ( ) => ! this . readonly && ! ! this . value && ! ! this . max && this . value ?. length > this . max ,
243
251
) ;
244
252
}
245
253
@@ -248,28 +256,28 @@ export class UmbInputRichMediaElement extends UUIFormControlMixin(UmbLitElement,
248
256
}
249
257
250
258
async #populateCards( ) {
251
- const missingCards = this . items . filter ( ( item ) => ! this . _cards . find ( ( card ) => card . unique === item . key ) ) ;
259
+ const mediaItems = this . #itemManager. getItems ( ) ;
260
+ // Check if all media items is loaded.
261
+ // But notice, it would be nicer UX if we could show a loading state on the cards that are missing(loading) their items.
262
+ const missingCards = mediaItems . filter ( ( item ) => ! this . _cards . find ( ( card ) => card . unique === item . unique ) ) ;
252
263
if ( ! missingCards . length ) return ;
253
264
254
- if ( ! this . items ? .length ) {
265
+ if ( ! mediaItems . length ) {
255
266
this . _cards = [ ] ;
256
267
return ;
257
268
}
258
269
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
- } ) ;
270
+ this . _cards =
271
+ this . value ?. map ( ( item ) => {
272
+ const media = mediaItems . find ( ( x ) => x . unique === item . mediaKey ) ;
273
+ return {
274
+ unique : item . key ,
275
+ media : item . mediaKey ,
276
+ name : media ?. name ?? '' ,
277
+ icon : media ?. mediaType ?. icon ,
278
+ isTrashed : media ?. isTrashed ?? false ,
279
+ } ;
280
+ } ) ?? [ ] ;
273
281
}
274
282
275
283
#pickableFilter: ( item : UmbMediaItemModel ) => boolean = ( item ) => {
@@ -290,12 +298,13 @@ export class UmbInputRichMediaElement extends UUIFormControlMixin(UmbLitElement,
290
298
focalPoint : null ,
291
299
} ) ) ;
292
300
293
- this . #items = [ ...this . #items , ...additions ] ;
301
+ this . value = [ ...( this . value ?? [ ] ) , ...additions ] ;
294
302
this . dispatchEvent ( new UmbChangeEvent ( ) ) ;
295
303
}
296
304
297
305
async #openPicker( ) {
298
- const modalHandler = this . #modalManager?. open ( this , UMB_MEDIA_PICKER_MODAL , {
306
+ const modalManager = await this . getContext ( UMB_MODAL_MANAGER_CONTEXT ) ;
307
+ const modalHandler = modalManager ?. open ( this , UMB_MEDIA_PICKER_MODAL , {
299
308
data : {
300
309
multiple : this . multiple ,
301
310
startNode : this . startNode ,
@@ -319,8 +328,7 @@ export class UmbInputRichMediaElement extends UUIFormControlMixin(UmbLitElement,
319
328
confirmLabel : this . localize . term ( 'actions_remove' ) ,
320
329
} ) ;
321
330
322
- this . #items = this . #items. filter ( ( x ) => x . key !== item . unique ) ;
323
- this . _cards = this . _cards . filter ( ( x ) => x . unique !== item . unique ) ;
331
+ this . value = this . value ?. filter ( ( x ) => x . key !== item . unique ) ;
324
332
325
333
this . dispatchEvent ( new UmbChangeEvent ( ) ) ;
326
334
}
@@ -356,15 +364,18 @@ export class UmbInputRichMediaElement extends UUIFormControlMixin(UmbLitElement,
356
364
}
357
365
358
366
#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 ;
367
+ if ( this . _cards && this . _cards . length && ! this . multiple ) return ;
361
368
if ( this . readonly && this . _cards . length > 0 ) {
362
369
return nothing ;
363
370
} else {
364
371
return html `
365
372
<uui- butto n
366
373
id= "btn-add"
367
374
look = "placeholder"
375
+ @blur = ${ ( ) => {
376
+ this . pristine = false ;
377
+ this . checkValidity ( ) ;
378
+ } }
368
379
@click = ${ this . #openPicker}
369
380
label= ${ this . localize . term ( 'general_choose' ) }
370
381
?dis abled= ${ this . readonly } >
0 commit comments