Skip to content

Commit 3c6f222

Browse files
authored
Fixes Block Catalogue Modal Filter (#19700)
* Mock data updates The `icon` is not part the block-type data. * Adds `description` to the mock doctype model * Refactors block catalogue modal to make the filter/search work with a block-type's name & description. This removes the need to use the `<umb-block-type-card>` component, all element-type data is requested upfront. * Reverted dev/debug change * Abstracted out the element-type items observation to its own method * Updated CSS rule thanks to a Copilot suggestion.
1 parent 50282ea commit 3c6f222

File tree

6 files changed

+137
-46
lines changed

6 files changed

+137
-46
lines changed

src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -624,41 +624,37 @@ export const data: Array<UmbMockDataTypeModel> = [
624624
label: 'Mocked Block Type for Block List',
625625
contentElementTypeKey: '4f68ba66-6fb2-4778-83b8-6ab4ca3a7c5c',
626626
settingsElementTypeKey: 'all-property-editors-document-type-id',
627-
icon: 'icon-server-alt',
627+
iconColor: '#F5C1BC',
628+
backgroundColor: '#1B264F',
628629
},
629630
{
630631
label: 'Mocked Coffee Block',
631632
contentElementTypeKey: 'coffee-umbraco-demo-block-id',
632633
iconColor: '#FFFDD0',
633634
backgroundColor: '#633f32',
634635
editorSize: 'medium',
635-
icon: 'icon-coffee',
636636
},
637637
{
638638
label: 'Headline',
639639
contentElementTypeKey: 'headline-umbraco-demo-block-id',
640640
settingsElementTypeKey: 'headline-settings-demo-block-id',
641641
backgroundColor: 'gold',
642642
editorSize: 'medium',
643-
icon: 'icon-edit',
644643
},
645644
{
646645
label: 'Image',
647646
contentElementTypeKey: 'image-umbraco-demo-block-id',
648647
editorSize: 'medium',
649-
icon: 'icon-picture',
650648
},
651649
{
652650
label: 'Rich Text',
653651
contentElementTypeKey: 'rich-text-umbraco-demo-block-id',
654652
editorSize: 'medium',
655-
icon: 'icon-diploma',
656653
},
657654
{
658655
label: 'Two Column Layout',
659656
contentElementTypeKey: 'two-column-layout-umbraco-demo-block-id',
660657
editorSize: 'medium',
661-
icon: 'icon-book-alt',
662658
},
663659
],
664660
},

src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1405,7 +1405,7 @@ export const data: Array<UmbMockDocumentTypeModel> = [
14051405
id: 'coffee-umbraco-demo-block-id',
14061406
alias: 'coffeeUmbracoDemoBlock',
14071407
name: 'Favorite Coffee',
1408-
description: null,
1408+
description: 'The delicious taste of coffee.',
14091409
icon: 'icon-coffee',
14101410
allowedAsRoot: true,
14111411
variesByCulture: false,

src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.db.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ const documentTypeItemMapper = (item: UmbMockDocumentTypeModel): DocumentTypeIte
140140
name: item.name,
141141
icon: item.icon,
142142
isElement: item.isElement,
143+
description: item.description ?? undefined,
143144
};
144145
};
145146

src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-type-configuration/property-editor-ui-block-grid-type-configuration.element.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
2929
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
3030
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
3131

32+
// 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-input-block-type` element.
33+
import '@umbraco-cms/backoffice/block-type';
34+
3235
interface MappedGroupWithBlockTypes extends UmbBlockGridTypeGroupType {
3336
blocks: Array<UmbBlockTypeWithGroupKey>;
3437
}

src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-type-configuration/property-editor-ui-block-list-type-configuration.element.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import '../../../block-type/components/input-block-type/index.js';
21
import { UMB_BLOCK_LIST_TYPE } from '../../constants.js';
32
import type { UmbBlockTypeBaseModel, UmbInputBlockTypeElement } from '@umbraco-cms/backoffice/block-type';
43
import type {
@@ -11,6 +10,9 @@ import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace';
1110
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
1211
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
1312

13+
// 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-input-block-type` element.
14+
import '@umbraco-cms/backoffice/block-type';
15+
1416
/**
1517
* @element umb-property-editor-ui-block-list-type-configuration
1618
*/

src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts

Lines changed: 127 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,44 @@
11
import { UMB_BLOCK_WORKSPACE_MODAL } from '../../workspace/index.js';
22
import { UMB_BLOCK_MANAGER_CONTEXT } from '../../context/index.js';
33
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';
815
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';
922
import type { UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event';
23+
import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
1024

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;
1326

1427
@customElement('umb-block-catalogue-modal')
1528
export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
1629
UmbBlockCatalogueModalData,
1730
UmbBlockCatalogueModalValue
1831
> {
32+
readonly #itemManager = new UmbRepositoryItemsManager<UmbDocumentTypeItemModel>(
33+
this,
34+
UMB_DOCUMENT_TYPE_ITEM_REPOSITORY_ALIAS,
35+
);
36+
1937
#search = '';
2038

21-
private _groupedBlocks: Array<{ name?: string; blocks: Array<UmbBlockTypeWithGroupKey> }> = [];
39+
#serverUrl = '';
40+
41+
private _groupedBlocks: Array<{ name?: string; blocks: Array<UmbBlockTypeItemWithGroupKey> }> = [];
2242

2343
@state()
2444
private _openClipboard?: boolean;
@@ -27,14 +47,21 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
2747
private _workspacePath?: string;
2848

2949
@state()
30-
private _filtered: Array<{ name?: string; blocks: Array<UmbBlockTypeWithGroupKey> }> = [];
50+
private _filtered: Array<{ name?: string; blocks: Array<UmbBlockTypeItemWithGroupKey> }> = [];
3151

3252
@state()
3353
_manager?: typeof UMB_BLOCK_MANAGER_CONTEXT.TYPE;
3454

55+
@state()
56+
_loading = true;
57+
3558
constructor() {
3659
super();
3760

61+
this.consumeContext(UMB_SERVER_CONTEXT, (instance) => {
62+
this.#serverUrl = instance?.getServerUrl() ?? '';
63+
});
64+
3865
this.consumeContext(UMB_MODAL_CONTEXT, (modalContext) => {
3966
if (modalContext?.data.createBlockInWorkspace) {
4067
new UmbModalRouteRegistrationController(this, UMB_BLOCK_WORKSPACE_MODAL)
@@ -57,6 +84,10 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
5784
this.consumeContext(UMB_BLOCK_MANAGER_CONTEXT, (manager) => {
5885
this._manager = manager;
5986
});
87+
88+
this.observe(this.#itemManager.items, async (items) => {
89+
this.#observeBlockTypes(items);
90+
});
6091
}
6192

6293
override connectedCallback() {
@@ -65,17 +96,37 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
6596

6697
this._openClipboard = this.data.openClipboard ?? false;
6798

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 ?? [];
70117

71118
const noGroupBlocks = blocks.filter((block) => !blockGroups.find((group) => group.key === block.groupKey));
119+
72120
const grouped = blockGroups.map((group) => ({
73121
name: group.name,
74122
blocks: blocks.filter((block) => block.groupKey === group.key),
75123
}));
76124

77125
this._groupedBlocks = [{ blocks: noGroupBlocks }, ...grouped];
126+
78127
this.#updateFiltered();
128+
129+
this._loading = false;
79130
}
80131

81132
#updateFiltered() {
@@ -84,7 +135,15 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
84135
} else {
85136
const search = this.#search.toLowerCase();
86137
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+
};
88147
});
89148
}
90149
}
@@ -115,7 +174,7 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
115174

116175
override render() {
117176
return html`
118-
<umb-body-layout headline="${this.localize.term('blockEditor_addBlock')}">
177+
<umb-body-layout headline=${this.localize.term('blockEditor_addBlock')}>
119178
${this.#renderViews()}${this.#renderMain()}
120179
<div slot="actions">
121180
<uui-button label=${this.localize.term('general_close')} @click=${this._rejectModal}></uui-button>
@@ -134,50 +193,73 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
134193
}
135194

136195
#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+
`;
142203
}
143204

144205
#renderCreateEmpty() {
206+
if (this._loading) return html`<div id="loader"><uui-loader></uui-loader></div>`;
145207
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
148212
id="search"
149213
@input=${this.#onSearch}
150214
label=${this.localize.term('general_search')}
151215
placeholder=${this.localize.term('placeholders_search')}>
152216
<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,
156223
(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>`)}
158225
<div class="blockGroup">
159226
${repeat(
160227
group.blocks,
161228
(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),
174230
)}
175231
</div>
176232
`,
177233
)}
178234
`;
179235
}
180236

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+
181263
#renderViews() {
182264
return html`
183265
<uui-tab-group slot="navigation">
@@ -201,14 +283,21 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
201283

202284
static override styles = [
203285
css`
286+
#loader {
287+
display: flex;
288+
justify-content: center;
289+
}
290+
204291
#search {
205292
width: 100%;
206293
align-items: center;
207294
margin-bottom: var(--uui-size-layout-1);
295+
296+
> uui-icon {
297+
padding-left: var(--uui-size-space-3);
298+
}
208299
}
209-
#search uui-icon {
210-
padding-left: var(--uui-size-space-3);
211-
}
300+
212301
.blockGroup {
213302
display: grid;
214303
gap: 1rem;

0 commit comments

Comments
 (0)