Merge pull request #1467 from umbraco/feature/datatype-collection-view-layouts

Feature: Collection View layouts configuration
This commit is contained in:
Lee Kelleher
2024-03-27 10:44:23 +00:00
committed by GitHub
3 changed files with 118 additions and 117 deletions

View File

@@ -70,16 +70,16 @@ export class UmbIconPickerModalElement extends UmbModalBaseElement<UmbIconPicker
${this.renderSearchbar()}
<hr />
<uui-color-swatches
.value="${this._modalValue?.color ?? ''}"
.value=${this._currentAlias}
label="Color switcher for icons"
@change="${this.#onColorChange}">
@change=${this.#onColorChange}>
${
// TODO: Missing translation for the color aliases.
this._colorList.map(
(color) => html`
<uui-color-swatch
label="${color.alias}"
title="${color.alias}"
label=${color.alias}
title=${color.alias}
value=${color.alias}
style="--uui-swatch-color: var(${color.varName})"></uui-color-swatch>
`,

View File

@@ -1,18 +1,28 @@
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
import { html, customElement, property, repeat, css, ifDefined } from '@umbraco-cms/backoffice/external/lit';
import type { UUIBooleanInputEvent, UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
import {
html,
customElement,
property,
repeat,
css,
ifDefined,
nothing,
when,
} from '@umbraco-cms/backoffice/external/lit';
import { extractUmbColorVariable } from '@umbraco-cms/backoffice/resources';
import { UMB_ICON_PICKER_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UMB_ICON_PICKER_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import type { UmbInputManifestElement } from '@umbraco-cms/backoffice/components';
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
import type { UUIInputElement, UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
interface LayoutConfig {
interface UmbCollectionLayoutConfig {
icon?: string;
isSystem?: boolean;
name?: string;
path?: string;
collectionView?: string;
isSystem?: boolean;
selected?: boolean;
}
@@ -24,14 +34,41 @@ export class UmbPropertyEditorUICollectionViewLayoutConfigurationElement
extends UmbLitElement
implements UmbPropertyEditorUiElement
{
// TODO: [LK] Add sorting.
@property({ type: Array })
value?: Array<LayoutConfig>;
value?: Array<UmbCollectionLayoutConfig>;
@property({ type: Object, attribute: false })
public config?: UmbPropertyEditorConfigCollection;
#onAdd() {
this.value = [...(this.value ?? []), { isSystem: false, icon: 'icon-stop', selected: true }];
async #focusNewItem() {
await this.updateComplete;
const input = this.shadowRoot?.querySelector('.layout-item:last-of-type > uui-input') as UUIInputElement;
input.focus();
}
#onAdd(event: { target: UmbInputManifestElement }) {
const manifest = event.target.value;
this.value = [
...(this.value ?? []),
{
icon: manifest?.icon,
name: manifest?.label,
collectionView: manifest?.value,
},
];
this.dispatchEvent(new UmbPropertyValueChangeEvent());
this.#focusNewItem();
}
#onChangeLabel(e: UUIInputEvent, index: number) {
const values = [...(this.value ?? [])];
values[index] = { ...values[index], name: e.target.value as string };
this.value = values;
this.dispatchEvent(new UmbPropertyValueChangeEvent());
}
@@ -42,34 +79,9 @@ export class UmbPropertyEditorUICollectionViewLayoutConfigurationElement
this.dispatchEvent(new UmbPropertyValueChangeEvent());
}
#onChangePath(e: UUIInputEvent, index: number) {
const values = [...(this.value ?? [])];
values[index] = { ...values[index], path: e.target.value as string };
this.value = values;
this.dispatchEvent(new UmbPropertyValueChangeEvent());
}
#onChangeName(e: UUIInputEvent, index: number) {
const values = [...(this.value ?? [])];
values[index] = { ...values[index], name: e.target.value as string };
this.value = values;
this.dispatchEvent(new UmbPropertyValueChangeEvent());
}
#onChangeSelected(e: UUIBooleanInputEvent, index: number) {
const values = [...(this.value ?? [])];
values[index] = { ...values[index], selected: e.target.checked };
this.value = values;
this.dispatchEvent(new UmbPropertyValueChangeEvent());
}
async #onIconChange(index: number) {
// This is not begin used? [NL]
//const icon = this.#iconReader((this.value ? this.value[index].icon : undefined) ?? '');
// TODO: send icon data to modal
async #onIconChange(icon: typeof UMB_ICON_PICKER_MODAL.VALUE, index: number) {
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modal = modalManager.open(this, UMB_ICON_PICKER_MODAL);
const modal = modalManager.open(this, UMB_ICON_PICKER_MODAL, { value: icon });
const picked = await modal?.onSubmit();
if (!picked) return;
@@ -79,81 +91,59 @@ export class UmbPropertyEditorUICollectionViewLayoutConfigurationElement
this.dispatchEvent(new UmbPropertyValueChangeEvent());
}
#parseIcon(iconString: string | undefined): typeof UMB_ICON_PICKER_MODAL.VALUE {
const [icon, color] = iconString?.split(' ') ?? [];
return { icon, color: color?.replace('color-', '') };
}
render() {
return html`<div id="layout-wrapper">
${this.value
? repeat(
this.value,
(layout, index) => '' + layout.name + layout.icon,
(layout, index) =>
html` <div class="layout-item">
<uui-icon name="icon-navigation"></uui-icon> ${layout.isSystem
? this.renderSystemFieldRow(layout, index)
: this.renderCustomFieldRow(layout, index)}
</div>`,
)
: ''}
if (!this.value) return nothing;
return html`
<div id="layout-wrapper">
${repeat(
this.value,
(layout, index) => '' + index + layout.name + layout.icon,
(layout, index) => this.#renderLayout(layout, index),
)}
</div>
<uui-button
id="add"
label=${this.localize.term('general_add')}
look="placeholder"
@click=${this.#onAdd}></uui-button>`;
<umb-input-manifest extension-type="collectionView" @change=${this.#onAdd}></umb-input-manifest>
`;
}
#iconReader(iconString: string): { icon: string; color?: string } {
if (!iconString) return { icon: '' };
#renderLayout(layout: UmbCollectionLayoutConfig, index: number) {
const icon = this.#parseIcon(layout.icon);
const varName = icon.color ? extractUmbColorVariable(icon.color) : undefined;
const parts = iconString.split(' ');
return html`
<div class="layout-item">
<uui-icon name="icon-navigation"></uui-icon>
if (parts.length === 2) {
const [icon, color] = parts;
const varName = extractUmbColorVariable(color.replace('color-', ''));
return { icon, color: varName };
} else {
const [icon] = parts;
return { icon };
}
}
<uui-button compact look="outline" label="pick icon" @click=${() => this.#onIconChange(icon, index)}>
${when(
icon.color,
() => html`<uui-icon name=${ifDefined(icon.icon)} style="color:var(${varName})"></uui-icon>`,
() => html`<uui-icon name=${ifDefined(icon.icon)}></uui-icon>`,
)}
</uui-button>
renderSystemFieldRow(layout: LayoutConfig, index: number) {
const icon = this.#iconReader(layout.icon ?? '');
<uui-input
label="name"
value=${ifDefined(layout.name)}
placeholder="Enter a label..."
@change=${(e: UUIInputEvent) => this.#onChangeLabel(e, index)}></uui-input>
return html` <uui-button compact disabled label="Icon" look="outline">
<uui-icon name=${ifDefined(icon.icon)}></uui-icon>
</uui-button>
${index}
<span><strong>${ifDefined(layout.name)}</strong> <small>(system field)</small></span>
<uui-checkbox
?checked=${layout.selected}
label="Show"
@change=${(e: UUIBooleanInputEvent) => this.#onChangeSelected(e, index)}>
</uui-checkbox>`;
}
<div class="alias">
<code>${layout.collectionView}</code>
</div>
renderCustomFieldRow(layout: LayoutConfig, index: number) {
const icon = this.#iconReader(layout.icon ?? '');
return html`<uui-button compact look="outline" label="pick icon" @click=${() => this.#onIconChange(index)}>
${icon.color
? html`<uui-icon name=${icon.icon} style="color:var(${icon.color})"></uui-icon>`
: html`<uui-icon name=${icon.icon}></uui-icon>`}
</uui-button>
${index}
<uui-input
label="name"
value=${ifDefined(layout.name)}
placeholder="Name..."
@change=${(e: UUIInputEvent) => this.#onChangeName(e, index)}></uui-input>
<uui-input
label="path"
value=${ifDefined(layout.path)}
placeholder="Layout path..."
@change=${(e: UUIInputEvent) => this.#onChangePath(e, index)}></uui-input>
<uui-button
label=${this.localize.term('actions_remove')}
look="secondary"
@click=${() => this.#onRemove(index)}></uui-button>`;
<div class="actions">
<uui-button
label=${this.localize.term('general_remove')}
look="secondary"
@click=${() => this.#onRemove(index)}></uui-button>
</div>
</div>
`;
}
static styles = [
@@ -163,7 +153,7 @@ export class UmbPropertyEditorUICollectionViewLayoutConfigurationElement
display: flex;
flex-direction: column;
gap: 1px;
margin-bottom: var(--uui-size-3);
margin-bottom: var(--uui-size-1);
}
.layout-item {
@@ -174,12 +164,23 @@ export class UmbPropertyEditorUICollectionViewLayoutConfigurationElement
padding: var(--uui-size-3) var(--uui-size-6);
}
.layout-item > :last-child {
margin-left: auto;
.layout-item > uui-icon {
flex: 0 0 var(--uui-size-6);
}
#add {
width: 100%;
.layout-item > uui-button {
flex: 0 0 var(--uui-size-10);
}
.layout-item > uui-input,
.layout-item > .alias {
flex: 1;
}
.layout-item > .actions {
flex: 0 0 auto;
display: flex;
justify-content: flex-end;
}
`,
];

View File

@@ -7,7 +7,7 @@ const tableCollectionView: ManifestCollectionView = {
type: 'collectionView',
alias: UMB_COLLECTION_VIEW_USER_TABLE,
name: 'User Table Collection View',
js: () => import('./table/user-table-collection-view.element.js'),
element: () => import('./table/user-table-collection-view.element.js'),
meta: {
label: 'Table',
icon: 'icon-list',
@@ -26,8 +26,8 @@ export const UMB_COLLECTION_VIEW_USER_GRID = 'Umb.CollectionView.User.Grid';
const gridCollectionView: ManifestCollectionView = {
type: 'collectionView',
alias: UMB_COLLECTION_VIEW_USER_GRID,
name: 'User Table Collection View',
js: () => import('./grid/user-grid-collection-view.element.js'),
name: 'User Grid Collection View',
element: () => import('./grid/user-grid-collection-view.element.js'),
weight: 200,
meta: {
label: 'Grid',