From 25212fa3f06f288fd961a410ba69e023274b1ec9 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 8 May 2023 10:01:37 +0200 Subject: [PATCH 1/8] add selection manager --- src/Umbraco.Web.UI.Client/libs/utils/index.ts | 1 + .../libs/utils/selection-manager.ts | 61 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/libs/utils/selection-manager.ts diff --git a/src/Umbraco.Web.UI.Client/libs/utils/index.ts b/src/Umbraco.Web.UI.Client/libs/utils/index.ts index 8d9fbe8634..ec8b2470c9 100644 --- a/src/Umbraco.Web.UI.Client/libs/utils/index.ts +++ b/src/Umbraco.Web.UI.Client/libs/utils/index.ts @@ -1,2 +1,3 @@ export * from './umbraco-path'; export * from './udi-service'; +export * from './selection-manager'; diff --git a/src/Umbraco.Web.UI.Client/libs/utils/selection-manager.ts b/src/Umbraco.Web.UI.Client/libs/utils/selection-manager.ts new file mode 100644 index 0000000000..606dc98bcd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/libs/utils/selection-manager.ts @@ -0,0 +1,61 @@ +import { Observable } from 'rxjs'; +import { UmbArrayState, UmbBooleanState } from '../observable-api'; + +export interface UmbSelectionManager { + selection: Observable>; + multiple: Observable; + + getSelection(): Array; + setSelection(value: Array): void; + + getMultiple(): boolean; + setMultiple(value: boolean): void; + + toggleSelect(unique: string | null): void; + select(unique: string | null): void; + deselect(unique: string | null): void; + isSelected(unique: string | null): boolean; +} + +export class UmbSelectionManagerBase implements UmbSelectionManager { + #selection = new UmbArrayState(>[]); + public readonly selection = this.#selection.asObservable(); + + #multiple = new UmbBooleanState(false); + public readonly multiple = this.#multiple.asObservable(); + + public getSelection() { + return this.#selection.getValue(); + } + + public setSelection(value: Array) { + if (value === undefined) throw new Error('Value cannot be undefined'); + this.#selection.next(value); + } + + public getMultiple() { + return this.#multiple.getValue(); + } + + public setMultiple(value: boolean) { + this.#multiple.next(value); + } + + public toggleSelect(unique: string | null) { + this.isSelected(unique) ? this.deselect(unique) : this.select(unique); + } + + public select(unique: string | null) { + const newSelection = this.getMultiple() ? [...this.getSelection(), unique] : [unique]; + this.#selection.next(newSelection); + } + + public deselect(unique: string | null) { + const newSelection = this.getSelection().filter((x) => x !== unique); + this.#selection.next(newSelection); + } + + public isSelected(unique: string | null) { + return this.getSelection().includes(unique); + } +} From 3efa0eb877cf40180c499d6494b7768f459243f3 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 8 May 2023 10:02:10 +0200 Subject: [PATCH 2/8] use selection manager in tree --- .../modal/token/section-picker-modal.token.ts | 17 +++++++--- .../core/components/tree/tree.context.ts | 31 +++++++++---------- .../core/components/tree/tree.element.ts | 1 + 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/libs/modal/token/section-picker-modal.token.ts b/src/Umbraco.Web.UI.Client/libs/modal/token/section-picker-modal.token.ts index c74346cc70..1ded522931 100644 --- a/src/Umbraco.Web.UI.Client/libs/modal/token/section-picker-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/libs/modal/token/section-picker-modal.token.ts @@ -2,10 +2,17 @@ import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; export interface UmbSectionPickerModalData { multiple: boolean; - selection: string[]; + selection: Array; } -export const UMB_SECTION_PICKER_MODAL = new UmbModalToken('Umb.Modal.SectionPicker', { - type: 'sidebar', - size: 'small', -}); +export interface UmbSectionPickerModalResult { + selection: Array; +} + +export const UMB_SECTION_PICKER_MODAL = new UmbModalToken( + 'Umb.Modal.SectionPicker', + { + type: 'sidebar', + size: 'small', + } +); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/core/components/tree/tree.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/core/components/tree/tree.context.ts index f9102ef0bd..d5ae1bfbe1 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/core/components/tree/tree.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/core/components/tree/tree.context.ts @@ -1,19 +1,23 @@ import { Observable, map } from 'rxjs'; import { UmbPagedData, UmbTreeRepository } from '@umbraco-cms/backoffice/repository'; import type { ManifestTree } from '@umbraco-cms/backoffice/extensions-registry'; -import { UmbBooleanState, UmbArrayState, UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; +import { UmbBooleanState, UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; import { createExtensionClass, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extensions-api'; import { ProblemDetailsModel, TreeItemPresentationModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbContextProviderController } from '@umbraco-cms/backoffice/context-api'; +import { UmbSelectionManagerBase } from '@umbraco-cms/backoffice/utils'; // TODO: update interface export interface UmbTreeContext { readonly selectable: Observable; readonly selection: Observable>; setSelectable(value: boolean): void; + getSelectable(): boolean; setMultiple(value: boolean): void; + getMultiple(): boolean; setSelection(value: Array): void; + getSelection(): Array; select(unique: string | null): void; deselect(unique: string | null): void; requestChildrenOf: (parentUnique: string | null) => Promise<{ @@ -28,18 +32,16 @@ export class UmbTreeContextBase { public host: UmbControllerHostElement; + #selectionManager = new UmbSelectionManagerBase(); + #selectable = new UmbBooleanState(false); public readonly selectable = this.#selectable.asObservable(); - #multiple = new UmbBooleanState(false); - public readonly multiple = this.#multiple.asObservable(); - - #selection = new UmbArrayState(>[]); - public readonly selection = this.#selection.asObservable(); + public readonly multiple = this.#selectionManager.multiple; + public readonly selection = this.#selectionManager.selection; #treeAlias?: string; repository?: UmbTreeRepository; - #treeManifestObserver?: UmbObserverController; #initResolver?: () => void; @@ -84,32 +86,29 @@ export class UmbTreeContextBase } public setMultiple(value: boolean) { - this.#multiple.next(value); + this.#selectionManager.setMultiple(value); } public getMultiple() { - return this.#multiple.getValue(); + return this.#selectionManager.getMultiple(); } public setSelection(value: Array) { - if (!value) return; - this.#selection.next(value); + this.#selectionManager.setSelection(value); } public getSelection() { - return this.#selection.getValue(); + return this.#selectionManager.getSelection(); } public select(unique: string | null) { if (!this.getSelectable()) return; - const newSelection = this.getMultiple() ? [...this.getSelection(), unique] : [unique]; - this.#selection.next(newSelection); + this.#selectionManager.select(unique); this.host.dispatchEvent(new CustomEvent('selected')); } public deselect(unique: string | null) { - const newSelection = this.getSelection().filter((x) => x !== unique); - this.#selection.next(newSelection); + this.#selectionManager.deselect(unique); this.host.dispatchEvent(new CustomEvent('selected')); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/core/components/tree/tree.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/core/components/tree/tree.element.ts index 304fc8d38e..ad8135b515 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/core/components/tree/tree.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/core/components/tree/tree.element.ts @@ -34,6 +34,7 @@ export class UmbTreeElement extends UmbLitElement { return this.#treeContext.getSelection(); } set selection(newVal) { + if (!Array.isArray(newVal)) return; this.#treeContext?.setSelection(newVal); } From b3cbc145b19c47315bfc0ffe0ff6d5f00f7d4746 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 8 May 2023 16:09:08 +0200 Subject: [PATCH 3/8] use selection manager in section picker + use uui-menu-item --- .../section-picker-modal.element.ts | 108 +++++++----------- 1 file changed, 40 insertions(+), 68 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/core/modals/section-picker/section-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/core/modals/section-picker/section-picker-modal.element.ts index b1a17a4676..3c38c9175f 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/core/modals/section-picker/section-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/core/modals/section-picker/section-picker-modal.element.ts @@ -1,96 +1,68 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; -import { UmbModalElementPickerBase } from '@umbraco-cms/internal/modal'; +import { UmbSelectionManagerBase } from '@umbraco-cms/backoffice/utils'; +import { UmbModalBaseElement } from '@umbraco-cms/internal/modal'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extensions-api'; import type { ManifestSection } from '@umbraco-cms/backoffice/extensions-registry'; +import { UmbSectionPickerModalData, UmbSectionPickerModalResult } from '@umbraco-cms/backoffice/modal'; @customElement('umb-section-picker-modal') -export class UmbSectionPickerModalElement extends UmbModalElementPickerBase { - - +export class UmbSectionPickerModalElement extends UmbModalBaseElement< + UmbSectionPickerModalData, + UmbSectionPickerModalResult +> { @state() private _sections: Array = []; + #selectionManager = new UmbSelectionManagerBase(); + + #submit() { + this.modalHandler?.submit({ + selection: this.#selectionManager.getSelection(), + }); + } + + #close() { + this.modalHandler?.reject(); + } + connectedCallback(): void { super.connectedCallback(); - this.observe(umbExtensionsRegistry.extensionsOfType('section'), (sections: Array) => { - this._sections = sections; - }); + + // TODO: in theory this config could change during the lifetime of the modal, so we could observe it + this.#selectionManager.setMultiple(false); + + this.observe( + umbExtensionsRegistry.extensionsOfType('section'), + (sections: Array) => (this._sections = sections) + ); } render() { return html` - -
-
- ${this._sections.map( - (item) => html` -
this.handleSelection(item.alias)} - @keydown=${(e: KeyboardEvent) => this._handleKeydown(e, item.alias)} - class=${this.isSelected(item.alias) ? 'item selected' : 'item'}> - ${item.meta.label} -
- ` - )} -
+ ${this._sections.map( + (item) => html` + this.#selectionManager.select(item.alias)} + @unselected=${() => this.#selectionManager.deselect(item.alias)}> + ` + )}
- - + +
`; } - static styles = [ - UUITextStyles, - css` - uui-input { - width: 100%; - } - hr { - border: none; - border-bottom: 1px solid var(--uui-color-divider); - margin: 16px 0; - } - #item-list { - display: flex; - flex-direction: column; - gap: var(--uui-size-1); - } - .item { - color: var(--uui-color-interactive); - display: grid; - grid-template-columns: var(--uui-size-8) 1fr; - padding: var(--uui-size-4) var(--uui-size-2); - gap: var(--uui-size-space-5); - align-items: center; - border-radius: var(--uui-border-radius); - cursor: pointer; - } - .item.selected { - background-color: var(--uui-color-selected); - color: var(--uui-color-selected-contrast); - } - .item:not(.selected):hover { - background-color: var(--uui-color-surface-emphasis); - color: var(--uui-color-interactive-emphasis); - } - .item.selected:hover { - background-color: var(--uui-color-selected-emphasis); - } - .item uui-icon { - width: 100%; - box-sizing: border-box; - display: flex; - height: fit-content; - } - `, - ]; + static styles = [UUITextStyles, css``]; } export default UmbSectionPickerModalElement; From 3b309322c286b4f6116f62662e6e40902c18f8d0 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 8 May 2023 19:30:56 +0200 Subject: [PATCH 4/8] use selection manager --- .../token/language-picker-modal.token.ts | 4 +- .../language-picker-modal.element.ts | 49 ++++++++++--------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/libs/modal/token/language-picker-modal.token.ts b/src/Umbraco.Web.UI.Client/libs/modal/token/language-picker-modal.token.ts index fb47287bd9..7cb3cb2b8b 100644 --- a/src/Umbraco.Web.UI.Client/libs/modal/token/language-picker-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/libs/modal/token/language-picker-modal.token.ts @@ -3,12 +3,12 @@ import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; export interface UmbLanguagePickerModalData { multiple?: boolean; - selection?: Array; + selection?: Array; filter?: (language: LanguageResponseModel) => boolean; } export interface UmbLanguagePickerModalResult { - selection: Array; + selection: Array; } export const UMB_LANGUAGE_PICKER_MODAL = new UmbModalToken( diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/modals/language-picker/language-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/modals/language-picker/language-picker-modal.element.ts index 968d766cd7..a494344837 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/modals/language-picker/language-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/modals/language-picker/language-picker-modal.element.ts @@ -2,34 +2,28 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { repeat } from 'lit/directives/repeat.js'; -import { UUIMenuItemElement, UUIMenuItemEvent } from '@umbraco-ui/uui'; -import { ifDefined } from 'lit/directives/if-defined.js'; import { UmbLanguageRepository } from '../../repository/language.repository'; -import { UmbModalElementPickerBase } from '@umbraco-cms/internal/modal'; +import { UmbModalBaseElement } from '@umbraco-cms/internal/modal'; import { LanguageResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import { UmbSelectionManagerBase } from '@umbraco-cms/backoffice/utils'; +import { UmbLanguagePickerModalResult, UmbLanguagePickerModalData } from '@umbraco-cms/backoffice/modal'; @customElement('umb-language-picker-modal') -export class UmbLanguagePickerModalElement extends UmbModalElementPickerBase { - - +export class UmbLanguagePickerModalElement extends UmbModalBaseElement< + UmbLanguagePickerModalData, + UmbLanguagePickerModalResult +> { @state() private _languages: Array = []; - private _languageRepository = new UmbLanguageRepository(this); + #languageRepository = new UmbLanguageRepository(this); + #selectionManager = new UmbSelectionManagerBase(); async firstUpdated() { - const { data } = await this._languageRepository.requestLanguages(); + const { data } = await this.#languageRepository.requestLanguages(); this._languages = data?.items ?? []; } - #onSelection(event: UUIMenuItemEvent) { - event?.stopPropagation(); - const language = event?.target as UUIMenuItemElement; - const isoCode = language.dataset.isoCode; - if (!isoCode) return; - this.handleSelection(isoCode); - } - get #filteredLanguages() { if (this.data?.filter) { return this._languages.filter(this.data.filter); @@ -38,6 +32,14 @@ export class UmbLanguagePickerModalElement extends UmbModalElementPickerBase @@ -47,23 +49,22 @@ export class UmbLanguagePickerModalElement extends UmbModalElementPickerBase html` + selectable + @selected=${() => this.#selectionManager.select(item.isoCode!)} + @unselected=${() => this.#selectionManager.deselect(item.isoCode!)} + ?selected=${this.#selectionManager.isSelected(item.isoCode!)}> ` )}
- - + +
`; } - + static styles = [UUITextStyles, css``]; } From ebb8f7d38a3c5564061899d3de711bbf3827eb4a Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 8 May 2023 19:35:47 +0200 Subject: [PATCH 5/8] set pre selection --- .../modals/section-picker/section-picker-modal.element.ts | 3 ++- .../modals/language-picker/language-picker-modal.element.ts | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/core/modals/section-picker/section-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/core/modals/section-picker/section-picker-modal.element.ts index 3c38c9175f..a748639e8b 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/core/modals/section-picker/section-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/core/modals/section-picker/section-picker-modal.element.ts @@ -31,7 +31,8 @@ export class UmbSectionPickerModalElement extends UmbModalBaseElement< super.connectedCallback(); // TODO: in theory this config could change during the lifetime of the modal, so we could observe it - this.#selectionManager.setMultiple(false); + this.#selectionManager.setMultiple(this.data?.multiple ?? false); + this.#selectionManager.setSelection(this.data?.selection ?? []); this.observe( umbExtensionsRegistry.extensionsOfType('section'), diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/modals/language-picker/language-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/modals/language-picker/language-picker-modal.element.ts index a494344837..cbcff66eff 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/modals/language-picker/language-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/modals/language-picker/language-picker-modal.element.ts @@ -19,6 +19,12 @@ export class UmbLanguagePickerModalElement extends UmbModalBaseElement< #languageRepository = new UmbLanguageRepository(this); #selectionManager = new UmbSelectionManagerBase(); + connectedCallback(): void { + super.connectedCallback(); + this.#selectionManager.setMultiple(this.data?.multiple ?? false); + this.#selectionManager.setSelection(this.data?.selection ?? []); + } + async firstUpdated() { const { data } = await this.#languageRepository.requestLanguages(); this._languages = data?.items ?? []; From 44e3fdc4a7401f7729592dcf998ac6899aba58e9 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 8 May 2023 19:44:46 +0200 Subject: [PATCH 6/8] use selection manager in user picker --- .../modal/token/user-picker-modal.token.ts | 2 +- .../user-picker/user-picker-modal.element.ts | 34 ++++++++----------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/libs/modal/token/user-picker-modal.token.ts b/src/Umbraco.Web.UI.Client/libs/modal/token/user-picker-modal.token.ts index 43cced8222..51eaf7d76f 100644 --- a/src/Umbraco.Web.UI.Client/libs/modal/token/user-picker-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/libs/modal/token/user-picker-modal.token.ts @@ -4,7 +4,7 @@ import { UserResponseModel } from '@umbraco-cms/backoffice/backend-api'; export type UmbUserPickerModalData = UmbPickerModalData; export interface UmbUserPickerModalResult { - selection: Array; + selection: Array; } export const UMB_USER_PICKER_MODAL = new UmbModalToken( diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/user-picker/user-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/user-picker/user-picker-modal.element.ts index 3b806610de..a3902aa277 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/user-picker/user-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/user-picker/user-picker-modal.element.ts @@ -1,30 +1,20 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; import { css, html } from 'lit'; -import { customElement, property, state } from 'lit/decorators.js'; +import { customElement, state } from 'lit/decorators.js'; import { UmbUserRepository } from '../../repository/user.repository'; -import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import { UmbModalHandler, UmbUserPickerModalData, UmbUserPickerModalResult } from '@umbraco-cms/backoffice/modal'; +import { UmbUserPickerModalData, UmbUserPickerModalResult } from '@umbraco-cms/backoffice/modal'; import { createExtensionClass, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extensions-api'; import { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; import { UserResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import { UmbModalBaseElement } from '@umbraco-cms/internal/modal'; +import { UmbSelectionManagerBase } from '@umbraco-cms/backoffice/utils'; @customElement('umb-user-picker-modal') -export class UmbUserPickerModalElement extends UmbLitElement { - @property({ attribute: false }) - modalHandler?: UmbModalHandler; - - @property({ type: Object, attribute: false }) - data?: UmbUserPickerModalData; - - @state() - _selection: Array = []; - - @state() - _multiple = false; - +export class UmbUserPickerModalElement extends UmbModalBaseElement { @state() private _users: Array = []; + #selectionManager = new UmbSelectionManagerBase(); #userRepository?: UmbUserRepository; constructor() { @@ -59,7 +49,7 @@ export class UmbUserPickerModalElement extends UmbLitElement { } #submit() { - this.modalHandler?.submit({ selection: this._selection }); + this.modalHandler?.submit({ selection: this.#selectionManager.getSelection() }); } #close() { @@ -72,10 +62,14 @@ export class UmbUserPickerModalElement extends UmbLitElement { ${this._users.map( (user) => html` - + this.#selectionManager.select(user.id!)} + @unselected=${() => this.#selectionManager.deselect(user.id!)} + ?selected=${this.#selectionManager.isSelected(user.id!)}> - Hello + ` )} From 877141e9d9741b413d2dffe318bdb439d943cb6a Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 8 May 2023 19:53:25 +0200 Subject: [PATCH 7/8] use selection manager --- .../user-group-picker-modal.element.ts | 98 +++++++------------ 1 file changed, 34 insertions(+), 64 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/modals/user-group-picker/user-group-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/modals/user-group-picker/user-group-picker-modal.element.ts index 6e1d3d7518..f47ba36f5d 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/modals/user-group-picker/user-group-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/modals/user-group-picker/user-group-picker-modal.element.ts @@ -3,17 +3,24 @@ import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { UmbUserGroupStore, UMB_USER_GROUP_STORE_CONTEXT_TOKEN } from '../../repository/user-group.store'; import type { UserGroupDetails } from '../../types'; -import { UmbModalElementPickerBase } from '@umbraco-cms/internal/modal'; +import { UmbSelectionManagerBase } from '@umbraco-cms/backoffice/utils'; +import { UmbModalBaseElement } from '@umbraco-cms/internal/modal'; @customElement('umb-user-group-picker-modal') -export class UmbUserGroupPickerModalElement extends UmbModalElementPickerBase { +export class UmbUserGroupPickerModalElement extends UmbModalBaseElement { @state() private _userGroups: Array = []; private _userGroupStore?: UmbUserGroupStore; + #selectionManager = new UmbSelectionManagerBase(); connectedCallback(): void { super.connectedCallback(); + + // TODO: in theory this config could change during the lifetime of the modal, so we could observe it + this.#selectionManager.setMultiple(this.data?.multiple ?? false); + this.#selectionManager.setSelection(this.data?.selection ?? []); + this.consumeContext(UMB_USER_GROUP_STORE_CONTEXT_TOKEN, (userGroupStore) => { this._userGroupStore = userGroupStore; this._observeUserGroups(); @@ -25,79 +32,42 @@ export class UmbUserGroupPickerModalElement extends UmbModalElementPickerBase (this._userGroups = userGroups)); } + #submit() { + this.modalHandler?.submit({ + selection: this.#selectionManager.getSelection(), + }); + } + + #close() { + this.modalHandler?.reject(); + } + render() { return html` - -
-
- ${this._userGroups.map( - (item) => html` -
this.handleSelection(item.id)} - @keydown=${(e: KeyboardEvent) => this._handleKeydown(e, item.id)} - class=${this.isSelected(item.id) ? 'item selected' : 'item'}> - - ${item.name} -
- ` - )} -
+ ${this._userGroups.map( + (item) => html` + this.#selectionManager.select(item.id!)} + @unselected=${() => this.#selectionManager.deselect(item.id!)} + ?selected=${this.#selectionManager.isSelected(item.id!)}> + + + ` + )}
- - + +
`; } - static styles = [ - UUITextStyles, - css` - uui-input { - width: 100%; - } - hr { - border: none; - border-bottom: 1px solid var(--uui-color-divider); - margin: 16px 0; - } - #item-list { - display: flex; - flex-direction: column; - gap: var(--uui-size-1); - } - .item { - color: var(--uui-color-interactive); - display: grid; - grid-template-columns: var(--uui-size-8) 1fr; - padding: var(--uui-size-4) var(--uui-size-2); - gap: var(--uui-size-space-5); - align-items: center; - border-radius: var(--uui-border-radius); - cursor: pointer; - } - .item.selected { - background-color: var(--uui-color-selected); - color: var(--uui-color-selected-contrast); - } - .item:not(.selected):hover { - background-color: var(--uui-color-surface-emphasis); - color: var(--uui-color-interactive-emphasis); - } - .item.selected:hover { - background-color: var(--uui-color-selected-emphasis); - } - .item uui-icon { - width: 100%; - box-sizing: border-box; - display: flex; - height: fit-content; - } - `, - ]; + static styles = [UUITextStyles, css``]; } export default UmbUserGroupPickerModalElement; From 20b865839369347ef5bb3fa94efd766d46d1bc60 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 8 May 2023 19:54:35 +0200 Subject: [PATCH 8/8] remove unused element --- .../src/core/modal/index.ts | 1 - .../core/modal/modal-element-picker-base.ts | 51 ------------------- 2 files changed, 52 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/src/core/modal/modal-element-picker-base.ts diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/index.ts b/src/Umbraco.Web.UI.Client/src/core/modal/index.ts index ee83635e51..c7e8968e3a 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/index.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/index.ts @@ -1,2 +1 @@ -export * from './modal-element-picker-base'; export * from './modal-element.element'; diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/modal-element-picker-base.ts b/src/Umbraco.Web.UI.Client/src/core/modal/modal-element-picker-base.ts deleted file mode 100644 index 111ddccd08..0000000000 --- a/src/Umbraco.Web.UI.Client/src/core/modal/modal-element-picker-base.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { property } from 'lit/decorators.js'; -import { UmbModalBaseElement } from './modal-element.element'; -import { UmbPickerModalData, UmbPickerModalResult } from '@umbraco-cms/backoffice/modal'; - -// TODO: we should consider moving this into a class/context instead of an element. -// So we don't have to extend an element to get basic picker/selection logic -export class UmbModalElementPickerBase extends UmbModalBaseElement, UmbPickerModalResult> { - @property() - selection: Array = []; - - connectedCallback(): void { - super.connectedCallback(); - this.selection = this.data?.selection || []; - } - - submit() { - this.modalHandler?.submit({ selection: this.selection }); - } - - close() { - this.modalHandler?.reject(); - } - - protected _handleKeydown(e: KeyboardEvent, id?: string | null) { - if (e.key === 'Enter') { - this.handleSelection(id); - } - } - - /* TODO: Write test for this select/deselect method. */ - handleSelection(id?: string | null) { - if (id === undefined) throw new Error('No key provided'); - - if (this.data?.multiple) { - if (this.isSelected(id)) { - this.selection = this.selection.filter((selectedKey) => selectedKey !== id); - } else { - this.selection.push(id); - } - } else { - this.selection = [id]; - } - - this.requestUpdate('_selection'); - } - - isSelected(id?: string | null): boolean { - if (id === undefined) throw new Error('No Id provided'); - return this.selection.includes(id); - } -}