diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts index 81b2e99b7e..e8623bad5e 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts @@ -706,6 +706,13 @@ export const data: Array = [ icon: 'icon-book-alt', groupKey: 'demo-block-group-id', }, + { + label: 'Test broken group key', + contentElementTypeKey: 'test-block-id', + editorSize: 'medium', + icon: 'icon-war', + groupKey: 'group-id-that-does-not-exist', + }, ], }, ], diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts index 676aea8ace..1deda0dd40 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts @@ -1488,4 +1488,30 @@ export const data: Array = [ properties: [], containers: [], }, + { + allowedTemplateIds: [], + defaultTemplateId: null, + id: 'test-block-id', + alias: 'testBlock', + name: 'Test broken group key', + description: null, + icon: 'icon-war', + allowedAsRoot: true, + variesByCulture: false, + variesBySegment: false, + isElement: true, + hasChildren: false, + isContainer: false, + parentId: null, + isFolder: false, + allowedContentTypes: [], + compositions: [], + cleanup: { + preventCleanup: false, + keepAllVersionsNewerThanDays: null, + keepLatestVersionPerDayForDays: null, + }, + properties: [], + containers: [], + }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-group-configuration/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-group-configuration/manifests.ts new file mode 100644 index 0000000000..7bb496f59d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-group-configuration/manifests.ts @@ -0,0 +1,13 @@ +import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifest: ManifestPropertyEditorUi = { + type: 'propertyEditorUi', + alias: 'Umb.PropertyEditorUi.BlockTypeGroupConfiguration', + name: 'Block Grid Group Configuration Property Editor UI', + js: () => import('./property-editor-ui-block-grid-group-configuration.element.js'), + meta: { + label: '', + icon: 'icon-box-alt', + group: 'common', + }, +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-group-configuration/property-editor-ui-block-grid-group-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-group-configuration/property-editor-ui-block-grid-group-configuration.element.ts new file mode 100644 index 0000000000..333d2258ed --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-group-configuration/property-editor-ui-block-grid-group-configuration.element.ts @@ -0,0 +1,56 @@ +import { html, customElement, property, css } from '@umbraco-cms/backoffice/external/lit'; +import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbId } from '@umbraco-cms/backoffice/id'; +import type { UmbBlockGridGroupType } from '@umbraco-cms/backoffice/block'; + +@customElement('umb-property-editor-ui-block-grid-group-configuration') +export class UmbPropertyEditorUIBlockGridGroupConfigurationElement + extends UmbLitElement + implements UmbPropertyEditorUiElement +{ + private _value: Array = []; + + @property({ type: Array }) + public get value(): Array { + return this._value; + } + public set value(value: Array) { + this._value = value || []; + } + + @property({ attribute: false }) + public set config(config: UmbPropertyEditorConfigCollection | undefined) {} + + #addGroup() { + this.value = [...this._value, { name: 'Unnamed group', key: UmbId.new() }]; + this.dispatchEvent(new CustomEvent('property-value-change')); + } + + render() { + return html` + + ${this.localize.term('blockEditor_addBlockGroup')} + + `; + } + + static styles = [ + UmbTextStyles, + css` + uui-button { + display: block; + } + `, + ]; +} + +export default UmbPropertyEditorUIBlockGridGroupConfigurationElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-property-editor-ui-block-grid-group-configuration': UmbPropertyEditorUIBlockGridGroupConfigurationElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-group-configuration/property-editor-ui-block-grid-group-configuration.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-group-configuration/property-editor-ui-block-grid-group-configuration.stories.ts new file mode 100644 index 0000000000..c001579428 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-group-configuration/property-editor-ui-block-grid-group-configuration.stories.ts @@ -0,0 +1,15 @@ +import type { Meta, Story } from '@storybook/web-components'; +import type { UmbPropertyEditorUIBlockGridGroupConfigurationElement } from './property-editor-ui-block-grid-group-configuration.element.js'; +import { html } from '@umbraco-cms/backoffice/external/lit'; + +import './property-editor-ui-block-grid-group-configuration.element.js'; + +export default { + title: 'Property Editor UIs/Block Grid Group Configuration', + component: 'umb-property-editor-ui-block-grid-group-configuration', + id: 'umb-property-editor-ui-block-grid-group-configuration', +} as Meta; + +export const AAAOverview: Story = () => + html` `; +AAAOverview.storyName = 'Overview'; diff --git a/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 b/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 index e92121a6d7..9714e9cd08 100644 --- a/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 +++ b/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 @@ -1,10 +1,21 @@ -import type { UmbBlockTypeBaseModel, UmbInputBlockTypeElement } from '../../../block-type/index.js'; +import type { UmbBlockTypeWithGroupKey, UmbInputBlockTypeElement } from '../../../block-type/index.js'; import '../../../block-type/components/input-block-type/index.js'; import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; -import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; -import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; +import { html, customElement, property, state, repeat, nothing, css } from '@umbraco-cms/backoffice/external/lit'; +import { + UmbPropertyValueChangeEvent, + type UmbPropertyEditorConfigCollection, +} from '@umbraco-cms/backoffice/property-editor'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import { + UMB_BLOCK_GRID_TYPE, + type UmbBlockGridGroupType, + type UmbBlockGridGroupTypeConfiguration, +} from '@umbraco-cms/backoffice/block'; +import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; +import { UMB_PROPERTY_DATASET_CONTEXT, type UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property'; +import { UMB_WORKSPACE_MODAL, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal'; /** * @element umb-property-editor-ui-block-grid-type-configuration @@ -14,22 +25,150 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement extends UmbLitElement implements UmbPropertyEditorUiElement { + #datasetContext?: UmbPropertyDatasetContext; + #blockTypeWorkspaceModalRegistration?: UmbModalRouteRegistrationController< + typeof UMB_WORKSPACE_MODAL.DATA, + typeof UMB_WORKSPACE_MODAL.VALUE + >; + + private _value: Array = []; @property({ attribute: false }) - value: UmbBlockTypeBaseModel[] = []; + get value() { + return this._value; + } + set value(value: Array) { + this._value = value ?? []; + } @property({ type: Object, attribute: false }) public config?: UmbPropertyEditorConfigCollection; - render() { - return html` { - this.value = (e.target as UmbInputBlockTypeElement).value; - }}>`; + @state() + private _blockGroups: Array = []; + + @state() + private _mappedValuesAndGroups: Array = []; + + @state() + private _workspacePath?: string; + + constructor() { + super(); + this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, async (instance) => { + this.#datasetContext = instance; + this.#observeProperties(); + }); + + this.#blockTypeWorkspaceModalRegistration?.destroy(); + + this.#blockTypeWorkspaceModalRegistration = new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL) + .addAdditionalPath(UMB_BLOCK_GRID_TYPE) + .onSetup(() => { + return { data: { entityType: UMB_BLOCK_GRID_TYPE, preset: {} }, modal: { size: 'large' } }; + }) + .observeRouteBuilder((routeBuilder) => { + const newpath = routeBuilder({}); + this._workspacePath = newpath; + }); } - static styles = [UmbTextStyles]; + async #observeProperties() { + if (!this.#datasetContext) return; + + this.observe(await this.#datasetContext.propertyValueByAlias('blockGroups'), (value) => { + this._blockGroups = (value as Array) ?? []; + this.#mapValuesToBlockGroups(); + }); + this.observe(await this.#datasetContext.propertyValueByAlias('blocks'), () => { + this.#mapValuesToBlockGroups(); + }); + } + + #mapValuesToBlockGroups() { + // What if a block is in a group that does not exist in the block groups? Should it be removed? (Right now they will never be rendered) + const valuesWithNoGroup = this._value.filter((value) => !value.groupKey); + + const valuesWithGroup = this._blockGroups.map((group) => { + return { name: group.name, key: group.key, blocks: this._value.filter((value) => value.groupKey === group.key) }; + }); + + this._mappedValuesAndGroups = [{ blocks: valuesWithNoGroup }, ...valuesWithGroup]; + } + + #onChange(e: CustomEvent, groupKey?: string) { + const updatedValues = (e.target as UmbInputBlockTypeElement).value.map((value) => ({ ...value, groupKey })); + const filteredValues = this.value.filter((value) => value.groupKey !== groupKey); + this.value = [...filteredValues, ...updatedValues]; + this.dispatchEvent(new UmbPropertyValueChangeEvent()); + } + + #onCreate(e: CustomEvent, groupKey: string | null) { + const selectedElementType = e.detail.contentElementTypeKey; + if (selectedElementType) { + this.#blockTypeWorkspaceModalRegistration?.open({}, 'create/' + selectedElementType + '/' + groupKey); + } + } + + #deleteGroup(groupKey: string) { + this.#datasetContext?.setPropertyValue( + 'blockGroups', + this._blockGroups.filter((group) => group.key !== groupKey), + ); + + // Should blocks that belonged to the removed group be deleted as well? + this.value = this._value.filter((block) => block.groupKey !== groupKey); + } + + #changeGroupName(e: UUIInputEvent, groupKey: string) { + const groupName = e.target.value as string; + this.#datasetContext?.setPropertyValue( + 'blockGroups', + this._blockGroups.map((group) => (group.key === groupKey ? { ...group, name: groupName } : group)), + ); + } + + render() { + return html`${repeat( + this._mappedValuesAndGroups, + (group) => group.key, + (group) => + html`${group.key ? this.#renderGroupInput(group.key, group.name) : nothing} + this.#onCreate(e, group.key ?? null)} + @change=${(e: CustomEvent) => this.#onChange(e, group.key)}>`, + )}`; + } + + #renderGroupInput(groupKey: string, groupName?: string) { + return html` this.#changeGroupName(e, groupKey)}> + this.#deleteGroup(groupKey)}> + + + `; + } + + static styles = [ + UmbTextStyles, + css` + uui-input { + margin-top: var(--uui-size-6); + margin-bottom: var(--uui-size-4); + } + + uui-input:not(:hover, :focus) { + border: 1px solid transparent; + } + uui-input:not(:hover, :focus) uui-button { + opacity: 0; + } + `, + ]; } export default UmbPropertyEditorUIBlockGridTypeConfigurationElement; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/manifests.ts index 11b3d055ed..f1737b0acd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/manifests.ts @@ -2,5 +2,12 @@ import { manifest as blockGridEditor } from './block-grid-editor/manifests.js'; import { manifest as blockGridLayoutStylesheet } from './block-grid-layout-stylesheet/manifests.js'; import { manifest as blockGridTypeConfiguration } from './block-grid-type-configuration/manifests.js'; import { manifest as blockGridColumnSpan } from './block-grid-column-span/manifests.js'; +import { manifest as blockGridGroupConfiguration } from './block-grid-group-configuration/manifests.js'; -export const manifests = [blockGridTypeConfiguration, blockGridEditor, blockGridLayoutStylesheet, blockGridColumnSpan]; +export const manifests = [ + blockGridTypeConfiguration, + blockGridEditor, + blockGridLayoutStylesheet, + blockGridColumnSpan, + blockGridGroupConfiguration, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/types.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/types.ts index cf593c1fd2..3a51638811 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/types.ts @@ -1,4 +1,6 @@ -import type { UmbBlockTypeBaseModel } from '../block-type/index.js'; +import type { UmbBlockTypeBaseModel, UmbBlockTypeWithGroupKey } from '../block-type/index.js'; + +export const UMB_BLOCK_GRID_TYPE = 'block-grid-type'; export interface UmbBlockGridType extends UmbBlockTypeBaseModel { columnSpanOptions: Array; @@ -9,5 +11,13 @@ export interface UmbBlockGridType extends UmbBlockTypeBaseModel { thumbnail?: string; areaGridColumns?: number; areas: Array; - groupKey: null | string; +} + +export interface UmbBlockGridGroupType { + name: string; + key: string; +} + +export interface UmbBlockGridGroupTypeConfiguration extends Partial { + blocks: Array; } diff --git a/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 b/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 index 5948997fcb..a1a1c7de07 100644 --- a/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 +++ b/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 @@ -1,10 +1,12 @@ import type { UmbBlockTypeBaseModel, UmbInputBlockTypeElement } from '../../../block-type/index.js'; import '../../../block-type/components/input-block-type/index.js'; +import { UMB_BLOCK_LIST_TYPE } from '../../types.js'; import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; -import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import { UMB_WORKSPACE_MODAL, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal'; /** * @element umb-property-editor-ui-block-list-type-configuration @@ -14,16 +16,47 @@ export class UmbPropertyEditorUIBlockListBlockConfigurationElement extends UmbLitElement implements UmbPropertyEditorUiElement { + #blockTypeWorkspaceModalRegistration?: UmbModalRouteRegistrationController< + typeof UMB_WORKSPACE_MODAL.DATA, + typeof UMB_WORKSPACE_MODAL.VALUE + >; + + @state() + private _workspacePath?: string; + + constructor() { + super(); + this.#blockTypeWorkspaceModalRegistration?.destroy(); + + this.#blockTypeWorkspaceModalRegistration = new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL) + .addAdditionalPath(UMB_BLOCK_LIST_TYPE) + .onSetup(() => { + return { data: { entityType: UMB_BLOCK_LIST_TYPE, preset: {} }, modal: { size: 'large' } }; + }) + .observeRouteBuilder((routeBuilder) => { + const newpath = routeBuilder({}); + this._workspacePath = newpath; + }); + } + @property({ attribute: false }) value: UmbBlockTypeBaseModel[] = []; @property({ type: Object, attribute: false }) public config?: UmbPropertyEditorConfigCollection; + #onCreate(e: CustomEvent) { + const selectedElementType = e.detail.contentElementTypeKey; + if (selectedElementType) { + this.#blockTypeWorkspaceModalRegistration?.open({}, 'create/' + selectedElementType + '/null'); + } + } + render() { return html` { this.value = (e.target as UmbInputBlockTypeElement).value; }}>`; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/types.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/types.ts index dee3748bfd..bd1e8053c9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/types.ts @@ -1,5 +1,7 @@ import type { UmbBlockTypeBaseModel } from '../block-type/index.js'; import type { UmbBlockLayoutBaseModel } from '../index.js'; +export const UMB_BLOCK_LIST_TYPE = 'block-list-type'; + export interface UmbBlockListTypeModel extends UmbBlockTypeBaseModel {} export interface UmbBlockListLayoutModel extends UmbBlockLayoutBaseModel {} diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/index.ts index fcdafe77a5..2ce6e9bff7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/index.ts @@ -1,2 +1,2 @@ -export * from './input-block-type/index.js'; export * from './block-type-card/index.js'; +export * from './input-block-type/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts index 03e92d961e..1dcad5fdf3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts @@ -1,20 +1,16 @@ import type { UmbBlockTypeBaseModel } from '../../types.js'; -import { - UMB_DOCUMENT_TYPE_PICKER_MODAL, - UMB_MODAL_MANAGER_CONTEXT, - UMB_WORKSPACE_MODAL, - UmbModalRouteRegistrationController, -} from '@umbraco-cms/backoffice/modal'; +import { UMB_DOCUMENT_TYPE_PICKER_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import '../block-type-card/index.js'; import { css, html, customElement, property, state, repeat } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import type { UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property'; +import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; @customElement('umb-input-block-type') export class UmbInputBlockTypeElement< BlockType extends UmbBlockTypeBaseModel = UmbBlockTypeBaseModel, > extends UmbLitElement { - // @property({ type: Array, attribute: false }) public get value() { return this._items; @@ -23,43 +19,23 @@ export class UmbInputBlockTypeElement< this._items = items ?? []; } - @property({ type: String, attribute: 'entity-type' }) - public get entityType() { - return this.#entityType; - } - public set entityType(entityType) { - this.#entityType = entityType; - - this.#blockTypeWorkspaceModalRegistration?.destroy(); - - if (entityType) { - // TODO: Make specific modal token that requires data. - this.#blockTypeWorkspaceModalRegistration = new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL) - .addAdditionalPath(entityType) - .onSetup(() => { - return { data: { entityType: entityType, preset: {} }, modal: { size: 'large' } }; - }) - .observeRouteBuilder((routeBuilder) => { - const newpath = routeBuilder({}); - this._workspacePath = newpath; - }); - } - } - #entityType?: string; + @property({ type: String }) + workspacePath?: string; @state() private _items: Array = []; - @state() - private _workspacePath?: string; - - #blockTypeWorkspaceModalRegistration?: UmbModalRouteRegistrationController< - typeof UMB_WORKSPACE_MODAL.DATA, - typeof UMB_WORKSPACE_MODAL.VALUE - >; + #datasetContext?: UmbPropertyDatasetContext; + #filter: Array = []; constructor() { super(); + this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, async (instance) => { + this.#datasetContext = instance; + this.observe(await this.#datasetContext?.propertyValueByAlias('blocks'), (value) => { + this.#filter = value as Array; + }); + }); } create() { @@ -74,23 +50,23 @@ export class UmbInputBlockTypeElement< // Only pick elements: docType.isElement && // Prevent picking the an already used element type: - this._items.find((x) => x.contentElementTypeKey === docType.unique) === undefined, + this.#filter && + this.#filter.find((x) => x.contentElementTypeKey === docType.unique) === undefined, }, }); const modalValue = await modalContext?.onSubmit(); const selectedElementType = modalValue.selection[0]; + if (selectedElementType) { - this.#blockTypeWorkspaceModalRegistration?.open({}, 'create/' + selectedElementType); + this.dispatchEvent(new CustomEvent('create', { detail: { contentElementTypeKey: selectedElementType } })); } } }); - - // No need to fire a change event, as all changes are made directly to the property, via context api. } deleteItem(contentElementTypeKey: string) { - this._items = this._items.filter((x) => x.contentElementTypeKey !== contentElementTypeKey); + this.value = this._items.filter((x) => x.contentElementTypeKey !== contentElementTypeKey); this.dispatchEvent(new UmbChangeEvent()); } @@ -99,12 +75,21 @@ export class UmbInputBlockTypeElement< } render() { - return html` - ${this._items ? repeat(this._items, (item) => item.contentElementTypeKey, this.#renderItem) : ''} - ${this.#renderButton()} - `; + return html`
+ ${repeat(this.value, (block) => block.contentElementTypeKey, this.#renderItem)} ${this.#renderButton()} +
`; } + #renderItem = (item: BlockType) => { + return html` + this.deleteItem(item.contentElementTypeKey)}> + + `; + }; + #renderButton() { return html` this.create()} label="open"> @@ -114,19 +99,9 @@ export class UmbInputBlockTypeElement< `; } - #renderItem = (item: BlockType) => { - return html` - this.deleteItem(item.contentElementTypeKey)}> - - `; - }; - static styles = [ css` - :host { + div { display: grid; gap: var(--uui-size-space-3); grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); @@ -135,6 +110,7 @@ export class UmbInputBlockTypeElement< #add-button { text-align: center; + min-height: 150px; height: 100%; } @@ -142,6 +118,18 @@ export class UmbInputBlockTypeElement< display: block; margin: 0 auto; } + + uui-input { + border: none; + margin: var(--uui-size-space-6) 0 var(--uui-size-space-4); + } + + uui-input:hover uui-button { + opacity: 1; + } + uui-input uui-button { + opacity: 0; + } `, ]; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/workspace/block-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/workspace/block-type-workspace.context.ts index 5209b21197..82ca7316ea 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/workspace/block-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/workspace/block-type-workspace.context.ts @@ -1,9 +1,10 @@ -import type { UmbBlockTypeBaseModel } from '../types.js'; +import type { UmbBlockTypeBaseModel, UmbBlockTypeWithGroupKey } from '../types.js'; import type { UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property'; import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property'; import type { UmbInvariantableWorkspaceContextInterface, - UmbWorkspaceContextInterface} from '@umbraco-cms/backoffice/workspace'; + UmbWorkspaceContextInterface, +} from '@umbraco-cms/backoffice/workspace'; import { UmbEditableWorkspaceContextBase, UmbInvariantWorkspacePropertyDatasetContext, @@ -13,7 +14,7 @@ import type { UmbControllerHost, UmbControllerHostElement } from '@umbraco-cms/b import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; import type { ManifestWorkspace, PropertyEditorConfigProperty } from '@umbraco-cms/backoffice/extension-registry'; -export class UmbBlockTypeWorkspaceContext +export class UmbBlockTypeWorkspaceContext extends UmbEditableWorkspaceContextBase implements UmbInvariantableWorkspaceContextInterface { @@ -57,9 +58,11 @@ export class UmbBlockTypeWorkspaceContext { const elementTypeKey = info.match.params.elementTypeKey; - this.#workspaceContext!.create(elementTypeKey); + const groupKey = info.match.params.groupKey === 'null' ? null : info.match.params.groupKey; + this.#workspaceContext!.create(elementTypeKey, groupKey); new UmbWorkspaceIsNewRedirectController( this, diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts index e205abc1a3..7fe74f0be1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts @@ -45,7 +45,6 @@ export class UmbBlockWorkspaceContext< readonly name = this.#label.asObservable(); constructor(host: UmbControllerHost, workspaceArgs: { manifest: ManifestWorkspace }) { - // TODO: We don't need a repo here, so maybe we should not require this of the UmbEditableWorkspaceContextBase super(host, workspaceArgs.manifest.alias); this.#entityType = workspaceArgs.manifest.meta?.entityType; this.workspaceAlias = workspaceArgs.manifest.alias; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dashboards/dictionary/dashboard-localization-dictionary.element.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dashboards/dictionary/dashboard-localization-dictionary.element.ts index 6f82558c59..9f60055470 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dashboards/dictionary/dashboard-localization-dictionary.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dashboards/dictionary/dashboard-localization-dictionary.element.ts @@ -42,6 +42,7 @@ export class UmbDashboardTranslationDictionaryElement extends UmbLitElement { const { data } = await this.#repo.list(0, 1000); this.#dictionaryItems = data?.items ?? []; + this.#setTableColumns(); this.#setTableItems(); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit.element.ts index 9b9920205b..f6babbd0eb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit.element.ts @@ -11,12 +11,12 @@ import type { PropertyTypeContainerModelBaseModel, } from '@umbraco-cms/backoffice/backend-api'; import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; -import type { UmbRoute , UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router'; +import type { UmbRoute, UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router'; import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbConfirmModalData } from '@umbraco-cms/backoffice/modal'; import { UMB_CONFIRM_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import type { UmbSorterConfig} from '@umbraco-cms/backoffice/sorter'; +import type { UmbSorterConfig } from '@umbraco-cms/backoffice/sorter'; import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; const SORTER_CONFIG: UmbSorterConfig = { @@ -216,7 +216,7 @@ export class UmbDocumentTypeWorkspaceViewEditElement extends UmbLitElement imple if (!tabId) return; this._workspaceContext?.structure.removeContainer(null, tabId); this._tabsStructureHelper?.isOwnerContainer(tabId) - ? window.history.replaceState(null, '', this._routerPath + this._routes[0]?.path ?? '/root') + ? window.history.replaceState(null, '', this._routerPath + (this._routes[0]?.path ?? '/root')) : ''; } async #addTab() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit.element.ts index a920094e78..cd24e5cf44 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit.element.ts @@ -11,12 +11,12 @@ import type { PropertyTypeContainerModelBaseModel, } from '@umbraco-cms/backoffice/backend-api'; import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; -import type { UmbRoute , UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router'; +import type { UmbRoute, UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router'; import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbConfirmModalData } from '@umbraco-cms/backoffice/modal'; import { UMB_CONFIRM_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import type { UmbSorterConfig} from '@umbraco-cms/backoffice/sorter'; +import type { UmbSorterConfig } from '@umbraco-cms/backoffice/sorter'; import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; const SORTER_CONFIG: UmbSorterConfig = { @@ -216,7 +216,7 @@ export class UmbMediaTypeWorkspaceViewEditElement extends UmbLitElement implemen if (!tabId) return; this._workspaceContext?.structure.removeContainer(null, tabId); this._tabsStructureHelper?.isOwnerContainer(tabId) - ? window.history.replaceState(null, '', this._routerPath + this._routes[0]?.path ?? '/root') + ? window.history.replaceState(null, '', this._routerPath + (this._routes[0]?.path ?? '/root')) : ''; } async #addTab() {