Merge remote-tracking branch 'origin/main' into feature/datatypepickerflow-property-icons

This commit is contained in:
Lone Iversen
2024-03-21 15:09:11 +01:00
12 changed files with 252 additions and 54 deletions

View File

@@ -35,6 +35,7 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement
extends UmbLitElement
implements UmbPropertyEditorUiElement
{
#moveData?: Array<UmbBlockTypeWithGroupKey>;
#sorter = new UmbSorterController<MappedGroupWithBlockTypes, HTMLElement>(this, {
getUniqueOfElement: (element) => element.getAttribute('data-umb-group-key'),
getUniqueOfModel: (modelEntry) => modelEntry.key!,
@@ -128,13 +129,40 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement
this.#sorter.setModel(this._groupsWithBlockTypes);
}
#onChange(e: CustomEvent, groupKey?: string) {
#onDelete(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());
}
async #onChange(e: CustomEvent) {
e.stopPropagation();
const element = e.target as UmbInputBlockTypeElement;
const value = element.value;
if (!e.detail?.moveComplete) {
// Container change, store data of the new group...
const newGroupKey = element.getAttribute('data-umb-group-key');
const movedItem = e.detail?.item as UmbBlockTypeWithGroupKey;
// Check if item moved back to original group...
movedItem.groupKey === newGroupKey
? (this.#moveData = undefined)
: (this.#moveData = value.map((block) => ({ ...block, groupKey: newGroupKey })));
} else if (e.detail?.moveComplete) {
// Move complete, get the blocks that were in an untouched group
const blocks = this.value
.filter((block) => !value.find((value) => value.contentElementTypeKey === block.contentElementTypeKey))
.filter(
(block) => !this.#moveData?.find((value) => value.contentElementTypeKey === block.contentElementTypeKey),
);
this.value = this.#moveData ? [...blocks, ...value, ...this.#moveData] : [...blocks, ...value];
this.dispatchEvent(new UmbPropertyValueChangeEvent());
this.#moveData = undefined;
}
}
#onCreate(e: CustomEvent, groupKey?: string) {
const selectedElementType = e.detail.contentElementTypeKey;
if (selectedElementType) {
@@ -170,8 +198,9 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement
? html`<umb-input-block-type
.value=${this._notGroupedBlockTypes}
.workspacePath=${this._workspacePath}
@change=${this.#onChange}
@create=${(e: CustomEvent) => this.#onCreate(e, undefined)}
@change=${(e: CustomEvent) => this.#onChange(e, undefined)}></umb-input-block-type>`
@delete=${(e: CustomEvent) => this.#onDelete(e, undefined)}></umb-input-block-type>`
: ''}
${repeat(
this._groupsWithBlockTypes,
@@ -180,10 +209,12 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement
html`<div class="group" data-umb-group-key=${ifDefined(group.key)}>
${group.key ? this.#renderGroupInput(group.key, group.name) : nothing}
<umb-input-block-type
data-umb-group-key=${group.key}
.value=${group.blocks}
.workspacePath=${this._workspacePath}
@change=${this.#onChange}
@create=${(e: CustomEvent) => this.#onCreate(e, group.key)}
@change=${(e: CustomEvent) => this.#onChange(e, group.key)}></umb-input-block-type>
@delete=${(e: CustomEvent) => this.#onDelete(e, group.key)}></umb-input-block-type>
</div>`,
)}
</div>`;

View File

@@ -3,7 +3,10 @@ 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, state } from '@umbraco-cms/backoffice/external/lit';
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
import {
UmbPropertyValueChangeEvent,
type UmbPropertyEditorConfigCollection,
} from '@umbraco-cms/backoffice/property-editor';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UMB_WORKSPACE_MODAL, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal';
@@ -51,14 +54,19 @@ export class UmbPropertyEditorUIBlockListBlockConfigurationElement
}
}
#onChange(e: CustomEvent) {
e.stopPropagation();
this.value = (e.target as UmbInputBlockTypeElement).value;
this.dispatchEvent(new UmbPropertyValueChangeEvent());
}
render() {
return html`<umb-input-block-type
.value=${this.value}
.workspacePath=${this._workspacePath}
@create=${this.#onCreate}
@change=${(e: Event) => {
this.value = (e.target as UmbInputBlockTypeElement).value;
}}></umb-input-block-type>`;
@delete=${this.#onChange}
@change=${this.#onChange}></umb-input-block-type>`;
}
}

View File

@@ -1,20 +1,41 @@
import type { UmbBlockTypeBaseModel } from '../../types.js';
import type { UmbBlockTypeCardElement } from '../block-type-card/index.js';
import type { UmbBlockTypeBaseModel, UmbBlockTypeWithGroupKey } from '../../types.js';
import { UMB_MODAL_MANAGER_CONTEXT, umbConfirmModal } 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/backoffice/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';
import { UmbDeleteEvent } from '@umbraco-cms/backoffice/event';
import { UMB_DOCUMENT_TYPE_PICKER_MODAL } from '@umbraco-cms/backoffice/document-type';
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
@customElement('umb-input-block-type')
export class UmbInputBlockTypeElement<
BlockType extends UmbBlockTypeBaseModel = UmbBlockTypeBaseModel,
BlockType extends UmbBlockTypeWithGroupKey = UmbBlockTypeWithGroupKey,
> extends UmbLitElement {
#sorter = new UmbSorterController<BlockType, UmbBlockTypeCardElement>(this, {
getUniqueOfElement: (element) => element.contentElementTypeKey,
getUniqueOfModel: (modelEntry) => modelEntry.contentElementTypeKey!,
itemSelector: 'umb-block-type-card',
identifier: 'umb-block-type-sorter',
containerSelector: '#blocks',
onChange: ({ model }) => {
this._items = model;
},
onContainerChange: ({ model, item }) => {
this._items = model;
this.dispatchEvent(new CustomEvent('change', { detail: { item } }));
},
onEnd: () => {
this.dispatchEvent(new CustomEvent('change', { detail: { moveComplete: true } }));
},
});
@property({ type: Array, attribute: false })
public set value(items) {
this._items = items ?? [];
this.#sorter.setModel(this._items);
}
public get value() {
return this._items;
@@ -67,7 +88,7 @@ export class UmbInputBlockTypeElement<
deleteItem(contentElementTypeKey: string) {
this.value = this.value.filter((x) => x.contentElementTypeKey !== contentElementTypeKey);
this.dispatchEvent(new UmbChangeEvent());
this.dispatchEvent(new UmbDeleteEvent());
}
protected getFormElement() {
@@ -85,7 +106,7 @@ export class UmbInputBlockTypeElement<
}
render() {
return html`<div>
return html`<div id="blocks">
${repeat(this.value, (block) => block.contentElementTypeKey, this.#renderItem)} ${this.#renderButton()}
</div>`;
}
@@ -93,6 +114,7 @@ export class UmbInputBlockTypeElement<
#renderItem = (block: BlockType) => {
return html`
<umb-block-type-card
.data-umb-content-element-key=${block.contentElementTypeKey}
.name=${block.label}
.iconColor=${block.iconColor}
.backgroundColor=${block.backgroundColor}
@@ -125,6 +147,10 @@ export class UmbInputBlockTypeElement<
grid-template-rows: repeat(auto-fill, minmax(160px, 1fr));
}
[drag-placeholder] {
opacity: 0.5;
}
#add-button {
text-align: center;
min-height: 150px;

View File

@@ -30,4 +30,5 @@ export * from './input-upload-field/index.js';
export * from './multiple-color-picker-input/index.js';
export * from './multiple-text-string-input/index.js';
export * from './popover-layout/index.js';
export * from './ref-item/index.js';
export * from './table/index.js';

View File

@@ -0,0 +1 @@
export * from './ref-item.element.js';

View File

@@ -0,0 +1,75 @@
import { html, customElement, css, property, when, nothing, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';
import { UUIRefElement, UUIRefEvent, UUIRefNodeElement } from '@umbraco-cms/backoffice/external/uui';
@customElement('umb-ref-item')
export class UmbRefItemElement extends UmbElementMixin(UUIRefElement) {
@property({ type: String })
name = '';
@property({ type: String })
detail = '';
@property({ type: String })
icon = '';
constructor() {
super();
this.selectable = true;
this.addEventListener(UUIRefEvent.OPEN, () => this.dispatchEvent(new Event('click')));
}
public render() {
return html`
<button
type="button"
id="btn-item"
tabindex="0"
@click=${this.handleOpenClick}
@keydown=${this.handleOpenKeydown}
?disabled=${this.disabled}>
${when(
this.icon,
() => html`<span id="icon"><uui-icon name=${this.icon ?? ''}></uui-icon></span>`,
() => nothing,
)}
<div id="info">
<div id="name">${this.name}</div>
<small id="detail">${this.detail}</small>
</div>
</button>
<div id="select-border"></div>
<slot></slot>
`;
}
static styles = [
...UUIRefElement.styles,
...UUIRefNodeElement.styles,
css`
:host {
padding: calc(var(--uui-size-4) + 1px);
}
#btn-item {
text-decoration: none;
color: inherit;
align-self: stretch;
line-height: normal;
display: flex;
position: relative;
align-items: center;
cursor: pointer;
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
'umb-ref-item': UmbRefItemElement;
}
}

View File

@@ -1,39 +1,80 @@
import { css, html, customElement, repeat, nothing, when } from '@umbraco-cms/backoffice/external/lit';
import {
css,
html,
customElement,
repeat,
nothing,
when,
state,
ifDefined,
} from '@umbraco-cms/backoffice/external/lit';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { UmbItemPickerModalData, UmbItemPickerModel } from '@umbraco-cms/backoffice/modal';
@customElement('umb-item-picker-modal')
export class UmbItemPickerModalElement extends UmbModalBaseElement<UmbItemPickerModalData, UmbItemPickerModel> {
@state()
private _filtered: Array<UmbItemPickerModel> = [];
#close() {
this.modalContext?.reject();
}
#filter(event: { target: HTMLInputElement }) {
if (!this.data) return;
if (event.target.value) {
const query = event.target.value.toLowerCase();
this._filtered = this.data.items.filter(
(item) => item.label.toLowerCase().includes(query) || item.value.toLowerCase().includes(query),
);
} else {
this._filtered = this.data.items;
}
}
#submit(item: UmbItemPickerModel) {
this.modalContext?.setValue(item);
this.modalContext?.submit();
}
connectedCallback() {
super.connectedCallback();
if (!this.data) return;
this._filtered = this.data.items;
}
render() {
if (!this.data) return nothing;
const items = this.data.items;
const items = this._filtered;
return html`
<umb-body-layout headline=${this.data.headline}>
<div>
<div id="main">
<uui-input type="search" placeholder=${this.localize.term('placeholders_filter')} @input=${this.#filter}>
<div slot="prepend">
<uui-icon name="search"></uui-icon>
</div>
</uui-input>
${when(
items.length,
() => html`
<uui-box>
${repeat(
items,
(item) => item.value,
(item) => html`
<uui-button @click=${() => this.#submit(item)} look="placeholder" label="${item.label}">
<h4>${item.label}</h4>
<p>${item.description}</p>
</uui-button>
`,
)}
<uui-ref-list>
${repeat(
items,
(item) => item.value,
(item) => html`
<umb-ref-item
name=${item.label}
detail=${ifDefined(item.description)}
icon=${ifDefined(item.icon)}
@click=${() => this.#submit(item)}>
</umb-ref-item>
`,
)}
</uui-ref-list>
</uui-box>
`,
() => html`<p>There are no items to select.</p>`,
@@ -49,6 +90,16 @@ export class UmbItemPickerModalElement extends UmbModalBaseElement<UmbItemPicker
static styles = [
UmbTextStyles,
css`
#main {
display: flex;
flex-direction: column;
gap: var(--uui-size-space-5);
}
uui-box > uui-input {
width: 100%;
}
uui-box > uui-button {
display: block;
--uui-button-content-align: flex-start;

View File

@@ -16,10 +16,8 @@ export abstract class UmbModalBaseElement<
@property({ type: Object, attribute: false })
public manifest?: ModalManifestType;
#modalContext?: UmbModalContext<ModalDataType, ModalValueType> | undefined;
@property({ attribute: false })
public get modalContext(): UmbModalContext<ModalDataType, ModalValueType> | undefined {
return this.#modalContext;
}
public set modalContext(context: UmbModalContext<ModalDataType, ModalValueType> | undefined) {
this.#modalContext = context;
if (context) {
@@ -35,7 +33,9 @@ export abstract class UmbModalBaseElement<
);
}
}
#modalContext?: UmbModalContext<ModalDataType, ModalValueType> | undefined;
public get modalContext(): UmbModalContext<ModalDataType, ModalValueType> | undefined {
return this.#modalContext;
}
@property({ attribute: false })
public set data(value: ModalDataType | undefined) {

View File

@@ -6,8 +6,9 @@ export type UmbItemPickerModalData = {
};
export type UmbItemPickerModel = {
label: string;
description?: string;
icon?: string;
label: string;
value: string;
};

View File

@@ -64,19 +64,22 @@ export class UmbDynamicRootOriginPickerModalModalElement extends UmbModalBaseEle
render() {
return html`
<umb-body-layout headline="${this.localize.term('dynamicRoot_pickDynamicRootOriginTitle')}">
<umb-body-layout headline=${this.localize.term('dynamicRoot_pickDynamicRootOriginTitle')}>
<div id="main">
<uui-box>
${repeat(
this._origins,
(item) => item.alias,
(item) => html`
<uui-button @click=${() => this.#choose(item)} look="placeholder" label="${ifDefined(item.meta.label)}">
<h3>${item.meta.label}</h3>
<p>${item.meta.description}</p>
</uui-button>
`,
)}
<uui-ref-list>
${repeat(
this._origins,
(item) => item.alias,
(item) => html`
<umb-ref-item
name=${ifDefined(item.meta.label)}
detail=${ifDefined(item.meta.description)}
icon=${ifDefined(item.meta.icon)}
@click=${() => this.#choose(item)}></umb-ref-item>
`,
)}
</uui-ref-list>
</uui-box>
</div>
<div slot="actions">

View File

@@ -52,19 +52,22 @@ export class UmbDynamicRootQueryStepPickerModalModalElement extends UmbModalBase
render() {
return html`
<umb-body-layout headline="${this.localize.term('dynamicRoot_pickDynamicRootQueryStepTitle')}">
<umb-body-layout headline=${this.localize.term('dynamicRoot_pickDynamicRootQueryStepTitle')}>
<div id="main">
<uui-box>
${repeat(
this._querySteps,
(item) => item.alias,
(item) => html`
<uui-button @click=${() => this.#choose(item)} look="placeholder" label="${ifDefined(item.meta.label)}">
<h3>${item.meta.label}</h3>
<p>${item.meta.description}</p>
</uui-button>
`,
)}
<uui-ref-list>
${repeat(
this._querySteps,
(item) => item.alias,
(item) => html`
<umb-ref-item
name=${ifDefined(item.meta.label)}
detail=${ifDefined(item.meta.description)}
icon=${ifDefined(item.meta.icon)}
@click=${() => this.#choose(item)}></umb-ref-item>
`,
)}
</uui-ref-list>
</uui-box>
</div>
<div slot="actions">

View File

@@ -111,9 +111,7 @@ export class UmbDashboardPublishedStatusElement extends UmbLitElement {
}
render() {
// TODO: Are we supposed to have the debug element here?
return html`
<umb-debug visible dialog></umb-debug>
<uui-box headline="Published Cache Status">
<p>${this._publishedStatusText}</p>
<uui-button