1
1
import { UMB_BLOCK_WORKSPACE_MODAL } from '../../workspace/index.js' ;
2
2
import { UMB_BLOCK_MANAGER_CONTEXT } from '../../context/index.js' ;
3
3
import type { UmbBlockCatalogueModalData , UmbBlockCatalogueModalValue } from './block-catalogue-modal.token.js' ;
4
- import type { UmbBlockTypeGroup , UmbBlockTypeWithGroupKey } from '@umbraco-cms/backoffice/block-type' ;
5
- import { css , html , customElement , state , repeat , nothing } from '@umbraco-cms/backoffice/external/lit' ;
6
- import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui' ;
7
- import { UMB_MODAL_CONTEXT , UmbModalBaseElement } from '@umbraco-cms/backoffice/modal' ;
4
+ import {
5
+ css ,
6
+ customElement ,
7
+ html ,
8
+ ifDefined ,
9
+ nothing ,
10
+ repeat ,
11
+ state ,
12
+ when ,
13
+ } from '@umbraco-cms/backoffice/external/lit' ;
14
+ import { transformServerPathToClientPath } from '@umbraco-cms/backoffice/utils' ;
8
15
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router' ;
16
+ import { UmbRepositoryItemsManager } from '@umbraco-cms/backoffice/repository' ;
17
+ import { UMB_DOCUMENT_TYPE_ITEM_REPOSITORY_ALIAS } from '@umbraco-cms/backoffice/document-type' ;
18
+ import { UMB_MODAL_CONTEXT , UmbModalBaseElement } from '@umbraco-cms/backoffice/modal' ;
19
+ import { UMB_SERVER_CONTEXT } from '@umbraco-cms/backoffice/server' ;
20
+ import type { UmbBlockTypeGroup , UmbBlockTypeWithGroupKey } from '@umbraco-cms/backoffice/block-type' ;
21
+ import type { UmbDocumentTypeItemModel } from '@umbraco-cms/backoffice/document-type' ;
9
22
import type { UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event' ;
23
+ import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui' ;
10
24
11
- // TODO: This is across packages, how should we go about getting just a single element from another package? like here we just need the umb-block-type-card element
12
- import '@umbraco-cms/backoffice/block-type' ;
25
+ type UmbBlockTypeItemWithGroupKey = UmbBlockTypeWithGroupKey & UmbDocumentTypeItemModel ;
13
26
14
27
@customElement ( 'umb-block-catalogue-modal' )
15
28
export class UmbBlockCatalogueModalElement extends UmbModalBaseElement <
16
29
UmbBlockCatalogueModalData ,
17
30
UmbBlockCatalogueModalValue
18
31
> {
32
+ readonly #itemManager = new UmbRepositoryItemsManager < UmbDocumentTypeItemModel > (
33
+ this ,
34
+ UMB_DOCUMENT_TYPE_ITEM_REPOSITORY_ALIAS ,
35
+ ) ;
36
+
19
37
#search = '' ;
20
38
21
- private _groupedBlocks : Array < { name ?: string ; blocks : Array < UmbBlockTypeWithGroupKey > } > = [ ] ;
39
+ #serverUrl = '' ;
40
+
41
+ private _groupedBlocks : Array < { name ?: string ; blocks : Array < UmbBlockTypeItemWithGroupKey > } > = [ ] ;
22
42
23
43
@state ( )
24
44
private _openClipboard ?: boolean ;
@@ -27,14 +47,21 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
27
47
private _workspacePath ?: string ;
28
48
29
49
@state ( )
30
- private _filtered : Array < { name ?: string ; blocks : Array < UmbBlockTypeWithGroupKey > } > = [ ] ;
50
+ private _filtered : Array < { name ?: string ; blocks : Array < UmbBlockTypeItemWithGroupKey > } > = [ ] ;
31
51
32
52
@state ( )
33
53
_manager ?: typeof UMB_BLOCK_MANAGER_CONTEXT . TYPE ;
34
54
55
+ @state ( )
56
+ _loading = true ;
57
+
35
58
constructor ( ) {
36
59
super ( ) ;
37
60
61
+ this . consumeContext ( UMB_SERVER_CONTEXT , ( instance ) => {
62
+ this . #serverUrl = instance ?. getServerUrl ( ) ?? '' ;
63
+ } ) ;
64
+
38
65
this . consumeContext ( UMB_MODAL_CONTEXT , ( modalContext ) => {
39
66
if ( modalContext ?. data . createBlockInWorkspace ) {
40
67
new UmbModalRouteRegistrationController ( this , UMB_BLOCK_WORKSPACE_MODAL )
@@ -57,6 +84,10 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
57
84
this . consumeContext ( UMB_BLOCK_MANAGER_CONTEXT , ( manager ) => {
58
85
this . _manager = manager ;
59
86
} ) ;
87
+
88
+ this . observe ( this . #itemManager. items , async ( items ) => {
89
+ this . #observeBlockTypes( items ) ;
90
+ } ) ;
60
91
}
61
92
62
93
override connectedCallback ( ) {
@@ -65,17 +96,37 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
65
96
66
97
this . _openClipboard = this . data . openClipboard ?? false ;
67
98
68
- const blocks : Array < UmbBlockTypeWithGroupKey > = this . data . blocks ?? [ ] ;
69
- const blockGroups : Array < UmbBlockTypeGroup > = this . data . blockGroups ?? [ ] ;
99
+ this . #itemManager. setUniques ( this . data . blocks . map ( ( block ) => block . contentElementTypeKey ) ) ;
100
+ }
101
+
102
+ #observeBlockTypes( items : Array < UmbDocumentTypeItemModel > | undefined ) {
103
+ if ( ! items ?. length ) return ;
104
+
105
+ const lookup = items . reduce (
106
+ ( acc , item ) => {
107
+ acc [ item . unique ] = item ;
108
+ return acc ;
109
+ } ,
110
+ { } as { [ key : string ] : UmbDocumentTypeItemModel } ,
111
+ ) ;
112
+
113
+ const blocks : Array < UmbBlockTypeItemWithGroupKey > =
114
+ this . data ?. blocks ?. map ( ( block ) => ( { ...( lookup [ block . contentElementTypeKey ] ?? { } ) , ...block } ) ) ?? [ ] ;
115
+
116
+ const blockGroups : Array < UmbBlockTypeGroup > = this . data ?. blockGroups ?? [ ] ;
70
117
71
118
const noGroupBlocks = blocks . filter ( ( block ) => ! blockGroups . find ( ( group ) => group . key === block . groupKey ) ) ;
119
+
72
120
const grouped = blockGroups . map ( ( group ) => ( {
73
121
name : group . name ,
74
122
blocks : blocks . filter ( ( block ) => block . groupKey === group . key ) ,
75
123
} ) ) ;
76
124
77
125
this . _groupedBlocks = [ { blocks : noGroupBlocks } , ...grouped ] ;
126
+
78
127
this . #updateFiltered( ) ;
128
+
129
+ this . _loading = false ;
79
130
}
80
131
81
132
#updateFiltered( ) {
@@ -84,7 +135,15 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
84
135
} else {
85
136
const search = this . #search. toLowerCase ( ) ;
86
137
this . _filtered = this . _groupedBlocks . map ( ( group ) => {
87
- return { ...group , blocks : group . blocks . filter ( ( block ) => block . label ?. toLocaleLowerCase ( ) . includes ( search ) ) } ;
138
+ return {
139
+ ...group ,
140
+ blocks : group . blocks . filter (
141
+ ( block ) =>
142
+ block . label ?. toLowerCase ( ) . includes ( search ) ||
143
+ block . name ?. toLowerCase ( ) . includes ( search ) ||
144
+ block . description ?. toLowerCase ( ) . includes ( search ) ,
145
+ ) ,
146
+ } ;
88
147
} ) ;
89
148
}
90
149
}
@@ -115,7 +174,7 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
115
174
116
175
override render ( ) {
117
176
return html `
118
- <umb- body- layout headline= " ${ this . localize . term ( 'blockEditor_addBlock' ) } " >
177
+ <umb- body- layout headline= ${ this . localize . term ( 'blockEditor_addBlock' ) } >
119
178
${ this . #renderViews( ) } ${ this . #renderMain( ) }
120
179
<div slot= "actions" >
121
180
<uui- butto n label= ${ this . localize . term ( 'general_close' ) } @click = ${ this . _rejectModal } > </ uui- butto n>
@@ -134,50 +193,73 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
134
193
}
135
194
136
195
#renderClipboard( ) {
137
- return html `<uui- box
138
- > <umb- clipboard- entry- picker
139
- .config = ${ { multiple : true , asyncFilter : this . data ?. clipboardFilter } }
140
- @selection-change = ${ this . #onClipboardPickerSelectionChange} > </ umb- clipboard- entry- picker
141
- > </ uui- box> ` ;
196
+ return html `
197
+ <uui- box>
198
+ <umb- clipboard- entry- picker
199
+ .config = ${ { multiple : true , asyncFilter : this . data ?. clipboardFilter } }
200
+ @selection-change = ${ this . #onClipboardPickerSelectionChange} > </ umb- clipboard- entry- picker>
201
+ </ uui- box>
202
+ ` ;
142
203
}
143
204
144
205
#renderCreateEmpty( ) {
206
+ if ( this . _loading ) return html `<div id= "loader" > <uui- loader> </ uui- loader> </ div> ` ;
145
207
return html `
146
- ${ this . data ?. blocks && this . data . blocks . length > 8
147
- ? html `<uui- input
208
+ ${ when (
209
+ this . data ?. blocks && this . data ?. blocks . length > 8 ,
210
+ ( ) => html `
211
+ <uui- input
148
212
id= "search"
149
213
@input = ${ this . #onSearch}
150
214
label= ${ this . localize . term ( 'general_search' ) }
151
215
placeholder= ${ this . localize . term ( 'placeholders_search' ) } >
152
216
<uui- icon name= "icon-search" slot = "prepend"> </ uui- icon>
153
- </ uui- input> `
154
- : nothing }
155
- ${ this . _filtered . map (
217
+ </ uui- input>
218
+ ` ,
219
+ ) }
220
+ ${ repeat (
221
+ this . _filtered ,
222
+ ( group ) => group . name ,
156
223
( group ) => html `
157
- ${ group . name && group . blocks . length !== 0 && group . name !== '' ? html `<h4> ${ group . name } </ h4> ` : nothing }
224
+ ${ when ( group . name && group . blocks . length !== 0 && group . name !== '' , ( ) => html `<h4> ${ group . name } </ h4> ` ) }
158
225
<div class= "blockGroup" >
159
226
${ repeat (
160
227
group . blocks ,
161
228
( block ) => block . contentElementTypeKey ,
162
- ( block ) => html `
163
- <umb- block- type-card
164
- .iconFile = ${ block . thumbnail }
165
- .iconColor = ${ block . iconColor }
166
- .backgroundColor = ${ block . backgroundColor }
167
- .contentElementTypeKey = ${ block . contentElementTypeKey }
168
- @open = ${ ( ) => this . #chooseBlock( block . contentElementTypeKey ) }
169
- .href = ${ this . _workspacePath && this . _manager ! . getContentTypeHasProperties ( block . contentElementTypeKey )
170
- ? `${ this . _workspacePath } create/${ block . contentElementTypeKey } `
171
- : undefined } >
172
- </ umb- block- type-card>
173
- ` ,
229
+ ( block ) => this . #renderBlockTypeCard( block ) ,
174
230
) }
175
231
</ div>
176
232
` ,
177
233
) }
178
234
` ;
179
235
}
180
236
237
+ #renderBlockTypeCard( block : UmbBlockTypeItemWithGroupKey ) {
238
+ const href =
239
+ this . _workspacePath && this . _manager ! . getContentTypeHasProperties ( block . contentElementTypeKey )
240
+ ? `${ this . _workspacePath } create/${ block . contentElementTypeKey } `
241
+ : undefined ;
242
+
243
+ const path = block . thumbnail ? transformServerPathToClientPath ( block . thumbnail ) : undefined ;
244
+ const imgSrc = path ? new URL ( path , this . #serverUrl) ?. href : undefined ;
245
+
246
+ return html `
247
+ <uui- card- block- type
248
+ href= ${ ifDefined ( href ) }
249
+ name= ${ this . localize . string ( block . name ) }
250
+ description= ${ this . localize . string ( block . description ) }
251
+ .background = ${ block . backgroundColor }
252
+ @open = ${ ( ) => this . #chooseBlock( block . contentElementTypeKey ) } >
253
+ ${ when (
254
+ imgSrc ,
255
+ ( src ) => html `<img src= ${ src } alt= "" / > ` ,
256
+ ( ) => html `<umb- icon name= ${ block . icon ?? '' } color = ${ ifDefined ( block . iconColor ) } > </ umb- icon> ` ,
257
+ ) }
258
+ <slot name= "actions" slot = "actions"> </ slot>
259
+ </ uui- card- block- type>
260
+ ` ;
261
+ }
262
+
181
263
#renderViews( ) {
182
264
return html `
183
265
<uui- tab- group slot= "navigation" >
@@ -201,14 +283,21 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
201
283
202
284
static override styles = [
203
285
css `
286
+ # loader {
287
+ display : flex;
288
+ justify-content : center;
289
+ }
290
+
204
291
# search {
205
292
width : 100% ;
206
293
align-items : center;
207
294
margin-bottom : var (--uui-size-layout-1 );
295
+
296
+ > uui-icon {
297
+ padding-left : var (--uui-size-space-3 );
298
+ }
208
299
}
209
- # search uui-icon {
210
- padding-left : var (--uui-size-space-3 );
211
- }
300
+
212
301
.blockGroup {
213
302
display : grid;
214
303
gap : 1rem ;
0 commit comments