From 86ac6807c5e49cd2d38ce89dde6e25581b7564b3 Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Thu, 19 Oct 2023 10:03:13 +0200 Subject: [PATCH 01/17] init --- .../src/mocks/data/data-type.data.ts | 31 +++- .../src/mocks/data/document-type.data.ts | 154 ++++++++++++++++++ .../src/mocks/data/document.data.ts | 15 +- .../src/mocks/data/user-group.data.ts | 7 + .../src/packages/core/components/index.ts | 1 + .../core/components/input-tree/index.ts | 1 + .../input-tree/input-tree.context.ts | 19 +++ .../input-tree/input-tree.element.ts | 150 +++++++++++++++++ .../input-tree/input-tree.stories.ts | 15 ++ .../components/input-tree/input-tree.test.ts | 18 ++ .../property-editor-ui-tree-picker.element.ts | 55 ++++++- 11 files changed, 460 insertions(+), 6 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.context.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.stories.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.test.ts diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type.data.ts index 8c1490effe..2e68da599a 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type.data.ts @@ -200,7 +200,36 @@ export const data: Array = parentId: null, propertyEditorAlias: 'Umbraco.MultiNodeTreePicker', propertyEditorUiAlias: 'Umb.PropertyEditorUi.TreePicker', - values: [], + values: [ + { + alias: 'startNode', + value: { + type: 'member', + query: '', + id: null, + }, + }, + { + alias: 'minNumber', + value: 1, + }, + { + alias: 'maxNumber', + value: 10, + }, + { + alias: 'ignoreUserStartNodes', + value: false, + }, + { + alias: 'showOpenButton', + value: true, + }, + { + alias: 'filter', + value: '', + }, + ], }, { type: 'data-type', diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/document-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/document-type.data.ts index ef912e8795..868b48e5ac 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/document-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/document-type.data.ts @@ -608,6 +608,151 @@ export const data: Array = [ keepLatestVersionPerDayForDays: null, }, }, + { + allowedTemplateIds: [], + defaultTemplateId: null, + id: 'simple-document-type-id', + alias: 'blogPost', + name: 'All property editors document type', + description: null, + icon: 'umb:item-arrangement', + allowedAsRoot: true, + variesByCulture: true, + variesBySegment: false, + isElement: false, + properties: [ + { + id: '6', + containerId: 'all-properties-group-key', + alias: 'multiNodeTreePicker', + name: 'Multi Node Tree Picker', + description: '', + dataTypeId: 'dt-multiNodeTreePicker', + variesByCulture: false, + variesBySegment: false, + validation: { + mandatory: true, + mandatoryMessage: null, + regEx: null, + regExMessage: null, + }, + appearance: { + labelOnTop: false, + }, + }, + { + id: '3', + containerId: 'all-properties-group-key', + alias: 'contentPicker', + name: 'Content Picker', + description: '', + dataTypeId: 'dt-contentPicker', + variesByCulture: false, + variesBySegment: false, + validation: { + mandatory: true, + mandatoryMessage: null, + regEx: null, + regExMessage: null, + }, + appearance: { + labelOnTop: false, + }, + }, + { + id: '19', + containerId: 'all-properties-group-key', + alias: 'mediaPicker', + name: 'Media Picker', + description: '', + dataTypeId: 'dt-mediaPicker', + variesByCulture: false, + variesBySegment: false, + validation: { + mandatory: true, + mandatoryMessage: null, + regEx: null, + regExMessage: null, + }, + appearance: { + labelOnTop: false, + }, + }, + { + id: '30', + containerId: 'all-properties-group-key', + alias: 'memberPicker', + name: 'Member Picker', + description: '', + dataTypeId: 'dt-memberPicker', + variesByCulture: false, + variesBySegment: false, + validation: { + mandatory: true, + mandatoryMessage: null, + regEx: null, + regExMessage: null, + }, + appearance: { + labelOnTop: false, + }, + }, + { + id: '31', + containerId: 'all-properties-group-key', + alias: 'memberGroupPicker', + name: 'Member Group Picker', + description: '', + dataTypeId: 'dt-memberGroupPicker', + variesByCulture: false, + variesBySegment: false, + validation: { + mandatory: true, + mandatoryMessage: null, + regEx: null, + regExMessage: null, + }, + appearance: { + labelOnTop: false, + }, + }, + { + id: '32', + containerId: 'all-properties-group-key', + alias: 'userPicker', + name: 'User Picker', + description: '', + dataTypeId: 'dt-userPicker', + variesByCulture: false, + variesBySegment: false, + validation: { + mandatory: true, + mandatoryMessage: null, + regEx: null, + regExMessage: null, + }, + appearance: { + labelOnTop: false, + }, + }, + ], + containers: [ + { + id: 'all-properties-group-key', + parentId: null, + name: 'Content', + type: 'Group', + sortOrder: 0, + }, + ], + allowedContentTypes: [], + compositions: [], + cleanup: { + preventCleanup: false, + keepAllVersionsNewerThanDays: null, + keepLatestVersionPerDayForDays: null, + }, + }, { allowedTemplateIds: [], @@ -1008,6 +1153,15 @@ export const treeData: Array = [ parentId: null, icon: '', }, + { + name: 'Simple document type', + type: 'document-type', + hasChildren: false, + id: 'simple-document-type-id', + isContainer: false, + parentId: null, + icon: '', + }, { name: 'Page Document Type', type: 'document-type', diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/document.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/document.data.ts index 5c4edfb0d2..640d4d7fec 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/document.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/document.data.ts @@ -462,7 +462,12 @@ export const data: Array = [ ], }, { - urls: [], + urls: [ + { + culture: 'en-US', + url: '/', + }, + ], templateId: null, id: 'simple-document-id', contentTypeId: 'simple-document-type-id', @@ -477,6 +482,14 @@ export const data: Array = [ updateDate: '2023-02-06T15:32:24.957009', }, ], + values: [ + { + alias: 'multiNodeTreePicker', + culture: null, + segment: null, + value: null, + }, + ], }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/user-group.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/user-group.data.ts index 1fd17caccf..0adf2a7ea6 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/user-group.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/user-group.data.ts @@ -50,6 +50,13 @@ export const data: Array = [ documentStartNodeId: 'all-property-editors-document-id', permissions: [UMB_USER_PERMISSION_DOCUMENT_CREATE, UMB_USER_PERMISSION_DOCUMENT_DELETE], }, + { + id: 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b2', + name: 'Something', + icon: 'umb:medal', + documentStartNodeId: 'simple-document-id', + permissions: [UMB_USER_PERMISSION_DOCUMENT_CREATE, UMB_USER_PERMISSION_DOCUMENT_DELETE], + }, { id: '9d24dc47-a4bf-427f-8a4a-b900f03b8a12', name: 'User Group 1', diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts index d212570fa6..007aa9e0e6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts @@ -26,6 +26,7 @@ export * from './input-radio-button-list/index.js'; export * from './input-section/index.js'; export * from './input-slider/index.js'; export * from './input-toggle/index.js'; +export * from './input-tree/index.js'; export * from './input-upload-field/index.js'; export * from './property-editor-config/index.js'; export * from './property-type-based-property/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/index.ts new file mode 100644 index 0000000000..490f084b27 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/index.ts @@ -0,0 +1 @@ +export * from './input-tree.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.context.ts new file mode 100644 index 0000000000..c962361b93 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.context.ts @@ -0,0 +1,19 @@ +import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { UMB_DOCUMENT_PICKER_MODAL, UMB_MEDIA_TREE_PICKER_MODAL } from '@umbraco-cms/backoffice/modal'; +import { ItemResponseModelBaseModel } from '@umbraco-cms/backoffice/backend-api'; + +export type NodeType = 'content' | 'member' | 'media'; + +export class UmbNodePickerContext extends UmbPickerInputContext { + #type: NodeType = 'content'; + readonly nodeType = this.#type; + + constructor(host: UmbControllerHostElement, type?: NodeType) { + super(host, 'Umb.Repository.Media', UMB_MEDIA_TREE_PICKER_MODAL); + } + + setNodeType(newNodeType: NodeType) { + this.#type = newNodeType; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts new file mode 100644 index 0000000000..dac0e15073 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts @@ -0,0 +1,150 @@ +import { NodeType, UmbNodePickerContext } from './input-tree.context.js'; +import { css, html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import type { ItemResponseModelBaseModel, MediaItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; + +@customElement('umb-input-tree') +export class UmbInputTreeElement extends FormControlMixin(UmbLitElement) { + protected getFormElement() { + return undefined; + } + + /** + * This is a minimum amount of selected items in this input. + * @type {number} + * @attr + * @default 0 + */ + @property({ type: Number }) + public get min(): number { + return this.#pickerContext.min; + } + public set min(value: number) { + this.#pickerContext.min = value; + } + + /** + * Min validation message. + * @type {boolean} + * @attr + * @default + */ + @property({ type: String, attribute: 'min-message' }) + minMessage = 'This field need more items'; + + /** + * This is a maximum amount of selected items in this input. + * @type {number} + * @attr + * @default Infinity + */ + @property({ type: Number }) + public get max(): number { + return this.#pickerContext.max; + } + public set max(value: number) { + this.#pickerContext.max = value; + } + + /** + * Max validation message. + * @type {boolean} + * @attr + * @default + */ + @property({ type: String, attribute: 'min-message' }) + maxMessage = 'This field exceeds the allowed amount of items'; + + public get selectedIds(): Array { + return this.#pickerContext.getSelection(); + } + public set selectedIds(ids: Array) { + this.#pickerContext.setSelection(ids); + } + + @property() + public set value(idsString: string) { + // Its with full purpose we don't call super.value, as thats being handled by the observation of the context selection. + this.selectedIds = idsString.split(/[ ,]+/); + } + + @property() + public set type(value: NodeType) { + this.#pickerContext.setNodeType(value); + } + public get type(): NodeType { + return this.#pickerContext.nodeType; + } + + @state() + private _items?: Array; + + #pickerContext: UmbNodePickerContext; + + constructor() { + super(); + + this.addValidator( + 'rangeUnderflow', + () => this.minMessage, + () => !!this.min && this.#pickerContext.getSelection().length < this.min, + ); + + this.addValidator( + 'rangeOverflow', + () => this.maxMessage, + () => !!this.max && this.#pickerContext.getSelection().length > this.max, + ); + + this.#pickerContext = new UmbNodePickerContext(this); + + this.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(','))); + this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems)); + } + + render() { + return html` ${this._items?.map((item) => this.#renderItem(item))} ${this.#renderButton()} `; + } + + #renderButton() { + if (this._items && this.max && this._items.length >= this.max) return; + return html` + this.#pickerContext.openPicker()} + label=${this.localize.term('general_add')}> + Add + + `; + } + + #renderItem(item: ItemResponseModelBaseModel) { + const icon = this.type === 'media' ? 'umb:picture' : this.type === 'member' ? 'umb:user' : 'umb:document'; + return html` + + `; + } + + static styles = [ + css` + #add-button { + width: 100%; + } + + uui-icon { + display: block; + margin: 0 auto; + } + `, + ]; +} + +export default UmbInputTreeElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-input-tree': UmbInputTreeElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.stories.ts new file mode 100644 index 0000000000..940a620ac8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.stories.ts @@ -0,0 +1,15 @@ +import { Meta, StoryObj } from '@storybook/web-components'; +import './input-tree.element.js'; +import type { UmbInputTreeElement } from './input-tree.element.js'; + +const meta: Meta = { + title: 'Components/Inputs/Tree', + component: 'umb-input-tree', +}; + +export default meta; +type Story = StoryObj; + +export const Overview: Story = { + args: {}, +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.test.ts new file mode 100644 index 0000000000..a037ab8754 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.test.ts @@ -0,0 +1,18 @@ +import { expect, fixture, html } from '@open-wc/testing'; +import { UmbInputTreeElement } from './input-tree.element.js'; +import { defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; +describe('UmbInputTreeElement', () => { + let element: UmbInputTreeElement; + + beforeEach(async () => { + element = await fixture(html` `); + }); + + it('is defined with its own instance', () => { + expect(element).to.be.instanceOf(UmbInputTreeElement); + }); + + it('passes the a11y audit', async () => { + await expect(element).shadowDom.to.be.accessible(defaultA11yConfig); + }); +}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts index 85a503542f..4bd02f1018 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts @@ -1,22 +1,69 @@ -import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; -import { UmbTextStyles } from "@umbraco-cms/backoffice/style"; +import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; +import { UmbInputTreeElement } from '@umbraco-cms/backoffice/components'; /** * @element umb-property-editor-ui-tree-picker */ + +interface StartNode { + type: 'content' | 'member' | 'media'; + query: string | null; + id: string | null; +} + @customElement('umb-property-editor-ui-tree-picker') export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implements UmbPropertyEditorUiElement { @property() value = ''; + @state() + startNode?: StartNode; + + @state() + filter?: string; + + @state() + ignoreUserStartNodes?: boolean; + + @state() + showOpenButton?: boolean; + + @state() + minNumber?: number; + + @state() + maxNumber?: number; + @property({ attribute: false }) - public config?: UmbPropertyEditorConfigCollection; + public set config(config: UmbPropertyEditorConfigCollection | undefined) { + this.startNode = config?.getValueByAlias('startNode'); + + this.filter = config?.getValueByAlias('filter'); + this.ignoreUserStartNodes = config?.getValueByAlias('ignoreUserStartNodes'); + this.showOpenButton = config?.getValueByAlias('showOpenButton'); + + this.minNumber = config?.getValueByAlias('minNumber'); + this.maxNumber = config?.getValueByAlias('maxNumber'); + } + + #onChange(e: CustomEvent) { + this.value = (e.target as UmbInputTreeElement).value; + this.dispatchEvent(new CustomEvent('property-value-change')); + } render() { - return html`
umb-property-editor-ui-tree-picker
`; + return html``; } static styles = [UmbTextStyles]; From e593f195c6f003b3c8021c3594ba79b0036b9284 Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Thu, 19 Oct 2023 10:24:24 +0200 Subject: [PATCH 02/17] context --- .../src/mocks/data/data-type.data.ts | 2 +- .../input-tree/input-tree.context.ts | 29 +++++++++++++++++-- .../input-tree/input-tree.element.ts | 4 +-- .../property-editor-ui-tree-picker.element.ts | 11 ++----- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type.data.ts index 2e68da599a..de4a10e552 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type.data.ts @@ -204,7 +204,7 @@ export const data: Array = { alias: 'startNode', value: { - type: 'member', + type: 'media', query: '', id: null, }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.context.ts index c962361b93..5a37bd3c09 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.context.ts @@ -1,16 +1,41 @@ import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; -import { UMB_DOCUMENT_PICKER_MODAL, UMB_MEDIA_TREE_PICKER_MODAL } from '@umbraco-cms/backoffice/modal'; +import { UMB_DOCUMENT_PICKER_MODAL, UMB_MEDIA_TREE_PICKER_MODAL, UmbModalToken } from '@umbraco-cms/backoffice/modal'; import { ItemResponseModelBaseModel } from '@umbraco-cms/backoffice/backend-api'; export type NodeType = 'content' | 'member' | 'media'; +export type StartNode = { + type?: NodeType; + query?: string | null; + id?: string | null; +}; + +type ContextCreator = { + repository?: string; + token?: UmbModalToken; +}; + export class UmbNodePickerContext extends UmbPickerInputContext { #type: NodeType = 'content'; readonly nodeType = this.#type; constructor(host: UmbControllerHostElement, type?: NodeType) { - super(host, 'Umb.Repository.Media', UMB_MEDIA_TREE_PICKER_MODAL); + const context: ContextCreator = {}; + + context.repository = 'Umb.Repository.Document'; + context.token = UMB_DOCUMENT_PICKER_MODAL; + + if (type === 'media') { + context.repository = 'Umb.Repository.Media'; + context.token = UMB_MEDIA_TREE_PICKER_MODAL; + } + if (type === 'member') { + //context.repository = 'Umb.Repository.Member'; + //context.token = UMB_MEMBER_TREE_PICKER_MODAL; + } + + super(host, context.repository, context.token); } setNodeType(newNodeType: NodeType) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts index dac0e15073..db51e70fdd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts @@ -2,7 +2,7 @@ import { NodeType, UmbNodePickerContext } from './input-tree.context.js'; import { css, html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import type { ItemResponseModelBaseModel, MediaItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import type { ItemResponseModelBaseModel } from '@umbraco-cms/backoffice/backend-api'; @customElement('umb-input-tree') export class UmbInputTreeElement extends FormControlMixin(UmbLitElement) { @@ -115,7 +115,7 @@ export class UmbInputTreeElement extends FormControlMixin(UmbLitElement) { look="placeholder" @click=${() => this.#pickerContext.openPicker()} label=${this.localize.term('general_add')}> - Add + ${this.localize.term('general_add')} `; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts index 4bd02f1018..d6527aa80d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts @@ -1,4 +1,5 @@ -import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { StartNode } from '../../../components/input-tree/input-tree.context.js'; +import { html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; @@ -9,12 +10,6 @@ import { UmbInputTreeElement } from '@umbraco-cms/backoffice/components'; * @element umb-property-editor-ui-tree-picker */ -interface StartNode { - type: 'content' | 'member' | 'media'; - query: string | null; - id: string | null; -} - @customElement('umb-property-editor-ui-tree-picker') export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implements UmbPropertyEditorUiElement { @property() @@ -57,7 +52,7 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen render() { return html` Date: Thu, 19 Oct 2023 12:53:25 +0200 Subject: [PATCH 03/17] config directly into input --- .../src/mocks/data/data-type.data.ts | 6 +- .../input-tree/input-tree.context.ts | 30 ++++------ .../input-tree/input-tree.element.ts | 60 ++++++++++++------- .../property-editor-ui-tree-picker.element.ts | 15 ++--- 4 files changed, 60 insertions(+), 51 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type.data.ts index de4a10e552..3898b179fd 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type.data.ts @@ -204,18 +204,18 @@ export const data: Array = { alias: 'startNode', value: { - type: 'media', + type: 'content', query: '', id: null, }, }, { alias: 'minNumber', - value: 1, + value: 0, }, { alias: 'maxNumber', - value: 10, + value: 3, }, { alias: 'ignoreUserStartNodes', diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.context.ts index 5a37bd3c09..d3ffc05b8e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.context.ts @@ -1,6 +1,6 @@ import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; -import { UMB_DOCUMENT_PICKER_MODAL, UMB_MEDIA_TREE_PICKER_MODAL, UmbModalToken } from '@umbraco-cms/backoffice/modal'; +import { UMB_DOCUMENT_PICKER_MODAL, UMB_MEDIA_TREE_PICKER_MODAL } from '@umbraco-cms/backoffice/modal'; import { ItemResponseModelBaseModel } from '@umbraco-cms/backoffice/backend-api'; export type NodeType = 'content' | 'member' | 'media'; @@ -11,34 +11,26 @@ export type StartNode = { id?: string | null; }; -type ContextCreator = { - repository?: string; - token?: UmbModalToken; -}; - -export class UmbNodePickerContext extends UmbPickerInputContext { +export class UmbNodeTreePickerContext extends UmbPickerInputContext { #type: NodeType = 'content'; - readonly nodeType = this.#type; - constructor(host: UmbControllerHostElement, type?: NodeType) { - const context: ContextCreator = {}; + constructor(host: UmbControllerHostElement, type: 'content' | 'media' | 'member' = 'content') { + const context = { + repository: 'Umb.Repository.Document', + token: UMB_DOCUMENT_PICKER_MODAL, + }; - context.repository = 'Umb.Repository.Document'; - context.token = UMB_DOCUMENT_PICKER_MODAL; + // TODO => if member if (type === 'media') { context.repository = 'Umb.Repository.Media'; context.token = UMB_MEDIA_TREE_PICKER_MODAL; } - if (type === 'member') { - //context.repository = 'Umb.Repository.Member'; - //context.token = UMB_MEMBER_TREE_PICKER_MODAL; - } - super(host, context.repository, context.token); + this.#type = type; } - setNodeType(newNodeType: NodeType) { - this.#type = newNodeType; + getType() { + return this.#type; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts index db51e70fdd..e9f8d5e235 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts @@ -1,8 +1,9 @@ -import { NodeType, UmbNodePickerContext } from './input-tree.context.js'; +import { NodeType, StartNode, UmbNodeTreePickerContext } from './input-tree.context.js'; import { css, html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import type { ItemResponseModelBaseModel } from '@umbraco-cms/backoffice/backend-api'; +import { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; @customElement('umb-input-tree') export class UmbInputTreeElement extends FormControlMixin(UmbLitElement) { @@ -16,12 +17,13 @@ export class UmbInputTreeElement extends FormControlMixin(UmbLitElement) { * @attr * @default 0 */ - @property({ type: Number }) public get min(): number { - return this.#pickerContext.min; + return this.#pickerContext?.min || 0; } public set min(value: number) { - this.#pickerContext.min = value; + if (this.#pickerContext) { + this.#pickerContext.min = value; + } } /** @@ -39,12 +41,13 @@ export class UmbInputTreeElement extends FormControlMixin(UmbLitElement) { * @attr * @default Infinity */ - @property({ type: Number }) public get max(): number { - return this.#pickerContext.max; + return this.#pickerContext?.max || 0; } public set max(value: number) { - this.#pickerContext.max = value; + if (this.#pickerContext) { + this.#pickerContext.max = value; + } } /** @@ -57,10 +60,14 @@ export class UmbInputTreeElement extends FormControlMixin(UmbLitElement) { maxMessage = 'This field exceeds the allowed amount of items'; public get selectedIds(): Array { - return this.#pickerContext.getSelection(); + return this.#pickerContext?.getSelection() ?? []; } public set selectedIds(ids: Array) { - this.#pickerContext.setSelection(ids); + this.#pickerContext?.setSelection(ids); + } + + public get type(): NodeType | undefined { + return this.#pickerContext?.getType(); } @property() @@ -69,22 +76,33 @@ export class UmbInputTreeElement extends FormControlMixin(UmbLitElement) { this.selectedIds = idsString.split(/[ ,]+/); } - @property() - public set type(value: NodeType) { - this.#pickerContext.setNodeType(value); - } - public get type(): NodeType { - return this.#pickerContext.nodeType; + @property({ attribute: false }) + public set configuration(value: UmbPropertyEditorConfigCollection | undefined) { + const config: Record = { + ...(value ? value.toObject() : {}), + }; + + this.#setup(config.startNode.type); + this.min = config.minNumber; + this.max = config.maxNumber; } @state() private _items?: Array; - #pickerContext: UmbNodePickerContext; + #pickerContext?: UmbNodeTreePickerContext; + + #setup(type: NodeType = 'content') { + this.#pickerContext = new UmbNodeTreePickerContext(this, type); + this.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(','))); + this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems)); + } constructor() { super(); + /* + TODO => only if pickrecontext exists this.addValidator( 'rangeUnderflow', () => this.minMessage, @@ -96,11 +114,12 @@ export class UmbInputTreeElement extends FormControlMixin(UmbLitElement) { () => this.maxMessage, () => !!this.max && this.#pickerContext.getSelection().length > this.max, ); + */ - this.#pickerContext = new UmbNodePickerContext(this); + //this.#pickerContext = new UmbNodePickerContext(this, this.type); - this.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(','))); - this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems)); + //this.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(','))); + //this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems)); } render() { @@ -110,10 +129,11 @@ export class UmbInputTreeElement extends FormControlMixin(UmbLitElement) { #renderButton() { if (this._items && this.max && this._items.length >= this.max) return; return html` + items: ${this._items?.length} - max: ${this.max} this.#pickerContext.openPicker()} + @click=${() => this.#pickerContext?.openPicker()} label=${this.localize.term('general_add')}> ${this.localize.term('general_add')} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts index d6527aa80d..00c12e846f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts @@ -1,5 +1,5 @@ import { StartNode } from '../../../components/input-tree/input-tree.context.js'; -import { html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; @@ -33,8 +33,12 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen @state() maxNumber?: number; + #configuration?: UmbPropertyEditorConfigCollection; + @property({ attribute: false }) public set config(config: UmbPropertyEditorConfigCollection | undefined) { + this.#configuration = config; + this.startNode = config?.getValueByAlias('startNode'); this.filter = config?.getValueByAlias('filter'); @@ -51,14 +55,7 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen } render() { - return html``; + return html``; } static styles = [UmbTextStyles]; From bc4c6355a363947a8d39cecab92f5cbeeece6520 Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Thu, 19 Oct 2023 16:24:17 +0200 Subject: [PATCH 04/17] load element depends on type --- .../input-tree/input-tree.context.ts | 36 --- .../input-tree/input-tree.element.ts | 220 +++++++++--------- .../property-editor-ui-tree-picker.element.ts | 36 +-- 3 files changed, 112 insertions(+), 180 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.context.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.context.ts deleted file mode 100644 index d3ffc05b8e..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.context.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; -import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; -import { UMB_DOCUMENT_PICKER_MODAL, UMB_MEDIA_TREE_PICKER_MODAL } from '@umbraco-cms/backoffice/modal'; -import { ItemResponseModelBaseModel } from '@umbraco-cms/backoffice/backend-api'; - -export type NodeType = 'content' | 'member' | 'media'; - -export type StartNode = { - type?: NodeType; - query?: string | null; - id?: string | null; -}; - -export class UmbNodeTreePickerContext extends UmbPickerInputContext { - #type: NodeType = 'content'; - - constructor(host: UmbControllerHostElement, type: 'content' | 'media' | 'member' = 'content') { - const context = { - repository: 'Umb.Repository.Document', - token: UMB_DOCUMENT_PICKER_MODAL, - }; - - // TODO => if member - - if (type === 'media') { - context.repository = 'Umb.Repository.Media'; - context.token = UMB_MEDIA_TREE_PICKER_MODAL; - } - super(host, context.repository, context.token); - this.#type = type; - } - - getType() { - return this.#type; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts index e9f8d5e235..2c63053e83 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts @@ -1,9 +1,16 @@ -import { NodeType, StartNode, UmbNodeTreePickerContext } from './input-tree.context.js'; import { css, html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import type { ItemResponseModelBaseModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; +import { UmbInputDocumentElement } from '@umbraco-cms/backoffice/document'; + +export type NodeType = 'content' | 'member' | 'media'; + +export type StartNode = { + type?: NodeType; + query?: string | null; + id?: string | null; +}; @customElement('umb-input-tree') export class UmbInputTreeElement extends FormControlMixin(UmbLitElement) { @@ -11,70 +18,29 @@ export class UmbInputTreeElement extends FormControlMixin(UmbLitElement) { return undefined; } - /** - * This is a minimum amount of selected items in this input. - * @type {number} - * @attr - * @default 0 - */ - public get min(): number { - return this.#pickerContext?.min || 0; - } - public set min(value: number) { - if (this.#pickerContext) { - this.#pickerContext.min = value; - } - } + @state() + type?: StartNode['type']; - /** - * Min validation message. - * @type {boolean} - * @attr - * @default - */ - @property({ type: String, attribute: 'min-message' }) - minMessage = 'This field need more items'; + @state() + query?: string; - /** - * This is a maximum amount of selected items in this input. - * @type {number} - * @attr - * @default Infinity - */ - public get max(): number { - return this.#pickerContext?.max || 0; - } - public set max(value: number) { - if (this.#pickerContext) { - this.#pickerContext.max = value; - } - } + @state() + startId?: string; - /** - * Max validation message. - * @type {boolean} - * @attr - * @default - */ - @property({ type: String, attribute: 'min-message' }) - maxMessage = 'This field exceeds the allowed amount of items'; + @state() + min = 0; - public get selectedIds(): Array { - return this.#pickerContext?.getSelection() ?? []; - } - public set selectedIds(ids: Array) { - this.#pickerContext?.setSelection(ids); - } + @state() + max = 0; - public get type(): NodeType | undefined { - return this.#pickerContext?.getType(); - } + @state() + filter?: string; - @property() - public set value(idsString: string) { - // Its with full purpose we don't call super.value, as thats being handled by the observation of the context selection. - this.selectedIds = idsString.split(/[ ,]+/); - } + @state() + showOpenButton?: boolean; + + @state() + ignoreUserStartNodes?: boolean; @property({ attribute: false }) public set configuration(value: UmbPropertyEditorConfigCollection | undefined) { @@ -82,80 +48,108 @@ export class UmbInputTreeElement extends FormControlMixin(UmbLitElement) { ...(value ? value.toObject() : {}), }; - this.#setup(config.startNode.type); + this.type = config.startNode.type.toLowerCase() as StartNode['type']; + this.query = config.query; + this.startId = config.startNode.id; + this.min = config.minNumber; this.max = config.maxNumber; + + this.filter = config.filter; + this.showOpenButton = config.showOpenButton; + this.ignoreUserStartNodes = config.ignoreUserStartNodes; + } + + @property() + set value(newValue: string | string[]) { + super.value = newValue; + this.items = newValue.split(','); + } + get value(): string { + return super.value as string; } @state() - private _items?: Array; + items: Array = []; - #pickerContext?: UmbNodeTreePickerContext; - - #setup(type: NodeType = 'content') { - this.#pickerContext = new UmbNodeTreePickerContext(this, type); - this.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(','))); - this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems)); + #onChange(event: CustomEvent) { + this.items = (event.target as UmbInputDocumentElement).selectedIds; + this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true })); } constructor() { super(); - - /* - TODO => only if pickrecontext exists - this.addValidator( - 'rangeUnderflow', - () => this.minMessage, - () => !!this.min && this.#pickerContext.getSelection().length < this.min, - ); - - this.addValidator( - 'rangeOverflow', - () => this.maxMessage, - () => !!this.max && this.#pickerContext.getSelection().length > this.max, - ); - */ - - //this.#pickerContext = new UmbNodePickerContext(this, this.type); - - //this.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(','))); - //this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems)); } render() { - return html` ${this._items?.map((item) => this.#renderItem(item))} ${this.#renderButton()} `; + return html`${this.#renderElement()} +

${this.#renderTip()}

`; } - #renderButton() { - if (this._items && this.max && this._items.length >= this.max) return; - return html` - items: ${this._items?.length} - max: ${this.max} - this.#pickerContext?.openPicker()} - label=${this.localize.term('general_add')}> - ${this.localize.term('general_add')} - - `; + #renderTip() { + if (this.items.length && this.items.length !== this.max) { + if (this.items.length > this.max) { + return `You can only have up to ${this.max} item(s) selected`; + } + if (this.min === this.max && this.min !== 0) { + return `Add ${this.min - this.items.length} more item(s)`; + } + if (this.min === 0 && this.max) { + return `Add up to ${this.max} items`; + } + if (this.min > 0 && this.max > 0) { + return `Add between ${this.min} and ${this.max} item(s)`; + } + if (this.items.length < this.min) { + return `You need to add at least ${this.min} items`; + } + } + return ''; } - #renderItem(item: ItemResponseModelBaseModel) { - const icon = this.type === 'media' ? 'umb:picture' : this.type === 'member' ? 'umb:user' : 'umb:document'; - return html` - - `; + #renderElement() { + switch (this.type) { + case 'content': + return html``; + case 'media': + return html``; + case 'member': + return html` + `; + default: + return html`Type not found`; + } } static styles = [ css` - #add-button { - width: 100%; - } - - uui-icon { - display: block; - margin: 0 auto; + p { + margin: 0; + color: var(--uui-color-border-emphasis); } `, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts index 00c12e846f..58cec9ee8e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts @@ -1,5 +1,4 @@ -import { StartNode } from '../../../components/input-tree/input-tree.context.js'; -import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; @@ -15,47 +14,22 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen @property() value = ''; - @state() - startNode?: StartNode; - - @state() - filter?: string; - - @state() - ignoreUserStartNodes?: boolean; - - @state() - showOpenButton?: boolean; - - @state() - minNumber?: number; - - @state() - maxNumber?: number; - #configuration?: UmbPropertyEditorConfigCollection; @property({ attribute: false }) public set config(config: UmbPropertyEditorConfigCollection | undefined) { this.#configuration = config; - - this.startNode = config?.getValueByAlias('startNode'); - - this.filter = config?.getValueByAlias('filter'); - this.ignoreUserStartNodes = config?.getValueByAlias('ignoreUserStartNodes'); - this.showOpenButton = config?.getValueByAlias('showOpenButton'); - - this.minNumber = config?.getValueByAlias('minNumber'); - this.maxNumber = config?.getValueByAlias('maxNumber'); } #onChange(e: CustomEvent) { - this.value = (e.target as UmbInputTreeElement).value; + this.value = (e.target as UmbInputTreeElement).items.join(','); this.dispatchEvent(new CustomEvent('property-value-change')); } render() { - return html``; + return html`${this.value}`; } static styles = [UmbTextStyles]; From 0cedaa068f91579db2d7da235dcfb6bf9845d210 Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Fri, 20 Oct 2023 10:01:40 +0200 Subject: [PATCH 05/17] move config --- .../input-tree/input-tree.element.ts | 75 ++++++++----------- .../property-editor-ui-tree-picker.element.ts | 57 ++++++++++++-- 2 files changed, 81 insertions(+), 51 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts index 2c63053e83..191da563ac 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts @@ -1,7 +1,6 @@ import { css, html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; import { UmbInputDocumentElement } from '@umbraco-cms/backoffice/document'; export type NodeType = 'content' | 'member' | 'media'; @@ -18,54 +17,44 @@ export class UmbInputTreeElement extends FormControlMixin(UmbLitElement) { return undefined; } - @state() + @property() type?: StartNode['type']; - @state() + @property() query?: string; - @state() - startId?: string; + @property({ type: String }) + startNodeId?: string; - @state() + @property({ type: Number }) min = 0; - @state() + @property({ type: Number }) max = 0; - @state() - filter?: string; + private _filter: Array = []; + @property() + public set filter(value: string) { + this._filter = value.split(','); + } + public get filter(): string { + return this._filter.join(','); + } - @state() + @property({ type: Boolean }) showOpenButton?: boolean; - @state() + @property({ type: Boolean }) ignoreUserStartNodes?: boolean; - @property({ attribute: false }) - public set configuration(value: UmbPropertyEditorConfigCollection | undefined) { - const config: Record = { - ...(value ? value.toObject() : {}), - }; - - this.type = config.startNode.type.toLowerCase() as StartNode['type']; - this.query = config.query; - this.startId = config.startNode.id; - - this.min = config.minNumber; - this.max = config.maxNumber; - - this.filter = config.filter; - this.showOpenButton = config.showOpenButton; - this.ignoreUserStartNodes = config.ignoreUserStartNodes; - } - @property() - set value(newValue: string | string[]) { - super.value = newValue; - this.items = newValue.split(','); + public set value(newValue: string) { + if (newValue) { + super.value = newValue; + this.items = newValue.split(','); + } } - get value(): string { + public get value(): string { return super.value as string; } @@ -73,7 +62,7 @@ export class UmbInputTreeElement extends FormControlMixin(UmbLitElement) { items: Array = []; #onChange(event: CustomEvent) { - this.items = (event.target as UmbInputDocumentElement).selectedIds; + this.value = (event.target as UmbInputDocumentElement).selectedIds.join(','); this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true })); } @@ -113,22 +102,22 @@ export class UmbInputTreeElement extends FormControlMixin(UmbLitElement) { return html``; case 'media': return html``; case 'member': return html` `; default: - return html`Type not found`; + return html`Node type could not be found`; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts index 58cec9ee8e..a77cc3854a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts @@ -1,9 +1,9 @@ -import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; -import { UmbInputTreeElement } from '@umbraco-cms/backoffice/components'; +import { StartNode, UmbInputTreeElement } from '@umbraco-cms/backoffice/components'; /** * @element umb-property-editor-ui-tree-picker @@ -14,11 +14,45 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen @property() value = ''; - #configuration?: UmbPropertyEditorConfigCollection; + @state() + type?: StartNode['type']; + + @state() + query?: string | null; + + @state() + startNodeId?: string | null; + + @state() + min = 0; + + @state() + max = 0; + + @state() + filter?: string | null; + + @state() + showOpenButton?: boolean; + + @state() + ignoreUserStartNodes?: boolean; @property({ attribute: false }) public set config(config: UmbPropertyEditorConfigCollection | undefined) { - this.#configuration = config; + const startNode: StartNode | undefined = config?.getValueByAlias('startNode'); + if (startNode) { + this.type = startNode.type; + this.query = startNode.query; + this.startNodeId = startNode.id; + } + + this.min = config?.getValueByAlias('minNumber') || 0; + this.max = config?.getValueByAlias('maxNumber') || 0; + + this.filter = config?.getValueByAlias('filter'); + this.showOpenButton = config?.getValueByAlias('showOpenButton'); + this.ignoreUserStartNodes = config?.getValueByAlias('ignoreUserStartNodes'); } #onChange(e: CustomEvent) { @@ -27,11 +61,18 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen } render() { - return html`${this.value}`; + return html``; } - static styles = [UmbTextStyles]; } From 8789bfddd8f5f9d52d3a24d9fc422c2f6e235445 Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Fri, 20 Oct 2023 10:53:11 +0200 Subject: [PATCH 06/17] value handling --- .../input-tree/input-tree.element.ts | 54 +++++++------------ .../property-editor-ui-tree-picker.element.ts | 24 ++++----- 2 files changed, 30 insertions(+), 48 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts index 191da563ac..86be8fef13 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts @@ -3,8 +3,7 @@ import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UmbInputDocumentElement } from '@umbraco-cms/backoffice/document'; -export type NodeType = 'content' | 'member' | 'media'; - +type NodeType = 'content' | 'member' | 'media'; export type StartNode = { type?: NodeType; query?: string | null; @@ -17,8 +16,16 @@ export class UmbInputTreeElement extends FormControlMixin(UmbLitElement) { return undefined; } + private _type: StartNode['type'] = undefined; @property() - type?: StartNode['type']; + public set type(newType: StartNode['type']) { + if (newType !== this._type) { + this._type = newType?.toLowerCase() as StartNode['type']; + } + } + public get type(): StartNode['type'] { + return this._type; + } @property() query?: string; @@ -49,17 +56,18 @@ export class UmbInputTreeElement extends FormControlMixin(UmbLitElement) { @property() public set value(newValue: string) { + super.value = newValue; if (newValue) { - super.value = newValue; - this.items = newValue.split(','); + this.selectedIds = newValue.split(','); + } else { + this.selectedIds = []; } } public get value(): string { return super.value as string; } - @state() - items: Array = []; + selectedIds: Array = []; #onChange(event: CustomEvent) { this.value = (event.target as UmbInputDocumentElement).selectedIds.join(','); @@ -71,36 +79,10 @@ export class UmbInputTreeElement extends FormControlMixin(UmbLitElement) { } render() { - return html`${this.#renderElement()} -

${this.#renderTip()}

`; - } - - #renderTip() { - if (this.items.length && this.items.length !== this.max) { - if (this.items.length > this.max) { - return `You can only have up to ${this.max} item(s) selected`; - } - if (this.min === this.max && this.min !== 0) { - return `Add ${this.min - this.items.length} more item(s)`; - } - if (this.min === 0 && this.max) { - return `Add up to ${this.max} items`; - } - if (this.min > 0 && this.max > 0) { - return `Add between ${this.min} and ${this.max} item(s)`; - } - if (this.items.length < this.min) { - return `You need to add at least ${this.min} items`; - } - } - return ''; - } - - #renderElement() { switch (this.type) { case 'content': return html``; case 'media': return html``; case 'member': return html`
`; + return html`${this.value}`; } static styles = [UmbTextStyles]; } From d4fcf8f07da5d40eb4e64d1303073f22ff7c39e3 Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Fri, 20 Oct 2023 12:19:04 +0200 Subject: [PATCH 07/17] start node picker --- .../input-tree/input-tree.element.ts | 2 +- ...ditor-ui-tree-picker-start-node.element.ts | 72 +++++++++++++++++-- .../uis/tree-picker/manifests.ts | 12 +++- 3 files changed, 77 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts index 86be8fef13..54bb7ece29 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree/input-tree.element.ts @@ -1,4 +1,4 @@ -import { css, html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UmbInputDocumentElement } from '@umbraco-cms/backoffice/document'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/config/start-node/property-editor-ui-tree-picker-start-node.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/config/start-node/property-editor-ui-tree-picker-start-node.element.ts index 743c8525f4..bbc99ff003 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/config/start-node/property-editor-ui-tree-picker-start-node.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/config/start-node/property-editor-ui-tree-picker-start-node.element.ts @@ -1,5 +1,10 @@ -import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; -import { UmbTextStyles } from "@umbraco-cms/backoffice/style"; +import { StartNode } from '@umbraco-cms/backoffice/components'; +import { UmbInputDocumentElement } from '@umbraco-cms/backoffice/document'; +import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; +import { UmbInputMediaElement } from '@umbraco-cms/backoffice/media'; +import { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; /** @@ -7,14 +12,67 @@ import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; */ @customElement('umb-property-editor-ui-tree-picker-start-node') export class UmbPropertyEditorUITreePickerStartNodeElement extends UmbLitElement { - @property() - value = ''; + @property({ type: Object }) + value: StartNode = { + type: 'content', + }; - @property({ type: Array, attribute: false }) - public config = []; + @state() + private _list: Array