From fffc2e3b82eb4b11b0764d5dbdb26bdde5741b87 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 2 Jul 2024 11:42:29 +0200 Subject: [PATCH 1/9] wip: implement search in member picker --- .../member-picker-modal.element.ts | 87 ++++++++++++++++--- 1 file changed, 75 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts index e7d0046b8e..74e7cc07a5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts @@ -1,9 +1,13 @@ import { UmbMemberCollectionRepository } from '../../collection/index.js'; import type { UmbMemberDetailModel } from '../../types.js'; +import { UmbMemberSearchProvider } from '../../search/member.search-provider.js'; +import type { UmbMemberItemModel } from '../../repository/index.js'; import type { UmbMemberPickerModalValue, UmbMemberPickerModalData } from './member-picker-modal.token.js'; -import { html, customElement, state, repeat } from '@umbraco-cms/backoffice/external/lit'; -import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils'; +import { html, customElement, state, repeat, css } from '@umbraco-cms/backoffice/external/lit'; +import { UmbSelectionManager, debounce } from '@umbraco-cms/backoffice/utils'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; +import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; @customElement('umb-member-picker-modal') export class UmbMemberPickerModalElement extends UmbModalBaseElement< @@ -13,8 +17,15 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< @state() private _members: Array = []; + @state() + private _searchQuery: string = ''; + + @state() + private _searchResult: Array = []; + #collectionRepository = new UmbMemberCollectionRepository(this); #selectionManager = new UmbSelectionManager(this); + #searchProvider = new UmbMemberSearchProvider(this); override connectedCallback(): void { super.connectedCallback(); @@ -36,6 +47,19 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< } } + #onSearchInput(event: UUIInputEvent) { + const value = event.target.value as string; + this._searchQuery = value; + this.#debouncedSearch(); + } + + #debouncedSearch = debounce(this.#search, 300); + + async #search() { + const { data } = await this.#searchProvider.search({ query: this._searchQuery }); + this._searchResult = data?.items ?? []; + } + #submit() { this.value = { selection: this.#selectionManager.getSelection() }; this.modalContext?.submit(); @@ -48,21 +72,14 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< override render() { return html` + ${this.#renderSearch()} ${repeat( this.#filteredMembers, (item) => item.unique, - (item) => html` - this.#selectionManager.select(item.unique)} - @deselected=${() => this.#selectionManager.deselect(item.unique)} - ?selected=${this.#selectionManager.isSelected(item.unique)}> - - - `, + (item) => this.#renderMemberItem(item), )} +
`; } + + #renderSearch() { + return html` + + `; + } + + #renderSearchResult() { + return html` + ${repeat( + this._searchResult, + (item) => item.unique, + (item) => this.#renderMemberItem(item), + )} + `; + } + + #renderMemberItem(item: UmbMemberItemModel | UmbMemberDetailModel) { + return html` + this.#selectionManager.select(item.unique)} + @deselected=${() => this.#selectionManager.deselect(item.unique)} + ?selected=${this.#selectionManager.isSelected(item.unique)}> + + + `; + } + + static override styles = [ + UmbTextStyles, + css` + #search { + padding-bottom: var(--uui-size-space-5); + border-bottom: 1px solid var(--uui-color-divider); + } + + #search-input { + width: 100%; + } + `, + ]; } export default UmbMemberPickerModalElement; From a27459d0fd16cb097ab9f9b7c97f32dcf657d0fc Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 2 Jul 2024 12:17:15 +0200 Subject: [PATCH 2/9] hide items when searching --- .../member-picker-modal.element.ts | 47 ++++++++++++------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts index 74e7cc07a5..c6f6479671 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts @@ -3,7 +3,7 @@ import type { UmbMemberDetailModel } from '../../types.js'; import { UmbMemberSearchProvider } from '../../search/member.search-provider.js'; import type { UmbMemberItemModel } from '../../repository/index.js'; import type { UmbMemberPickerModalValue, UmbMemberPickerModalData } from './member-picker-modal.token.js'; -import { html, customElement, state, repeat, css } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement, state, repeat, css, nothing } from '@umbraco-cms/backoffice/external/lit'; import { UmbSelectionManager, debounce } from '@umbraco-cms/backoffice/utils'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; @@ -56,6 +56,11 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< #debouncedSearch = debounce(this.#search, 300); async #search() { + if (!this._searchQuery) { + this._searchResult = []; + return; + } + const { data } = await this.#searchProvider.search({ query: this._searchQuery }); this._searchResult = data?.items ?? []; } @@ -71,14 +76,7 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< override render() { return html` - - ${this.#renderSearch()} - ${repeat( - this.#filteredMembers, - (item) => item.unique, - (item) => this.#renderMemberItem(item), - )} - + ${this.#renderSearch()} ${this.#renderItems()}
@@ -91,12 +89,22 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< `; } + #renderItems() { + if (this._searchQuery) return nothing; + return html` + ${repeat( + this.#filteredMembers, + (item) => item.unique, + (item) => this.#renderMemberItem(item), + )} + `; + } + #renderSearch() { return html` - + +
+ ${this.#renderSearchResult()} `; } @@ -126,14 +134,17 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< static override styles = [ UmbTextStyles, css` - #search { - padding-bottom: var(--uui-size-space-5); - border-bottom: 1px solid var(--uui-color-divider); - } - #search-input { width: 100%; } + + #search-divider { + width: 100%; + height: 1px; + background-color: var(--uui-color-divider); + margin-top: var(--uui-size-space-5); + margin-bottom: var(--uui-size-space-3); + } `, ]; } From 6f9d6733f9dbcf59ec9941a95bc387dec10e731d Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 2 Jul 2024 12:20:10 +0200 Subject: [PATCH 3/9] add search icon --- .../member-picker-modal/member-picker-modal.element.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts index c6f6479671..11018a788b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts @@ -102,7 +102,11 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< #renderSearch() { return html` - + +
+ +
+
${this.#renderSearchResult()} `; From 6fa049b4cfac775628c72f016680e9b94d960bc2 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 2 Jul 2024 16:35:43 +0200 Subject: [PATCH 4/9] show load indicator --- .../member-picker-modal.element.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts index 11018a788b..211b3facbd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts @@ -23,6 +23,9 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< @state() private _searchResult: Array = []; + @state() + private _searching = false; + #collectionRepository = new UmbMemberCollectionRepository(this); #selectionManager = new UmbSelectionManager(this); #searchProvider = new UmbMemberSearchProvider(this); @@ -50,6 +53,7 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< #onSearchInput(event: UUIInputEvent) { const value = event.target.value as string; this._searchQuery = value; + this._searching = true; this.#debouncedSearch(); } @@ -58,11 +62,13 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< async #search() { if (!this._searchQuery) { this._searchResult = []; + this._searching = false; return; } const { data } = await this.#searchProvider.search({ query: this._searchQuery }); this._searchResult = data?.items ?? []; + this._searching = false; } #submit() { @@ -104,7 +110,9 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< return html`
- + ${this._searching + ? html`` + : html``}
@@ -149,6 +157,11 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< margin-top: var(--uui-size-space-5); margin-bottom: var(--uui-size-space-3); } + + #search-indicator { + margin-left: 7px; + margin-top: 4px; + } `, ]; } From 1bae82758ea29c21512aa90da671679c6ae49330 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 2 Jul 2024 19:12:05 +0200 Subject: [PATCH 5/9] add clear search button --- .../member-picker-modal.element.ts | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts index 211b3facbd..5ed953118a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts @@ -53,6 +53,13 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< #onSearchInput(event: UUIInputEvent) { const value = event.target.value as string; this._searchQuery = value; + + if (!this._searchQuery) { + this._searchResult = []; + this._searching = false; + return; + } + this._searching = true; this.#debouncedSearch(); } @@ -60,12 +67,6 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< #debouncedSearch = debounce(this.#search, 300); async #search() { - if (!this._searchQuery) { - this._searchResult = []; - this._searching = false; - return; - } - const { data } = await this.#searchProvider.search({ query: this._searchQuery }); this._searchResult = data?.items ?? []; this._searching = false; @@ -80,6 +81,11 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< this.modalContext?.reject(); } + #onSearchClear() { + this._searchQuery = ''; + this._searchResult = []; + } + override render() { return html` ${this.#renderSearch()} ${this.#renderItems()} @@ -108,12 +114,18 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< #renderSearch() { return html` - +
${this._searching ? html`` : html``}
+ +
+ + + +
${this.#renderSearchResult()} From 2141d4c7bb70ce36a9c344ac3f5b22688eef273c Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 12 Aug 2024 19:18:01 +0200 Subject: [PATCH 6/9] observe selection from selection manager --- .../member-picker-modal.element.ts | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts index 5ed953118a..cb03e1b029 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts @@ -37,6 +37,18 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< this.#selectionManager.setSelection(this.value?.selection ?? []); } + constructor() { + super(); + this.observe( + this.#selectionManager.selection, + (selection) => { + this.updateValue({ selection }); + this.requestUpdate(); + }, + 'umbSelectionObserver', + ); + } + override async firstUpdated() { const { data } = await this.#collectionRepository.requestCollection({}); this._members = data?.items ?? []; @@ -72,15 +84,6 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< this._searching = false; } - #submit() { - this.value = { selection: this.#selectionManager.getSelection() }; - this.modalContext?.submit(); - } - - #close() { - this.modalContext?.reject(); - } - #onSearchClear() { this._searchQuery = ''; this._searchResult = []; @@ -91,12 +94,14 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< ${this.#renderSearch()} ${this.#renderItems()}
- + this.modalContext?.reject()}> + @click=${() => this.modalContext?.submit()}>
`; } From 6e68f49d5a0387c15e1433af3ef3a04a005fc649 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 12 Aug 2024 19:23:03 +0200 Subject: [PATCH 7/9] prevent search with empty string --- .../member-picker-modal/member-picker-modal.element.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts index cb03e1b029..8977625f1c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts @@ -79,6 +79,7 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< #debouncedSearch = debounce(this.#search, 300); async #search() { + if (!this._searchQuery) return; const { data } = await this.#searchProvider.search({ query: this._searchQuery }); this._searchResult = data?.items ?? []; this._searching = false; From b68a65d29a83a2f28599e9d1a81c8635d9d1934f Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 12 Aug 2024 19:27:25 +0200 Subject: [PATCH 8/9] add empty search result text --- .../member-picker-modal/member-picker-modal.element.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts index 8977625f1c..bcd3db3584 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts @@ -139,6 +139,10 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< } #renderSearchResult() { + if (this._searchQuery && this._searching === false && this._searchResult.length === 0) { + return this.#renderEmptySearchResult(); + } + return html` ${repeat( this._searchResult, @@ -148,6 +152,10 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< `; } + #renderEmptySearchResult() { + return html`No result for "${this._searchQuery}".`; + } + #renderMemberItem(item: UmbMemberItemModel | UmbMemberDetailModel) { return html` Date: Tue, 13 Aug 2024 16:40:11 +0100 Subject: [PATCH 9/9] Member picker modal UI tweaks - Localization of "Search" placeholder label - Shows search clear button only when there is text/query - Code imports sorting --- .../member-picker-modal.element.ts | 59 +++++++++++-------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts index bcd3db3584..e0c3ec8ed1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/member-picker-modal/member-picker-modal.element.ts @@ -1,13 +1,13 @@ import { UmbMemberCollectionRepository } from '../../collection/index.js'; -import type { UmbMemberDetailModel } from '../../types.js'; import { UmbMemberSearchProvider } from '../../search/member.search-provider.js'; +import type { UmbMemberDetailModel } from '../../types.js'; import type { UmbMemberItemModel } from '../../repository/index.js'; import type { UmbMemberPickerModalValue, UmbMemberPickerModalData } from './member-picker-modal.token.js'; -import { html, customElement, state, repeat, css, nothing } from '@umbraco-cms/backoffice/external/lit'; -import { UmbSelectionManager, debounce } from '@umbraco-cms/backoffice/utils'; +import { css, customElement, html, nothing, repeat, state, when } from '@umbraco-cms/backoffice/external/lit'; +import { debounce, UmbSelectionManager } from '@umbraco-cms/backoffice/utils'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; -import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; @customElement('umb-member-picker-modal') export class UmbMemberPickerModalElement extends UmbModalBaseElement< @@ -91,20 +91,21 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< } override render() { - return html` - ${this.#renderSearch()} ${this.#renderItems()} - -
- this.modalContext?.reject()}> - this.modalContext?.submit()}> -
-
`; + return html` + + ${this.#renderSearch()} ${this.#renderItems()} +
+ this.modalContext?.reject()}> + this.modalContext?.submit()}> +
+
+ `; } #renderItems() { @@ -120,18 +121,26 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement< #renderSearch() { return html` - +
${this._searching ? html`` : html``}
- -
- - - -
+ ${when( + this._searchQuery, + () => html` +
+ + + +
+ `, + )}
${this.#renderSearchResult()}