diff --git a/src/Umbraco.Web.UI.Client/libs/picker-input/picker-input.context.ts b/src/Umbraco.Web.UI.Client/libs/picker-input/picker-input.context.ts index 3313f5ad45..7e1d3b6c49 100644 --- a/src/Umbraco.Web.UI.Client/libs/picker-input/picker-input.context.ts +++ b/src/Umbraco.Web.UI.Client/libs/picker-input/picker-input.context.ts @@ -1,8 +1,5 @@ -import { UmbItemRepository } from '@umbraco-cms/backoffice/repository'; +import { UmbItemRepository, UmbRepositorySelectionManager } from '@umbraco-cms/backoffice/repository'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; -import { UmbArrayState, UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; -import { createExtensionClass } from '@umbraco-cms/backoffice/extension-api'; -import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UMB_CONFIRM_MODAL, UMB_MODAL_CONTEXT_TOKEN, @@ -26,13 +23,10 @@ export class UmbPickerInputContext #init: Promise; - #selection = new UmbArrayState([]); - selection = this.#selection.asObservable(); + #selectionManager; - #selectedItems = new UmbArrayState([]); - selectedItems = this.#selectedItems.asObservable(); - - #selectedItemsObserver?: UmbObserverController; + selection; + selectedItems; max = Infinity; min = 0; @@ -49,29 +43,13 @@ export class UmbPickerInputContext this.modalAlias = modalAlias; this.#getUnique = getUniqueMethod || ((entry) => entry.id || ''); - //TODO: The promise can probably be done in a cleaner way. - const repositoryPromise: Promise = new Promise((resolve) => { - new UmbObserverController( - this.host, + this.#selectionManager = new UmbRepositorySelectionManager(host, repositoryAlias); - // TODO: this code is reused in multiple places, so it should be extracted to a function - umbExtensionsRegistry.getByTypeAndAlias('repository', repositoryAlias), - async (repositoryManifest) => { - if (!repositoryManifest) return; - - try { - const result = await createExtensionClass>(repositoryManifest, [this.host]); - this.repository = result; - resolve(); - } catch (error) { - throw new Error('Could not create repository with alias: ' + repositoryAlias + ''); - } - } - ); - }); + this.selection = this.#selectionManager.selection; + this.selectedItems = this.#selectionManager.selectedItems; this.#init = Promise.all([ - repositoryPromise, + this.#selectionManager.init, new UmbContextConsumerController(this.host, UMB_MODAL_CONTEXT_TOKEN, (instance) => { this.modalContext = instance; }).asPromise(), @@ -79,14 +57,11 @@ export class UmbPickerInputContext } getSelection() { - return this.#selection.value; + return this.#selectionManager.getSelection(); } setSelection(selection: string[]) { - this.#selection.next(selection); - - //TODO: Check if it's safe to call requestItems here. - this.#requestItems(); + this.#selectionManager.setSelection(selection); } // TODO: If modalAlias is a ModalToken, then via TS, we should get the correct type for pickerData. Otherwise fallback to unknown. @@ -112,7 +87,7 @@ export class UmbPickerInputContext if (!this.repository) throw new Error('Repository is not initialized'); // TODO: id won't always be available on the model, so we need to get the unique property from somewhere. Maybe the repository? - const item = this.#selectedItems.value.find((item) => this.#getUnique(item) === unique); + const item = this.#selectionManager.getSelectedItems().find((item) => this.#getUnique(item) === unique); if (!item) throw new Error('Could not find item with unique: ' + unique); const modalHandler = this.modalContext?.open(UMB_CONFIRM_MODAL, { @@ -126,26 +101,8 @@ export class UmbPickerInputContext this.#removeItem(unique); } - async #requestItems() { - await this.#init; - if (!this.repository) throw new Error('Repository is not initialized'); - if (this.#selectedItemsObserver) this.#selectedItemsObserver.destroy(); - - const { asObservable } = await this.repository.requestItems(this.getSelection()); - - if (asObservable) { - this.#selectedItemsObserver = new UmbObserverController(this.host, asObservable(), (data) => - this.#selectedItems.next(data) - ); - } - } - #removeItem(unique: string) { const newSelection = this.getSelection().filter((value) => value !== unique); - this.#selection.next(newSelection); - // remove items items from selectedItems array - // TODO: id won't always be available on the model, so we need to get the unique property from somewhere. Maybe the repository? - const newSelectedItems = this.#selectedItems.value.filter((item) => this.#getUnique(item) !== unique); - this.#selectedItems.next(newSelectedItems); + this.setSelection(newSelection); } } diff --git a/src/Umbraco.Web.UI.Client/libs/repository/index.ts b/src/Umbraco.Web.UI.Client/libs/repository/index.ts index 9efaa532e9..c3be0d12de 100644 --- a/src/Umbraco.Web.UI.Client/libs/repository/index.ts +++ b/src/Umbraco.Web.UI.Client/libs/repository/index.ts @@ -6,3 +6,4 @@ export * from './collection-repository.interface'; export * from './item-repository.interface'; export * from './move-repository.interface'; export * from './copy-repository.interface'; +export * from './repository-selection.manager'; diff --git a/src/Umbraco.Web.UI.Client/libs/repository/repository-selection.manager.ts b/src/Umbraco.Web.UI.Client/libs/repository/repository-selection.manager.ts new file mode 100644 index 0000000000..791acf609d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/libs/repository/repository-selection.manager.ts @@ -0,0 +1,90 @@ +import { UmbItemRepository } from '@umbraco-cms/backoffice/repository'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { UmbArrayState, UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; +import { createExtensionClass } from '@umbraco-cms/backoffice/extension-api'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import { ItemResponseModelBaseModel } from '@umbraco-cms/backoffice/backend-api'; + +export class UmbRepositorySelectionManager { + host: UmbControllerHostElement; + repository?: UmbItemRepository; + + init: Promise; + + #selection = new UmbArrayState([]); + selection = this.#selection.asObservable(); + + #selectedItems = new UmbArrayState([]); + selectedItems = this.#selectedItems.asObservable(); + + #selectedItemsObserver?: UmbObserverController; + + /* TODO: find a better way to have a getUniqueMethod. If we want to support trees/items of different types, + then it need to be bound to the type and can't be a generic method we pass in. */ + constructor(host: UmbControllerHostElement, repositoryAlias: string) { + this.host = host; + + //TODO: The promise can probably be done in a cleaner way. + this.init = new Promise((resolve) => { + new UmbObserverController( + this.host, + + // TODO: this code is reused in multiple places, so it should be extracted to a function + umbExtensionsRegistry.getByTypeAndAlias('repository', repositoryAlias), + async (repositoryManifest) => { + if (!repositoryManifest) return; + + try { + const result = await createExtensionClass>(repositoryManifest, [this.host]); + this.repository = result; + resolve(); + } catch (error) { + throw new Error('Could not create repository with alias: ' + repositoryAlias + ''); + } + } + ); + }); + } + + getSelection() { + return this.#selection.value; + } + + setSelection(selection: string[]) { + this.#selection.next(selection); + + //TODO: Check if it's safe to call requestItems here. + this.#requestItems(); + } + + getSelectedItems() { + return this.#selectedItems.value; + } + + async #requestItems() { + await this.init; + if (!this.repository) throw new Error('Repository is not initialized'); + if (this.#selectedItemsObserver) this.#selectedItemsObserver.destroy(); + + // TODO: Test if its just some items that is gone now, if so then just filter them out. (maybe use code from #removeItem) + + const { asObservable } = await this.repository.requestItems(this.getSelection()); + + if (asObservable) { + this.#selectedItemsObserver = new UmbObserverController(this.host, asObservable(), (data) => + this.#selectedItems.next(data) + ); + } + } + + /* + #removeItem(unique: string) { + const newSelection = this.getSelection().filter((value) => value !== unique); + this.#selection.next(newSelection); + // remove items items from selectedItems array + // TODO: id won't always be available on the model, so we need to get the unique property from somewhere. Maybe the repository? + const newSelectedItems = this.#selectedItems.value.filter((item) => this.#getUnique(item) !== unique); + this.#selectedItems.next(newSelectedItems); + } + */ +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/components/data-type-flow-input/data-type-flow-input.element.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/components/data-type-flow-input/data-type-flow-input.element.ts index 6b2da756ea..74062f232b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/components/data-type-flow-input/data-type-flow-input.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/components/data-type-flow-input/data-type-flow-input.element.ts @@ -3,9 +3,9 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, property, state } from 'lit/decorators.js'; import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UmbModalRouteRegistrationController, UMB_DATA_TYPE_PICKER_FLOW_MODAL } from '@umbraco-cms/backoffice/modal'; import type { UmbDataTypeModel } from '@umbraco-cms/backoffice/models'; +import { UmbRepositorySelectionManager } from '@umbraco-cms/backoffice/repository'; // Note: Does only support picking a single data type. But this could be developed later into this same component. To follow other picker input components. /** @@ -17,20 +17,26 @@ import type { UmbDataTypeModel } from '@umbraco-cms/backoffice/models'; */ @customElement('umb-data-type-flow-input') export class UmbInputDataTypeElement extends FormControlMixin(UmbLitElement) { + #selectionManager; + protected getFormElement() { return undefined; } - @state() private _selectedDataType?: UmbDataTypeModel; + @state() + private _items?: Array; /** * @param {string} dataTypeId * @default [] */ @property({ attribute: false }) + get value(): string { + return super.value.toString(); + } set value(dataTypeId: string) { super.value = dataTypeId; - this.#observeDataTypeId(); + this.#selectionManager.setSelection(dataTypeId.split(',')); } @state() @@ -39,16 +45,22 @@ export class UmbInputDataTypeElement extends FormControlMixin(UmbLitElement) { constructor() { super(); + this.#selectionManager = new UmbRepositorySelectionManager(this, 'dataType'); + this.observe(this.#selectionManager.selection, (selection) => { + super.value = selection.join(','); + }); + this.observe(this.#selectionManager.selectedItems, (selectedItems) => (this._items = selectedItems)); + new UmbModalRouteRegistrationController(this, UMB_DATA_TYPE_PICKER_FLOW_MODAL) .onSetup(() => { return { - selection: [this._value.toString()], + selection: this.#selectionManager.getSelection(), submitLabel: 'Submit', }; }) .onSubmit((submitData) => { // TODO: we might should set the alias to null or empty string, if no selection. - this.value = submitData.selection[0] ?? null; + this.#selectionManager.setSelection(submitData.selection); this.dispatchEvent(new CustomEvent('change', { composed: true, bubbles: true })); }) .observeRouteBuilder((routeBuilder) => { @@ -57,27 +69,13 @@ export class UmbInputDataTypeElement extends FormControlMixin(UmbLitElement) { }); } - #observeDataTypeId() { - if (!this._value) return; - - /*this.observe( - umbExtensionsRegistry.getByTypeAndAlias('propertyEditorUI', this._value.toString()), - (propertyEditorUI) => { - if (!propertyEditorUI) return; - - this._selectedDataType = propertyEditorUI; - }, - 'observePropertyEditorUI' - );*/ - } - render() { - return this._selectedDataType + return this._items && this._items.length > 0 ? html` { console.warn('TO BE DONE..'); }} diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/components/data-type-input/data-type-input.element.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/components/data-type-input/data-type-input.element.ts index 8a5e055745..6f4dcea17b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/components/data-type-input/data-type-input.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/components/data-type-input/data-type-input.element.ts @@ -4,7 +4,7 @@ import { customElement, property, state } from 'lit/decorators.js'; import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins'; import { UmbDataTypePickerContext } from './data-type-input.context'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import type { DataTypeItemResponseModel, FolderTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import type { DataTypeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; @customElement('umb-data-type-input') export class UmbDataTypeInputElement extends FormControlMixin(UmbLitElement) {