Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -624,41 +624,37 @@ export const data: Array<UmbMockDataTypeModel> = [
label: 'Mocked Block Type for Block List',
contentElementTypeKey: '4f68ba66-6fb2-4778-83b8-6ab4ca3a7c5c',
settingsElementTypeKey: 'all-property-editors-document-type-id',
icon: 'icon-server-alt',
iconColor: '#F5C1BC',
backgroundColor: '#1B264F',
},
{
label: 'Mocked Coffee Block',
contentElementTypeKey: 'coffee-umbraco-demo-block-id',
iconColor: '#FFFDD0',
backgroundColor: '#633f32',
editorSize: 'medium',
icon: 'icon-coffee',
},
{
label: 'Headline',
contentElementTypeKey: 'headline-umbraco-demo-block-id',
settingsElementTypeKey: 'headline-settings-demo-block-id',
backgroundColor: 'gold',
editorSize: 'medium',
icon: 'icon-edit',
},
{
label: 'Image',
contentElementTypeKey: 'image-umbraco-demo-block-id',
editorSize: 'medium',
icon: 'icon-picture',
},
{
label: 'Rich Text',
contentElementTypeKey: 'rich-text-umbraco-demo-block-id',
editorSize: 'medium',
icon: 'icon-diploma',
},
{
label: 'Two Column Layout',
contentElementTypeKey: 'two-column-layout-umbraco-demo-block-id',
editorSize: 'medium',
icon: 'icon-book-alt',
},
],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1405,7 +1405,7 @@ export const data: Array<UmbMockDocumentTypeModel> = [
id: 'coffee-umbraco-demo-block-id',
alias: 'coffeeUmbracoDemoBlock',
name: 'Favorite Coffee',
description: null,
description: 'The delicious taste of coffee.',
icon: 'icon-coffee',
allowedAsRoot: true,
variesByCulture: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ const documentTypeItemMapper = (item: UmbMockDocumentTypeModel): DocumentTypeIte
name: item.name,
icon: item.icon,
isElement: item.isElement,
description: item.description ?? undefined,
};
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,44 @@
import { UMB_BLOCK_WORKSPACE_MODAL } from '../../workspace/index.js';
import { UMB_BLOCK_MANAGER_CONTEXT } from '../../context/index.js';
import type { UmbBlockCatalogueModalData, UmbBlockCatalogueModalValue } from './block-catalogue-modal.token.js';
import type { UmbBlockTypeGroup, UmbBlockTypeWithGroupKey } from '@umbraco-cms/backoffice/block-type';
import { css, html, customElement, state, repeat, nothing } from '@umbraco-cms/backoffice/external/lit';
import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
import { UMB_MODAL_CONTEXT, UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import {
css,
customElement,
html,
ifDefined,
nothing,
repeat,
state,
when,
} from '@umbraco-cms/backoffice/external/lit';
import { transformServerPathToClientPath } from '@umbraco-cms/backoffice/utils';
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
import { UmbRepositoryItemsManager } from '@umbraco-cms/backoffice/repository';
import { UMB_DOCUMENT_TYPE_ITEM_REPOSITORY_ALIAS } from '@umbraco-cms/backoffice/document-type';
import { UMB_MODAL_CONTEXT, UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import { UMB_SERVER_CONTEXT } from '@umbraco-cms/backoffice/server';
import type { UmbBlockTypeGroup, UmbBlockTypeWithGroupKey } from '@umbraco-cms/backoffice/block-type';
import type { UmbDocumentTypeItemModel } from '@umbraco-cms/backoffice/document-type';
import type { UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event';
import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';

// 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
import '@umbraco-cms/backoffice/block-type';
type UmbBlockTypeItemWithGroupKey = UmbBlockTypeWithGroupKey & UmbDocumentTypeItemModel;

@customElement('umb-block-catalogue-modal')
export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
UmbBlockCatalogueModalData,
UmbBlockCatalogueModalValue
> {
readonly #itemManager = new UmbRepositoryItemsManager<UmbDocumentTypeItemModel>(
this,
UMB_DOCUMENT_TYPE_ITEM_REPOSITORY_ALIAS,
);

#search = '';

private _groupedBlocks: Array<{ name?: string; blocks: Array<UmbBlockTypeWithGroupKey> }> = [];
#serverUrl = '';

private _groupedBlocks: Array<{ name?: string; blocks: Array<UmbBlockTypeItemWithGroupKey> }> = [];

@state()
private _openClipboard?: boolean;
Expand All @@ -27,36 +47,73 @@
private _workspacePath?: string;

@state()
private _filtered: Array<{ name?: string; blocks: Array<UmbBlockTypeWithGroupKey> }> = [];
private _filtered: Array<{ name?: string; blocks: Array<UmbBlockTypeItemWithGroupKey> }> = [];

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

@state()
_loading = true;

constructor() {
super();

this.consumeContext(UMB_SERVER_CONTEXT, (instance) => {
this.#serverUrl = instance?.getServerUrl() ?? '';
});

this.consumeContext(UMB_MODAL_CONTEXT, (modalContext) => {
if (modalContext?.data.createBlockInWorkspace) {
new UmbModalRouteRegistrationController(this, UMB_BLOCK_WORKSPACE_MODAL)
//.addAdditionalPath('block') // No need for additional path specification in this context as this is for sure the only workspace we want to open here.
.onSetup(() => {
return {
data: { preset: {}, originData: (modalContext.data as UmbBlockCatalogueModalData).originData },
};
})
.onSubmit(() => {
// When workspace is submitted, we want to close this modal.
this.modalContext?.submit();
})
.observeRouteBuilder((routeBuilder) => {
this._workspacePath = routeBuilder({});
});
}
});

this.consumeContext(UMB_BLOCK_MANAGER_CONTEXT, (manager) => {
this._manager = manager;
});

this.observe(this.#itemManager.items, async (items) => {
if (!items?.length) return;

const lookup = items.reduce(
(acc, item) => {
acc[item.unique] = item;
return acc;
},
{} as { [key: string]: UmbDocumentTypeItemModel },
);

const blocks: Array<UmbBlockTypeItemWithGroupKey> =
this.data?.blocks?.map((block) => ({ ...(lookup[block.contentElementTypeKey] ?? {}), ...block })) ?? [];

const blockGroups: Array<UmbBlockTypeGroup> = this.data?.blockGroups ?? [];

const noGroupBlocks = blocks.filter((block) => !blockGroups.find((group) => group.key === block.groupKey));

const grouped = blockGroups.map((group) => ({
name: group.name,
blocks: blocks.filter((block) => block.groupKey === group.key),
}));

this._groupedBlocks = [{ blocks: noGroupBlocks }, ...grouped];

this.#updateFiltered();

this._loading = false;
});

Check warning on line 116 in src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

❌ New issue: Complex Method

UmbBlockCatalogueModalElement.constructor has a cyclomatic complexity of 10, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
}

override connectedCallback() {
Expand All @@ -65,17 +122,7 @@

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

const blocks: Array<UmbBlockTypeWithGroupKey> = this.data.blocks ?? [];
const blockGroups: Array<UmbBlockTypeGroup> = this.data.blockGroups ?? [];

const noGroupBlocks = blocks.filter((block) => !blockGroups.find((group) => group.key === block.groupKey));
const grouped = blockGroups.map((group) => ({
name: group.name,
blocks: blocks.filter((block) => block.groupKey === group.key),
}));

this._groupedBlocks = [{ blocks: noGroupBlocks }, ...grouped];
this.#updateFiltered();
this.#itemManager.setUniques(this.data.blocks.map((block) => block.contentElementTypeKey));
}

#updateFiltered() {
Expand All @@ -84,7 +131,15 @@
} else {
const search = this.#search.toLowerCase();
this._filtered = this._groupedBlocks.map((group) => {
return { ...group, blocks: group.blocks.filter((block) => block.label?.toLocaleLowerCase().includes(search)) };
return {
...group,
blocks: group.blocks.filter(
(block) =>
block.label?.toLowerCase().includes(search) ||
block.name?.toLowerCase().includes(search) ||
block.description?.toLowerCase().includes(search),
),
};
});
}
}
Expand Down Expand Up @@ -115,7 +170,7 @@

override render() {
return html`
<umb-body-layout headline="${this.localize.term('blockEditor_addBlock')}">
<umb-body-layout headline=${this.localize.term('blockEditor_addBlock')}>
${this.#renderViews()}${this.#renderMain()}
<div slot="actions">
<uui-button label=${this.localize.term('general_close')} @click=${this._rejectModal}></uui-button>
Expand All @@ -134,50 +189,73 @@
}

#renderClipboard() {
return html`<uui-box
><umb-clipboard-entry-picker
.config=${{ multiple: true, asyncFilter: this.data?.clipboardFilter }}
@selection-change=${this.#onClipboardPickerSelectionChange}></umb-clipboard-entry-picker
></uui-box>`;
return html`
<uui-box>
<umb-clipboard-entry-picker
.config=${{ multiple: true, asyncFilter: this.data?.clipboardFilter }}
@selection-change=${this.#onClipboardPickerSelectionChange}></umb-clipboard-entry-picker>
</uui-box>
`;
}

#renderCreateEmpty() {
if (this._loading) return html`<div id="loader"><uui-loader></uui-loader></div>`;
return html`
${this.data?.blocks && this.data.blocks.length > 8
? html`<uui-input
${when(
this.data?.blocks && this.data?.blocks.length > 8,
() => html`
<uui-input
id="search"
@input=${this.#onSearch}
label=${this.localize.term('general_search')}
placeholder=${this.localize.term('placeholders_search')}>
<uui-icon name="icon-search" slot="prepend"></uui-icon>
</uui-input>`
: nothing}
${this._filtered.map(
</uui-input>
`,
)}
${repeat(
this._filtered,
(group) => group.name,
(group) => html`
${group.name && group.blocks.length !== 0 && group.name !== '' ? html`<h4>${group.name}</h4>` : nothing}
${when(group.name && group.blocks.length !== 0 && group.name !== '', () => html`<h4>${group.name}</h4>`)}
<div class="blockGroup">
${repeat(
group.blocks,
(block) => block.contentElementTypeKey,
(block) => html`
<umb-block-type-card
.iconFile=${block.thumbnail}
.iconColor=${block.iconColor}
.backgroundColor=${block.backgroundColor}
.contentElementTypeKey=${block.contentElementTypeKey}
@open=${() => this.#chooseBlock(block.contentElementTypeKey)}
.href=${this._workspacePath && this._manager!.getContentTypeHasProperties(block.contentElementTypeKey)
? `${this._workspacePath}create/${block.contentElementTypeKey}`
: undefined}>
</umb-block-type-card>
`,
(block) => this.#renderBlockTypeCard(block),
)}
</div>
`,
)}
`;
}

#renderBlockTypeCard(block: UmbBlockTypeItemWithGroupKey) {
const href =
this._workspacePath && this._manager!.getContentTypeHasProperties(block.contentElementTypeKey)
? `${this._workspacePath}create/${block.contentElementTypeKey}`
: undefined;

const path = block.thumbnail ? transformServerPathToClientPath(block.thumbnail) : undefined;
const imgSrc = path ? new URL(path, this.#serverUrl)?.href : undefined;

return html`
<uui-card-block-type
href=${ifDefined(href)}
name=${this.localize.string(block.name)}
description=${this.localize.string(block.description)}
.background=${block.backgroundColor}
@open=${() => this.#chooseBlock(block.contentElementTypeKey)}>
${when(
imgSrc,
(src) => html`<img src=${src} alt="" />`,
() => html`<umb-icon name=${block.icon ?? ''} color=${ifDefined(block.iconColor)}></umb-icon>`,
)}
<slot name="actions" slot="actions"> </slot>
</uui-card-block-type>
`;
}

#renderViews() {
return html`
<uui-tab-group slot="navigation">
Expand All @@ -201,14 +279,21 @@

static override styles = [
css`
#loader {
display: flex;
justify-content: center;
}

#search {
width: 100%;
align-items: center;
margin-bottom: var(--uui-size-layout-1);

uui-icon {
padding-left: var(--uui-size-space-3);
}
}
#search uui-icon {
padding-left: var(--uui-size-space-3);
}

.blockGroup {
display: grid;
gap: 1rem;
Expand Down
Loading