From 401e734f52c06ee74518aece110388652e7eade1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20M=C3=B8ller=20Jensen?= <26099018+JesmoDev@users.noreply.github.com> Date: Mon, 8 Jan 2024 13:13:46 +1300 Subject: [PATCH 01/19] add story --- .../packages/core/components/table/table.stories.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.stories.ts index bbd06ebb6c..212b566856 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.stories.ts @@ -101,3 +101,14 @@ export const WithHiddenIcons: Story = { }, }, }; + +export const WithHiddenIconsAndDisallowedSelections: Story = { + args: { + items: items, + columns: columns, + config: { + allowSelection: false, + hideIcon: true, + }, + }, +}; From 2ab22e1790bbf61f6a4d204c9ad5fa27de3e324f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20M=C3=B8ller=20Jensen?= <26099018+JesmoDev@users.noreply.github.com> Date: Mon, 8 Jan 2024 13:13:49 +1300 Subject: [PATCH 02/19] fix --- .../src/packages/core/components/table/table.element.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.element.ts index 70a3f182c4..1242c59aa8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.element.ts @@ -155,7 +155,11 @@ export class UmbTableElement extends LitElement { render() { return html` - + 'width: 60px', + )}> ${this._renderHeaderCheckboxCell()} ${this.columns.map((column) => this._renderHeaderCell(column))} From fb93d66ad6dc0c9a81353c2af3e15269907be42f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 18:39:35 +0000 Subject: [PATCH 03/19] Bump typescript from 5.3.2 to 5.3.3 Bumps [typescript](https://github.com/Microsoft/TypeScript) from 5.3.2 to 5.3.3. - [Release notes](https://github.com/Microsoft/TypeScript/releases) - [Commits](https://github.com/Microsoft/TypeScript/compare/v5.3.2...v5.3.3) --- updated-dependencies: - dependency-name: typescript dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- src/Umbraco.Web.UI.Client/package-lock.json | 8 ++++---- src/Umbraco.Web.UI.Client/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 44f780f893..a41d24b123 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -76,7 +76,7 @@ "storybook": "7.6.5", "tiny-glob": "^0.2.9", "tsc-alias": "^1.8.8", - "typescript": "^5.3.2", + "typescript": "^5.3.3", "typescript-json-schema": "^0.62.0", "vite": "^4.4.12", "vite-plugin-static-copy": "^0.17.0", @@ -21611,9 +21611,9 @@ "dev": true }, "node_modules/typescript": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", - "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, "bin": { "tsc": "bin/tsc", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index f3df7fa2e0..2930fa80bd 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -202,7 +202,7 @@ "tiny-glob": "^0.2.9", "tsc-alias": "^1.8.8", "typescript-json-schema": "^0.62.0", - "typescript": "^5.3.2", + "typescript": "^5.3.3", "vite-plugin-static-copy": "^0.17.0", "vite-tsconfig-paths": "^4.2.0", "vite": "^4.4.12", From aa1c4455d63acba560c545a078c7565954fad8cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 18:39:13 +0000 Subject: [PATCH 04/19] Bump marked from 11.1.0 to 11.1.1 Bumps [marked](https://github.com/markedjs/marked) from 11.1.0 to 11.1.1. - [Release notes](https://github.com/markedjs/marked/releases) - [Changelog](https://github.com/markedjs/marked/blob/master/.releaserc.json) - [Commits](https://github.com/markedjs/marked/compare/v11.1.0...v11.1.1) --- updated-dependencies: - dependency-name: marked dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- src/Umbraco.Web.UI.Client/package-lock.json | 8 ++++---- src/Umbraco.Web.UI.Client/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index a41d24b123..e058c10403 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -18,7 +18,7 @@ "element-internals-polyfill": "^1.3.9", "lit": "^2.8.0", "lodash-es": "4.17.21", - "marked": "^11.1.0", + "marked": "^11.1.1", "monaco-editor": "^0.45.0", "rxjs": "^7.8.1", "tinymce": "^6.8.2", @@ -16463,9 +16463,9 @@ } }, "node_modules/marked": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-11.1.0.tgz", - "integrity": "sha512-fvKJWAPEafVj1dwGwcPI5mBB/0pvViL6NlCbNDG1HOIRwwAU/jeMoFxfbRLuirO1wRH7m4yPvBqD/O1wyWvayw==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-11.1.1.tgz", + "integrity": "sha512-EgxRjgK9axsQuUa/oKMx5DEY8oXpKJfk61rT5iY3aRlgU6QJtUcxU5OAymdhCvWvhYcd9FKmO5eQoX8m9VGJXg==", "bin": { "marked": "bin/marked.js" }, diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 2930fa80bd..64502a378c 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -143,7 +143,7 @@ "element-internals-polyfill": "^1.3.9", "lit": "^2.8.0", "lodash-es": "4.17.21", - "marked": "^11.1.0", + "marked": "^11.1.1", "monaco-editor": "^0.45.0", "rxjs": "^7.8.1", "tinymce-i18n": "^23.12.4", From 5295c6fc6d89897a8e7bb968cd710732eb47f6d1 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 4 Jan 2024 16:00:26 +0000 Subject: [PATCH 05/19] Property Editor: Number - set numeric value This was setting the value as a `string`, which would cause issues when used in certain scenarios, e.g. MNTP min/max configuration, the value would assumed to be numeric, but it'd be a string, so conditions like `max === 1` would be false, as it'd be `"1"` instead. Ensuring the value is a `number` before the "property-value-change" event is sent appears to resolve this issue. --- .../uis/number/property-editor-ui-number.element.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/number/property-editor-ui-number.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/number/property-editor-ui-number.element.ts index b7c369276c..548c303821 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/number/property-editor-ui-number.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/number/property-editor-ui-number.element.ts @@ -6,8 +6,8 @@ import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/ @customElement('umb-property-editor-ui-number') export class UmbPropertyEditorUINumberElement extends UmbLitElement implements UmbPropertyEditorUiElement { - @property() - value = ''; + @property({ type: Number }) + value: undefined | number = undefined; @state() private _max?: number; @@ -26,7 +26,7 @@ export class UmbPropertyEditorUINumberElement extends UmbLitElement implements U } private onInput(e: InputEvent) { - this.value = (e.target as HTMLInputElement).value; + this.value = Number((e.target as HTMLInputElement).value); this.dispatchEvent(new CustomEvent('property-value-change')); } From dbbb5067cf0bd73275b3ff46f805a09b49866751 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 4 Jan 2024 15:51:35 +0000 Subject: [PATCH 06/19] Integer schema name correction --- .../packages/core/property-editor/schemas/Umbraco.Integer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/schemas/Umbraco.Integer.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/schemas/Umbraco.Integer.ts index 726fd458d4..e4a699a8b9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/schemas/Umbraco.Integer.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/schemas/Umbraco.Integer.ts @@ -2,7 +2,7 @@ import type { ManifestPropertyEditorSchema } from '@umbraco-cms/backoffice/exten export const manifest: ManifestPropertyEditorSchema = { type: 'propertyEditorSchema', - name: 'Decimal', + name: 'Integer', alias: 'Umbraco.Integer', meta: { defaultPropertyEditorUiAlias: 'Umb.PropertyEditorUi.Integer', From 103c5fc9d7a5835315d4a597ad7df1f27a6f584c Mon Sep 17 00:00:00 2001 From: Markus Johansson Date: Thu, 28 Dec 2023 11:25:48 +0100 Subject: [PATCH 07/19] #1012 label changes for pickers --- .../modal/common/tree-picker/tree-picker-modal.element.ts | 4 ++-- .../components/input-document/input-document.element.ts | 4 ++-- .../media/media/components/input-media/input-media.element.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/tree-picker/tree-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/tree-picker/tree-picker-modal.element.ts index 9cd0f507f9..69868cf5dc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/tree-picker/tree-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/tree-picker/tree-picker-modal.element.ts @@ -51,8 +51,8 @@ export class UmbTreePickerModalElement
- - + +
`; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts index 174b3ef549..504d487406 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts @@ -115,7 +115,7 @@ export class UmbInputDocumentElement extends FormControlMixin(UmbLitElement) { id="add-button" look="placeholder" @click=${() => this.#pickerContext.openPicker()} - label=${this.localize.term('general_add')}>`; + label=${this.localize.term('general_choose')}>`; } private _renderItem(item: DocumentItemResponseModel) { @@ -127,7 +127,7 @@ export class UmbInputDocumentElement extends FormControlMixin(UmbLitElement) { this.#pickerContext.requestRemoveItem(item.id!)} label="Remove document ${item.name}" - >Remove${this.localize.term('general_remove')} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.element.ts index d350b8ac7f..9b802c6757 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.element.ts @@ -101,9 +101,9 @@ export class UmbInputMediaElement extends FormControlMixin(UmbLitElement) { #renderButton() { if (this._items && this.max && this._items.length >= this.max) return; return html` - this.#pickerContext.openPicker()} label="open"> + this.#pickerContext.openPicker()} label=${this.localize.term('general_choose')}> - Add + ${this.localize.term('general_choose')} `; } From c6c45cf6cc1b6772189de5aac14d6d38df59a849 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 20 Dec 2023 17:39:18 +0000 Subject: [PATCH 08/19] [WIP] Adds Member Type picker A placeholder component for ``, for use with the Multinode Treepicker property-editor. Code files were largely duplicated from `umb-document-type-input`. The modal context code hasn't been copied over as it requires substantial work with developing the Member Type repository. --- .../token/member-type-picker-modal.token.ts | 18 ++ .../members/member-types/components/index.ts | 1 + .../input-member-type.context.ts | 13 ++ .../input-member-type.element.ts | 179 ++++++++++++++++++ .../packages/members/member-types/index.ts | 5 + .../members/member-types/manifests.ts | 10 +- .../members/member-types/repository/index.ts | 3 +- 7 files changed, 224 insertions(+), 5 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/modal/token/member-type-picker-modal.token.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/members/member-types/components/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/members/member-types/components/input-member-type/input-member-type.context.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/members/member-types/components/input-member-type/input-member-type.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/members/member-types/index.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/member-type-picker-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/member-type-picker-modal.token.ts new file mode 100644 index 0000000000..cbb0d5f635 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/member-type-picker-modal.token.ts @@ -0,0 +1,18 @@ +import { UmbModalToken, UmbPickerModalValue, UmbTreePickerModalData } from '@umbraco-cms/backoffice/modal'; +import { UmbEntityTreeItemModel } from '@umbraco-cms/backoffice/tree'; + +export type UmbMemberTypePickerModalData = UmbTreePickerModalData; +export type UmbMemberTypePickerModalValue = UmbPickerModalValue; + +export const UMB_MEMBER_TYPE_PICKER_MODAL = new UmbModalToken( + 'Umb.Modal.TreePicker', + { + modal: { + type: 'sidebar', + size: 'small', + }, + data: { + treeAlias: 'Umb.Tree.MemberType', + }, + }, +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-types/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/components/index.ts new file mode 100644 index 0000000000..eacc86c77b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/components/index.ts @@ -0,0 +1 @@ +import './input-member-type/input-member-type.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-types/components/input-member-type/input-member-type.context.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/components/input-member-type/input-member-type.context.ts new file mode 100644 index 0000000000..e2dbb59411 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/components/input-member-type/input-member-type.context.ts @@ -0,0 +1,13 @@ +import { UMB_MEMBER_TYPE_PICKER_MODAL } from '../../../../core/modal/token/member-type-picker-modal.token.js'; +import { UMB_MEMBER_TYPE_REPOSITORY_ALIAS } from '../../repository/index.js'; +import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { MemberTypeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; + +export class UmbMemberTypePickerContext extends UmbPickerInputContext { + constructor(host: UmbControllerHostElement) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + super(host, UMB_MEMBER_TYPE_REPOSITORY_ALIAS, UMB_MEMBER_TYPE_PICKER_MODAL); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-types/components/input-member-type/input-member-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/components/input-member-type/input-member-type.element.ts new file mode 100644 index 0000000000..12e3e64ffe --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/components/input-member-type/input-member-type.element.ts @@ -0,0 +1,179 @@ +import { UmbMemberTypePickerContext } from './input-member-type.context.js'; +import { css, html, customElement, property, state, ifDefined, repeat } from '@umbraco-cms/backoffice/external/lit'; +import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import type { MemberTypeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import { splitStringToArray } from '@umbraco-cms/backoffice/utils'; + +@customElement('umb-input-member-type') +export class UmbMemberTypeInputElement extends FormControlMixin(UmbLitElement) { + /** + * 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 = splitStringToArray(idsString); + } + + @property() + get pickableFilter() { + return this.#pickerContext.pickableFilter; + } + set pickableFilter(newVal) { + this.#pickerContext.pickableFilter = newVal; + } + + @state() + private _items?: Array; + + #pickerContext = new UmbMemberTypePickerContext(this); + + constructor() { + super(); + } + + connectedCallback() { + super.connectedCallback(); + + 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.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(','))); + this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems)); + } + + protected _openPicker() { + this.#pickerContext.openPicker({ + hideTreeRoot: true, + }); + } + + protected getFormElement() { + return undefined; + } + + render() { + return html` + ${this.#renderItems()} + ${this.#renderAddButton()} + `; + } + + #renderItems() { + if (!this._items) return; + // TODO: Add sorting. [LK] + return html` + ${repeat( + this._items, + (item) => item.id, + (item) => this._renderItem(item), + )} + `; + } + + #renderAddButton() { + if (this.max > 0 && this.selectedIds.length >= this.max) return; + return html` + ${this.localize.term('general_choose')} + `; + } + + private _renderItem(item: MemberTypeItemResponseModel) { + if (!item.id) return; + return html` + + + this.#pickerContext.requestRemoveItem(item.id!)} + label="Remove Member Type ${item.name}" + >${this.localize.term('general_remove')} + + + `; + } + + static styles = [ + css` + #add-button { + width: 100%; + } + `, + ]; +} + +export default UmbMemberTypeInputElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-input-member-type': UmbMemberTypeInputElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-types/index.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/index.ts new file mode 100644 index 0000000000..48ebd24242 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/index.ts @@ -0,0 +1,5 @@ +import './components/index.js'; + +export * from './components/index.js'; +export * from './repository/index.js'; +export * from './entity.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-types/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/manifests.ts index c2dbcfe939..47e4ffa227 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-types/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/manifests.ts @@ -1,13 +1,15 @@ +import { manifests as entityActionsManifests } from './entity-actions/manifests.js'; import { manifests as menuItemManifests } from './menu-item/manifests.js'; -import { manifests as treeManifests } from './tree/manifests.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; +import { manifests as treeManifests } from './tree/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; -import { manifests as entityActionManifests } from './entity-actions/manifests.js'; + +import './components/index.js'; export const manifests = [ + ...entityActionsManifests, ...menuItemManifests, - ...treeManifests, ...repositoryManifests, + ...treeManifests, ...workspaceManifests, - ...entityActionManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-types/repository/index.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/repository/index.ts index 8cddbed0b2..8be388a62c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-types/repository/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/repository/index.ts @@ -1 +1,2 @@ -export { UmbMemberTypeRepository } from './member-type.repository.js'; +export * from './member-type.repository.js'; +export * from './manifests.js'; From 550c7710cc3fa271bfe87ce82e9fabfc2a6bf1ee Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 3 Jan 2024 15:29:15 +0000 Subject: [PATCH 09/19] [WIP] Input Member picker A placeholder component for ``, for use with the Multinode Treepicker property-editor. Code files were largely duplicated from `input-document`. The modal context code hasn't been copied over as it requires substantial work with developing the Member repository. --- .../src/packages/members/manifests.ts | 2 + .../members/members/components/index.ts | 3 + .../input-member/input-member.element.ts | 171 ++++++++++++++++++ .../input-member/input-member.stories.ts | 14 ++ .../input-member/input-member.test.ts | 20 ++ .../src/packages/members/members/index.ts | 3 + 6 files changed, 213 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/members/members/components/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/members/members/components/input-member/input-member.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/members/members/components/input-member/input-member.stories.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/members/members/components/input-member/input-member.test.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/members/manifests.ts index f928869b69..a643b8be78 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/manifests.ts @@ -4,6 +4,8 @@ import { manifests as memberGroupManifests } from './member-groups/manifests.js' import { manifests as memberTypeManifests } from './member-types/manifests.js'; import { manifests as memberManifests } from './members/manifests.js'; +import './members/components/index.js'; + export const manifests = [ ...memberSectionManifests, ...menuSectionManifests, diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/members/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/members/members/components/index.ts new file mode 100644 index 0000000000..90f47707f8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/members/members/components/index.ts @@ -0,0 +1,3 @@ +import './input-member/input-member.element.js'; + +export * from './input-member/input-member.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/members/components/input-member/input-member.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/members/components/input-member/input-member.element.ts new file mode 100644 index 0000000000..d1106e1f2e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/members/members/components/input-member/input-member.element.ts @@ -0,0 +1,171 @@ +import { css, html, customElement, property, state, ifDefined, repeat } from '@umbraco-cms/backoffice/external/lit'; +import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import type { MemberItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import { splitStringToArray } from '@umbraco-cms/backoffice/utils'; + +@customElement('umb-input-member') +export class UmbInputMemberElement extends FormControlMixin(UmbLitElement) { + /** + * 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; + return 0; + } + 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; + return Infinity; + } + 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(); + return []; + } + 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 = splitStringToArray(idsString); + } + + @state() + private _items?: Array; + + // TODO: Create the `UmbMemberPickerContext` [LK] + //#pickerContext = new UmbMemberPickerContext(this); + + 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.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(','))); + // this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems)); + } + + protected _openPicker() { + console.log("member.openPicker"); + // this.#pickerContext.openPicker({ + // hideTreeRoot: true, + // }); + } + + protected _requestRemoveItem(item: MemberItemResponseModel){ + console.log("member.requestRemoveItem", item); + //this.#pickerContext.requestRemoveItem(item.id!); + } + + protected getFormElement() { + return undefined; + } + + render() { + return html` + ${this.#renderItems()} + ${this.#renderAddButton()} + `; + } + + #renderItems() { + if (!this._items) return; + // TODO: Add sorting. [LK] + return html`${repeat( + this._items, + (item) => item.id, + (item) => this._renderItem(item), + )} + `; + } + + #renderAddButton() { + if (this.max > 0 && this.selectedIds.length >= this.max) return; + return html``; + } + + private _renderItem(item: MemberItemResponseModel) { + if (!item.id) return; + return html` + + + + this._requestRemoveItem(item)} + label="Remove member ${item.name}" + >Remove + + + `; + } + + static styles = [ + css` + #add-button { + width: 100%; + } + `, + ]; +} + +export default UmbInputMemberElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-input-member': UmbInputMemberElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/members/components/input-member/input-member.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/members/members/components/input-member/input-member.stories.ts new file mode 100644 index 0000000000..7bca7fc5db --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/members/members/components/input-member/input-member.stories.ts @@ -0,0 +1,14 @@ +import { Meta, StoryObj } from '@storybook/web-components'; +import './input-member.element.js'; +import type { UmbInputMemberElement } from './input-member.element.js'; + +const meta: Meta = { + title: 'Components/Inputs/Member', + component: 'umb-input-member', +}; + +export default meta; +type Story = StoryObj; +export const Overview: Story = { + args: {}, +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/members/components/input-member/input-member.test.ts b/src/Umbraco.Web.UI.Client/src/packages/members/members/components/input-member/input-member.test.ts new file mode 100644 index 0000000000..7bef94720e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/members/members/components/input-member/input-member.test.ts @@ -0,0 +1,20 @@ +import { expect, fixture, html } from '@open-wc/testing'; +import { UmbInputMemberElement } from './input-member.element.js'; +import { defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; +describe('UmbInputMemberElement', () => { + let element: UmbInputMemberElement; + + beforeEach(async () => { + element = await fixture(html` `); + }); + + it('is defined with its own instance', () => { + expect(element).to.be.instanceOf(UmbInputMemberElement); + }); + + if ((window as any).__UMBRACO_TEST_RUN_A11Y_TEST) { + it('passes the a11y audit', async () => { + await expect(element).shadowDom.to.be.accessible(defaultA11yConfig); + }); + } +}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/members/index.ts b/src/Umbraco.Web.UI.Client/src/packages/members/members/index.ts index 3d76f338dd..f23e6176e5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/members/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/members/index.ts @@ -1 +1,4 @@ +import './components/index.js'; + +export * from './components/index.js'; export * from './repository/index.js'; From d67f4f26effdcad74b20e5b7be85470459893cc2 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 3 Jan 2024 12:04:35 +0000 Subject: [PATCH 10/19] Selection manager: fixes initial value When the modal initially opens, the `#multiple` is `false`, (regardless of how it is configured), so the initial value is set to `[undefined]`. Then when the `#multiple` is observed as `true` (there must be an underlying bug with the modal context code here), then the `#selection` array already has an initial value of `[undefined]` so will append newly selected values to that array. I discovered this issue due to a bug with the Tree Picker editor. --- .../src/shared/utils/selection-manager/selection.manager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection.manager.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection.manager.ts index b71f1a66d6..ff63bfd93d 100644 --- a/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection.manager.ts @@ -57,7 +57,7 @@ export class UmbSelectionManager extends UmbBaseController { public setSelection(value: Array) { if (this.getSelectable() === false) return; if (value === undefined) throw new Error('Value cannot be undefined'); - const newSelection = this.getMultiple() ? value : [value[0]]; + const newSelection = this.getMultiple() ? value : value.length > 0 ? [value[0]] : value; this.#selection.next(newSelection); } @@ -78,7 +78,7 @@ export class UmbSelectionManager extends UmbBaseController { public setMultiple(value: boolean) { this.#multiple.next(value); - /* If multiple is set to false, and the current selection is more than one, + /* If multiple is set to false, and the current selection is more than one, then we need to set the selection to the first item. */ if (value === false && this.getSelection().length > 1) { this.setSelection([this.getSelection()[0]]); From 877274cac47d30114c1b75d418817ee4159bcc56 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 4 Jan 2024 15:36:19 +0000 Subject: [PATCH 11/19] Refactored the initial selection value --- .../src/shared/utils/selection-manager/selection.manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection.manager.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection.manager.ts index ff63bfd93d..477a6d3daa 100644 --- a/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection.manager.ts @@ -57,7 +57,7 @@ export class UmbSelectionManager extends UmbBaseController { public setSelection(value: Array) { if (this.getSelectable() === false) return; if (value === undefined) throw new Error('Value cannot be undefined'); - const newSelection = this.getMultiple() ? value : value.length > 0 ? [value[0]] : value; + const newSelection = this.getMultiple() ? value : value.slice(0, 1); this.#selection.next(newSelection); } From 0e7fca53634e780222288be4f069fda77cac360e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20M=C3=B8ller=20Jensen?= <26099018+JesmoDev@users.noreply.github.com> Date: Wed, 13 Dec 2023 11:53:56 +1300 Subject: [PATCH 12/19] Dont do full page reload when creating language from the entity action button --- .../entity-actions/language-create-entity-action.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/languages/entity-actions/language-create-entity-action.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/languages/entity-actions/language-create-entity-action.ts index 908018aa52..aae26d3732 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/languages/entity-actions/language-create-entity-action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/languages/entity-actions/language-create-entity-action.ts @@ -8,8 +8,8 @@ export class UmbLanguageCreateEntityAction extends UmbEntityActionBase Date: Tue, 19 Dec 2023 16:11:36 +0000 Subject: [PATCH 13/19] Added `input-document-source` element This is different to `input-document` as it will also contain the DynamicRoot functionality. --- .../documents/documents/components/index.ts | 1 + .../input-document-source.element.ts | 108 ++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-source/input-document-source.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/index.ts index a644a4c547..ce63360331 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/index.ts @@ -1,2 +1,3 @@ export * from './input-document/input-document.element.js'; export * from './input-document-granular-permission/input-document-granular-permission.element.js'; +export * from './input-document-source/input-document-source.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-source/input-document-source.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-source/input-document-source.element.ts new file mode 100644 index 0000000000..188d196417 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-source/input-document-source.element.ts @@ -0,0 +1,108 @@ +import { UmbDocumentPickerContext } from '../input-document/input-document.context.js'; +import { css, html, customElement, property, state, ifDefined, repeat } from '@umbraco-cms/backoffice/external/lit'; +import { FormControlMixin, UUIButtonElement } from '@umbraco-cms/backoffice/external/uui'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import type { DocumentItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; + +@customElement('umb-input-document-source') +export class UmbInputDocumentSourceElement extends FormControlMixin(UmbLitElement) { + public get nodeId(): string | null | undefined { + return this.#pickerContext.getSelection()[0]; + } + public set nodeId(id: string | null | undefined) { + const selection = id ? [id] : []; + this.#pickerContext.setSelection(selection); + } + + @property() + public set value(id: string) { + this.nodeId = id; + } + + @state() + private _items?: Array; + + #pickerContext = new UmbDocumentPickerContext(this); + + constructor() { + super(); + + this.#pickerContext.max = 1; + + this.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(','))); + this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems)); + } + + protected getFormElement() { + return undefined; + } + + // TODO: Wire up the DynamicRoot picker feature. [LK] + private _openDynamicRootPicker(e: Event) { + console.log('openDynamicRootPicker', e); + const btn = e.target as UUIButtonElement; + btn.color = 'warning'; + btn.label = 'TODO!'; + btn.look = 'primary'; + } + + render() { + return html` + ${this._items + ? html` ${repeat( + this._items, + (item) => item.id, + (item) => this._renderItem(item), + )} + ` + : ''} + ${this.#renderButtons()} + `; + } + + #renderButtons() { + if (this.nodeId) return; + + //TODO: Dynamic paths + return html` + this.#pickerContext.openPicker()} + label=${this.localize.term('contentPicker_defineRootNode')}> + + `; + } + + private _renderItem(item: DocumentItemResponseModel) { + if (!item.id) return; + return html` + + + + this.#pickerContext.openPicker()} label="Edit document ${item.name}" + >Edit + this.#pickerContext.requestRemoveItem(item.id!)} + label="Remove document ${item.name}" + >Remove + + + `; + } + + static styles = [css``]; +} + +export default UmbInputDocumentSourceElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-input-document-source': UmbInputDocumentSourceElement; + } +} From 78715d0bfc1d41923a080f13707be35c97ee522e Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 19 Dec 2023 16:12:24 +0000 Subject: [PATCH 14/19] Added localized keys/terms for the button labels --- src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts | 2 ++ src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts index df0a6a2e45..d3b00b4443 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts @@ -1151,6 +1151,8 @@ export default { }, contentPicker: { allowedItemTypes: 'Du kan kun vælge følgende type(r) dokumenter: %0%', + defineDynamicRoot: 'Definer Dynamisk Udgangspunkt', + defineRootNode: 'Vælg udgangspunkt', pickedTrashedItem: 'Du har valgt et dokument som er slettet eller lagt i papirkurven', pickedTrashedItems: 'Du har valgt dokumenter som er slettede eller lagt i papirkurven', }, diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts index 19de39092d..cfbeba5478 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts @@ -1148,6 +1148,8 @@ export default { }, contentPicker: { allowedItemTypes: 'You can only select items of type(s): %0%', + defineDynamicRoot: 'Specify a Dynamic Root', + defineRootNode: 'Pick root node', pickedTrashedItem: 'You have picked a content item currently deleted or in the recycle bin', pickedTrashedItems: 'You have picked content items currently deleted or in the recycle bin', }, From 6a9574b18a4bd1fb8c1f4290b1ca48b231a207a9 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 19 Dec 2023 16:18:15 +0000 Subject: [PATCH 15/19] Updates to `start-node` elements to use the new `input-document-source` element. Extended the `StartNode` type to include the DynamicRoot schema. --- .../input-start-node.element.ts | 64 ++++++++++++++----- ...ditor-ui-tree-picker-start-node.element.ts | 8 ++- 2 files changed, 52 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-start-node/input-start-node.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-start-node/input-start-node.element.ts index d6db6cc9c5..d5392ca592 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-start-node/input-start-node.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-start-node/input-start-node.element.ts @@ -1,16 +1,26 @@ -import { UmbInputDocumentElement } from '@umbraco-cms/backoffice/document'; +import { UmbInputDocumentSourceElement } from '@umbraco-cms/backoffice/document'; import { html, customElement, property, css, state } from '@umbraco-cms/backoffice/external/lit'; import { FormControlMixin, UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UmbInputMediaElement } from '@umbraco-cms/backoffice/media'; -import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +//import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; export type ContentType = 'content' | 'member' | 'media'; +export type DynamicRootQueryStepType = { + alias: string; + anyOfDocTypeKeys: Array; +}; + +export type DynamicRootType = { + originAlias: string; + querySteps?: Array | null; +}; + export type StartNode = { type?: ContentType; id?: string | null; - query?: string | null; + dynamicRoot?: DynamicRootType | null; }; @customElement('umb-input-start-node') @@ -20,14 +30,21 @@ export class UmbInputStartNodeElement extends FormControlMixin(UmbLitElement) { } private _type: StartNode['type'] = 'content'; + @property() public set type(value: StartNode['type']) { + if (value === undefined) { + value = this._type; + } + const oldValue = this._type; this._options = this._options.map((option) => option.value === value ? { ...option, selected: true } : { ...option, selected: false }, ); + this._type = value; + this.requestUpdate('type', oldValue); } public get type(): StartNode['type'] { @@ -35,30 +52,43 @@ export class UmbInputStartNodeElement extends FormControlMixin(UmbLitElement) { } @property({ attribute: 'node-id' }) - nodeId = ''; + nodeId?: string | null; - @property({ attribute: 'dynamic-path' }) - dynamicPath = ''; + @property({ attribute: false }) + dynamicRoot?: DynamicRootType | null; @state() _options: Array