From f60436270f0e3108c08aae9eb4cb17b0f7068eb2 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 10 Oct 2025 11:46:48 +0200 Subject: [PATCH] Property Editor Data Source (#20375) * Add property editor data source extension types Introduces types and extension interfaces for property editor data sources, including manifest and API definitions. Updates the main property-editor types export to include the new data source types. * add test data sources * wip collection and item repos * export consts * fix picker modal token * make global components file * render picker in data type * wire up repositories * append editor data source alias to data type detail model * fix global manifest declaration * make optional * fix types * register collection item picker modal element + wip collection menu extension * register collection menu for property editor data source * wire up modal tokens * fix circular * register as global element * register default kind for collection menu * wip fleshing out collection menu * pass props + listen for selection events * fix imports * accept icon in manifest * extend base type * use correct data to calculate length * export types * add load more button * wire up load more * remove debugger * add search for property editor data sources * only select one data source * rename file * add entity type * add manifest for search result item * fix imports/exports * fix manifest imports * wire up data source value with workspace * remove debugger * wip property editor + input * move data-source files * more specific extension types * remove copy from file name * allow settings in manifests * export types * merge settings * fix ui alias * remerge if data source is removed * Update data-type-details-workspace-view.element.ts * reset data * Update data-type-workspace.context.ts * update merging + move mapping to data source * Fix mutation of data.values in data type detail mapping Refactored #mapServerResponseModelToEntityDetailModel to avoid mutating the original data.values array when removing the editorDataSourceAlias. This ensures the original server response remains unchanged and improves data integrity. * add forDataSourceTypes to manifest * update interfaces * test data source implementations * only show data source select if property editor supports it * remove custom context * remove unused token * use generic collection item picker modal * remove custom modal * export types * render data source alias on data type into view * pass data source alias * allow data source alias * allow data source alias * pass data source alias * add prop for data source alias * Add property editor data source alias support * Add editor data source alias to property context Introduces support for storing and retrieving the editor data source alias in UmbPropertyContext. Updates UmbPropertyElement to use the context for managing the data source alias and ensures the alias is set on the property editor element. * pass data source alias to input * pass data source alias to context * update js docs * split types from token file * fix import * update error message * add more test sources * Refactor repository manager initialization logic Changed the initialization flow in UmbRepositoryItemsManager to support optional repository alias and deferred repository setup. Added setItemRepository and getItemRepository methods for explicit repository management, and moved repository initialization logic to a dedicated private method. * remove support for passing a filter * wip wire up input with modal * add constant * test user data source * add todo * require entityType on webhook items * add entityType * use id as unique * add default icon * wire up search * add search to media * pass config * support configuration in data sources + temp test cases * remove temp text * change to one generic extension type with a data type sub type * search in label * pass filter args to collection item picker * clean up * aligning interfaces * iterate status instead of item * simplify examples * add types for config * move to examples * add custom data examples for collection and tree * update imports * add manifests for collection and tree custom data examples * add type guards * add type guards * Update types.ts * add return type * remove debuggers * make observables optional * add null checks for observables * use statuses * extend picker input context * map config * use data to set value when there is no observable * store as string array * Add getDefaultApiConstructor to tree item element * make it optional * fix search types * add fallback icon and name * remove unused imports * pass stored value to input * rename file * remove unused config value * make api observable * add search to custom collection example * render fallback item * fix import order * add fallback render to tree item element * Update tree-item.element.ts * Revert "Update tree-item.element.ts" This reverts commit 3458877de91359f8b7a242a7936ae2bd7641ae1f. * Revert "add fallback render to tree item element" This reverts commit b30219d3ed66c6b0f8a6ca010adcc137fffcd00c. * move from data type to property editor * align file names * introduce picker-property-editor module * remove custom types * use basic types * use tree item type * Update input-entity-data.context.ts * update types * add interface for item model * force unique on collection item model * require an item model in picker context * allow icon to be null * extend item model from user group item model * add entity type to mapped data * Update user-group-item.server.data-source.ts * align static file models * correct types for user picker * extend item model * fix types * more type fixing * align models * align models * fix types * add utils for fallback name and icon * add todo * use fallback name and icon functions * Update default-picker-search-result-item.element.ts * add fallback tree item if none is registered * add search to example * extract data source config and pass to api * align naming * temp type cast * move search module into core * fix illegal imports * add missing const exports * make property-editor-data-source module * register property editor data source ref item + render description * remove console log * remove indention * simplify data source type * Update src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/input/input-entity-data.element.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/input/input-property-editor-data-source.element.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/default/default-collection-menu.element.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * add todo * hide add button when readonly * check correct amount config * Update input-entity-data.element.ts --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ...le-custom-picker-collection-data-source.ts | 75 ++++++ .../example-custom-picker-tree-data-source.ts | 126 ++++++++++ .../example-document-picker-data-source.ts | 75 ++++++ .../example-language-picker-data-source.ts | 22 ++ .../example-media-picker-data-source.ts | 51 ++++ .../example-user-picker-data-source.ts | 22 ++ .../example-webhook-picker-data-source.ts | 22 ++ .../examples/picker-data-source/index.ts | 102 ++++++++ src/Umbraco.Web.UI.Client/package-lock.json | 7 +- src/Umbraco.Web.UI.Client/package.json | 4 +- .../src/apps/backoffice/backoffice.element.ts | 1 - .../block-type-custom-view-guide.element.ts | 3 +- .../property-type-based-property.element.ts | 6 +- .../collection-item-picker-modal.element.ts | 154 ++++++++++++ .../collection-item-picker-modal/constants.ts | 1 + .../collection-item-picker-modal/manifests.ts | 10 + .../collection-item-picker-modal/types.ts | 15 ++ .../src/packages/core/collection/constants.ts | 2 + .../core/collection/global-components.ts | 3 + .../src/packages/core/collection/index.ts | 6 +- .../packages/core/collection/item/types.ts | 7 + .../src/packages/core/collection/manifests.ts | 8 +- .../menu/collection-menu.element.ts | 27 ++ .../core/collection/menu/constants.ts | 1 + .../default-collection-menu.context.token.ts | 6 + .../default-collection-menu.context.ts | 123 +++++++++ .../default-collection-menu.element.ts | 162 ++++++++++++ .../core/collection/menu/default/index.ts | 2 + .../core/collection/menu/default/manifests.ts | 16 ++ .../extension/collection-menu.extension.ts | 16 ++ .../core/collection/menu/extension/types.ts | 1 + .../core/collection/menu/manifests.ts | 4 + .../packages/core/collection/menu/types.ts | 1 + .../src/packages/core/collection/types.ts | 11 +- .../input-entity/input-entity.element.ts | 12 +- .../default-item-ref.element.ts | 16 +- .../src/packages/core/entity-item/index.ts | 3 + .../src/packages/core/entity-item/types.ts | 6 +- .../src/packages/core/entity-item/utils.ts | 18 ++ .../src/packages/core/entry-point.ts | 8 +- ...nsion-element-and-api-slot-element-base.ts | 5 +- .../src/packages/core/manifests.ts | 4 + .../is-picker-collection-data-source.guard.ts | 9 + .../collection-data-source/types.ts | 5 + .../picker-data-source/data-source/types.ts | 8 + .../packages/core/picker-data-source/index.ts | 4 + .../is-picker-searchable.data-source.guard.ts | 9 + .../searchable-data-source/types.ts | 4 + .../is-picker-tree-data-source.guard.ts | 13 + .../tree-data-source/types.ts | 5 + .../packages/core/picker-data-source/types.ts | 4 + .../core/picker-input/picker-input.context.ts | 23 +- .../search/picker-search-result.element.ts | 23 +- ...fault-picker-search-result-item.element.ts | 5 +- .../picker-search-result-item-element-base.ts | 6 +- .../collection/constants.ts | 2 + ...llection.extension-registry.data-source.ts | 49 ++++ .../collection/data/collection.repository.ts | 17 ++ .../collection/data/constants.ts | 2 + .../collection/data/index.ts | 2 + .../collection/data/manifests.ts | 10 + .../collection/data/types.ts | 20 ++ .../collection/index.ts | 1 + .../collection/manifests.ts | 4 + .../collection/menu/constants.ts | 1 + .../collection/menu/manifests.ts | 15 ++ .../collection/types.ts | 1 + .../property-editor-data-source/constants.ts | 4 + .../property-editor-data-source/entity.ts | 3 + .../property-editor-data-source.extension.ts | 22 ++ .../extension/types.ts | 1 + .../global-components.ts | 3 + .../core/property-editor-data-source/index.ts | 4 + ...put-property-editor-data-source.context.ts | 67 +++++ ...put-property-editor-data-source.element.ts | 233 +++++++++++++++++ .../item/constants.ts | 1 + .../item/data/constants.ts | 3 + .../item/data/index.ts | 1 + .../item.extension-registry.data-source.ts | 31 +++ .../item/data/item.repository.ts | 17 ++ .../item/data/item.store.context-token.ts | 5 + .../item/data/item.store.ts | 23 ++ .../item/data/manifests.ts | 19 ++ .../item/data/types.ts | 5 + .../item/manifests.ts | 4 + .../item/ref/manifests.ts | 11 + ...rty-editor-data-source-item-ref.element.ts | 53 ++++ .../property-editor-data-source/item/types.ts | 1 + .../property-editor-data-source/manifests.ts | 5 + .../search/constants.ts | 1 + .../search/index.ts | 1 + .../search/manifests.ts | 20 ++ .../search/search-provider.ts | 21 ++ .../search.extension-registry.data-source.ts | 53 ++++ .../search/search.repository.ts | 23 ++ .../search/types.ts | 5 + .../core/property-editor-data-source/types.ts | 1 + .../property-editor-ui-element.interface.ts | 1 + .../extensions/property-editor.extension.ts | 14 ++ .../components/property/property.context.ts | 19 ++ .../components/property/property.element.ts | 13 + .../read/read-detail-repository.interface.ts | 2 +- .../item/item-repository.interface.ts | 2 +- .../repository/repository-items.manager.ts | 54 +++- .../src/packages/core/repository/types.ts | 2 +- .../packages/{ => core}/search/constants.ts | 0 .../examine-management-dashboard/constants.ts | 0 .../dashboard-examine-management.element.ts | 0 .../examine-management-dashboard/manifests.ts | 0 .../modal/constants.ts | 0 .../modal/fields-settings/constants.ts | 0 .../examine-fields-settings-modal.element.ts | 0 .../examine-fields-settings-modal.token.ts | 0 .../modal/fields-viewer/constants.ts | 0 .../examine-fields-viewer-modal.element.ts | 0 .../examine-fields-viewer-modal.token.ts | 0 .../modal/manifests.ts | 0 .../views/section-view-examine-indexers.ts | 0 .../views/section-view-examine-overview.ts | 0 .../views/section-view-examine-searchers.ts | 0 .../extensions/search-provider.extension.ts | 0 .../search-result-item.extension.ts | 0 .../{ => core}/search/extensions/types.ts | 0 .../global-search/global-search-base.ts | 0 .../global-search/global-search.extension.ts | 0 .../{ => core}/search/global-search/index.ts | 0 .../{ => core}/search/global-search/types.ts | 0 .../src/packages/{ => core}/search/index.ts | 0 .../packages/{ => core}/search/manifests.ts | 6 +- .../search/search-data-source.interface.ts | 0 .../search-modal/search-modal.element.ts | 0 .../search/search-modal/search-modal.token.ts | 0 .../search/search-repository.interface.ts | 0 .../search-result-item.element.ts | 0 .../src/packages/{ => core}/search/types.ts | 12 +- .../search/umb-search-header-app.element.ts | 0 .../input-section/input-section.context.ts | 2 +- .../item/section-item.repository.ts | 2 + .../core/section/repository/item/types.ts | 6 +- .../tree/data/tree-repository.interface.ts | 4 +- .../core/tree/tree-item/tree-item.element.ts | 40 +++ .../core/tree/tree-picker-modal/index.ts | 3 +- .../tree-picker-modal.element.ts | 2 +- .../tree-picker-modal.token.ts | 29 +-- .../core/tree/tree-picker-modal/types.ts | 28 +++ .../core/utils/config-collection/index.ts | 12 + .../core/utils/config-collection/types.ts | 6 + .../src/packages/core/utils/index.ts | 1 + .../src/packages/core/utils/type/index.ts | 1 + .../src/packages/core/vite.config.ts | 3 + .../property-editor-config.element.ts | 1 + .../ref-data-type/ref-data-type.element.ts | 2 +- .../data-type-detail.server.data-source.ts | 42 +++- .../src/packages/data-type/types.ts | 1 + .../workspace/data-type-workspace.context.ts | 73 +++++- ...ata-type-details-workspace-view.element.ts | 57 ++++- .../workspace-view-data-type-info.element.ts | 21 +- .../src/packages/documents/documents/index.ts | 1 + .../documents/documents/search/index.ts | 1 + .../src/packages/documents/documents/types.ts | 1 + .../input-language/input-language.context.ts | 6 +- .../media-picker-modal.element.ts | 5 +- .../media-picker/media-picker-modal.token.ts | 4 +- .../src/packages/media/media/search/index.ts | 1 + .../input-member-group.context.ts | 6 +- .../member-group/repository/item/types.ts | 6 +- .../entity-data-picker/constants.ts | 4 + ...y-data-picker-data-source.context.token.ts | 9 + .../entity-data-picker-data-source.context.ts | 24 ++ .../input/input-entity-data.context.ts | 179 +++++++++++++ .../input/input-entity-data.element.ts | 237 ++++++++++++++++++ .../entity-data-picker/manifests.ts | 13 + .../picker-collection/constants.ts | 2 + ...ntity-data-picker-collection.repository.ts | 40 +++ .../picker-collection/manifests.ts | 22 ++ .../picker-item/constants.ts | 1 + .../entity-data-picker-item.repository.ts | 37 +++ .../picker-item/manifests.ts | 10 + .../picker-search/constants.ts | 1 + .../entity-data-picker.search-provider.ts | 40 +++ .../picker-search/manifests.ts | 10 + .../picker-tree/constants.ts | 1 + .../entity-data-picker-tree.repository.ts | 60 +++++ .../picker-tree/manifests.ts | 21 ++ ...-data-picker-property-editor-ui.element.ts | 139 ++++++++++ .../property-editor/manifests.ts | 33 +++ .../property-editor/types.ts | 1 + .../packages/property-editors/manifests.ts | 2 + .../src/packages/search/package.json | 8 - .../src/packages/search/umbraco-package.ts | 9 - .../src/packages/search/vite.config.ts | 12 - .../src/packages/static-file/entity.ts | 3 + .../static-file-item.server.data-source.ts | 2 + .../static-file/repository/item/types.ts | 3 + .../stylesheet-input.context.ts | 9 +- .../modals/table-properties-modal.element.ts | 4 +- .../user/user-group/repository/item/types.ts | 5 +- .../user-group-item.server.data-source.ts | 2 + .../user-input/user-input.context.ts | 4 +- .../src/packages/webhook/entity.ts | 1 + .../webhook/webhook/repository/item/types.ts | 5 +- .../item/webhook-item.server.data-source.ts | 4 +- src/Umbraco.Web.UI.Client/tsconfig.json | 6 +- 203 files changed, 3326 insertions(+), 176 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/examples/picker-data-source/example-custom-picker-collection-data-source.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/picker-data-source/example-custom-picker-tree-data-source.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/picker-data-source/example-document-picker-data-source.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/picker-data-source/example-language-picker-data-source.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/picker-data-source/example-media-picker-data-source.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/picker-data-source/example-user-picker-data-source.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/picker-data-source/example-webhook-picker-data-source.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/picker-data-source/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-item-picker-modal/collection-item-picker-modal.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-item-picker-modal/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-item-picker-modal/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-item-picker-modal/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/collection/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/collection/global-components.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/collection/item/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/collection-menu.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/default/default-collection-menu.context.token.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/default/default-collection-menu.context.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/default/default-collection-menu.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/default/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/default/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/extension/collection-menu.extension.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/extension/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity-item/utils.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/collection-data-source/is-picker-collection-data-source.guard.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/collection-data-source/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/data-source/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/searchable-data-source/is-picker-searchable.data-source.guard.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/searchable-data-source/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/tree-data-source/is-picker-tree-data-source.guard.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/tree-data-source/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/data/collection.extension-registry.data-source.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/data/collection.repository.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/data/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/data/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/data/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/data/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/menu/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/menu/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/entity.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/extension/property-editor-data-source.extension.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/extension/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/global-components.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/input/input-property-editor-data-source.context.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/input/input-property-editor-data-source.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/item.extension-registry.data-source.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/item.repository.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/item.store.context-token.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/item.store.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/ref/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/ref/property-editor-data-source-item-ref.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/search-provider.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/search.extension-registry.data-source.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/search.repository.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/types.ts rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/constants.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/examine-management-dashboard/constants.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/examine-management-dashboard/dashboard-examine-management.element.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/examine-management-dashboard/manifests.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/examine-management-dashboard/modal/constants.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/examine-management-dashboard/modal/fields-settings/constants.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/examine-management-dashboard/modal/fields-settings/examine-fields-settings-modal.element.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/examine-management-dashboard/modal/fields-settings/examine-fields-settings-modal.token.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/examine-management-dashboard/modal/fields-viewer/constants.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/examine-management-dashboard/modal/fields-viewer/examine-fields-viewer-modal.element.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/examine-management-dashboard/modal/fields-viewer/examine-fields-viewer-modal.token.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/examine-management-dashboard/modal/manifests.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/examine-management-dashboard/views/section-view-examine-indexers.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/examine-management-dashboard/views/section-view-examine-overview.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/examine-management-dashboard/views/section-view-examine-searchers.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/extensions/search-provider.extension.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/extensions/search-result-item.extension.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/extensions/types.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/global-search/global-search-base.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/global-search/global-search.extension.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/global-search/index.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/global-search/types.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/index.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/manifests.ts (86%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/search-data-source.interface.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/search-modal/search-modal.element.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/search-modal/search-modal.token.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/search-repository.interface.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/search-result/search-result-item.element.ts (100%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/types.ts (85%) rename src/Umbraco.Web.UI.Client/src/packages/{ => core}/search/umb-search-header-app.element.ts (100%) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker-modal/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/utils/config-collection/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/utils/config-collection/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/input/entity-data-picker-data-source.context.token.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/input/entity-data-picker-data-source.context.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/input/input-entity-data.context.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/input/input-entity-data.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-collection/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-collection/entity-data-picker-collection.repository.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-collection/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-item/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-item/entity-data-picker-item.repository.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-item/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-search/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-search/entity-data-picker.search-provider.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-search/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-tree/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-tree/entity-data-picker-tree.repository.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-tree/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/property-editor/entity-data-picker-property-editor-ui.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/property-editor/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/property-editor/types.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/search/package.json delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/search/umbraco-package.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/search/vite.config.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/static-file/entity.ts diff --git a/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-custom-picker-collection-data-source.ts b/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-custom-picker-collection-data-source.ts new file mode 100644 index 0000000000..6ab13a8459 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-custom-picker-collection-data-source.ts @@ -0,0 +1,75 @@ +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbCollectionFilterModel, UmbCollectionItemModel } from '@umbraco-cms/backoffice/collection'; +import type { + UmbPickerCollectionDataSource, + UmbPickerSearchableDataSource, +} from '@umbraco-cms/backoffice/picker-data-source'; +import type { UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; + +export class ExampleCustomPickerCollectionPropertyEditorDataSource + extends UmbControllerBase + implements UmbPickerCollectionDataSource, UmbPickerSearchableDataSource +{ + async requestCollection(args: UmbCollectionFilterModel) { + // TODO: use args to filter/paginate etc + console.log(args); + const data = { + items: customItems, + total: customItems.length, + }; + + return { data }; + } + + async requestItems(uniques: Array) { + const items = customItems.filter((x) => uniques.includes(x.unique)); + return { data: items }; + } + + async search(args: UmbSearchRequestArgs) { + const items = customItems.filter((item) => item.name?.toLowerCase().includes(args.query.toLowerCase())); + const total = items.length; + + const data = { + items, + total, + }; + + return { data }; + } +} + +export { ExampleCustomPickerCollectionPropertyEditorDataSource as api }; + +const customItems: Array = [ + { + unique: '1', + entityType: 'example', + name: 'Example 1', + icon: 'icon-shape-triangle', + }, + { + unique: '2', + entityType: 'example', + name: 'Example 2', + icon: 'icon-shape-triangle', + }, + { + unique: '3', + entityType: 'example', + name: 'Example 3', + icon: 'icon-shape-triangle', + }, + { + unique: '4', + entityType: 'example', + name: 'Example 4', + icon: 'icon-shape-triangle', + }, + { + unique: '5', + entityType: 'example', + name: 'Example 5', + icon: 'icon-shape-triangle', + }, +]; diff --git a/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-custom-picker-tree-data-source.ts b/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-custom-picker-tree-data-source.ts new file mode 100644 index 0000000000..8e6f8e6ce2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-custom-picker-tree-data-source.ts @@ -0,0 +1,126 @@ +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbPickerTreeDataSource } from '@umbraco-cms/backoffice/picker-data-source'; +import type { UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import type { UmbTreeChildrenOfRequestArgs, UmbTreeItemModel } from '@umbraco-cms/backoffice/tree'; + +export class ExampleCustomPickerTreePropertyEditorDataSource + extends UmbControllerBase + implements UmbPickerTreeDataSource +{ + async requestTreeRoot() { + const root = { + unique: null, + name: 'Examples', + icon: 'icon-folder', + hasChildren: true, + entityType: 'example-root', + isFolder: true, + }; + + return { data: root }; + } + + async requestTreeRootItems() { + // TODO: implement args when needed + const rootItems = customItems.filter((item) => item.parent.unique === null); + + const data = { + items: rootItems, + total: rootItems.length, + }; + + return { data }; + } + + async requestTreeItemsOf(args: UmbTreeChildrenOfRequestArgs) { + const items = customItems.filter( + (item) => item.parent.entityType === args.parent.entityType && item.parent.unique === args.parent.unique, + ); + + const data = { + items: items, + total: items.length, + }; + + return { data }; + } + + async requestTreeItemAncestors() { + // TODO: implement when needed + return { data: [] }; + } + + async requestItems(uniques: Array) { + const items = customItems.filter((x) => uniques.includes(x.unique)); + return { data: items }; + } + + async search(args: UmbSearchRequestArgs) { + const result = customItems.filter((item) => item.name.toLowerCase().includes(args.query.toLowerCase())); + + const data = { + items: result, + totalItems: result.length, + }; + + return { data }; + } +} + +export { ExampleCustomPickerTreePropertyEditorDataSource as api }; + +const customItems: Array = [ + { + unique: '1', + entityType: 'example', + name: 'Example 1', + icon: 'icon-shape-triangle', + parent: { unique: null, entityType: 'example-root' }, + isFolder: false, + hasChildren: false, + }, + { + unique: '2', + entityType: 'example', + name: 'Example 2', + icon: 'icon-shape-triangle', + parent: { unique: null, entityType: 'example-root' }, + isFolder: false, + hasChildren: false, + }, + { + unique: '3', + entityType: 'example', + name: 'Example 3', + icon: 'icon-shape-triangle', + parent: { unique: null, entityType: 'example-root' }, + isFolder: false, + hasChildren: false, + }, + { + unique: '4', + entityType: 'example', + name: 'Example 4', + icon: 'icon-shape-triangle', + parent: { unique: '6', entityType: 'example-folder' }, + isFolder: false, + hasChildren: false, + }, + { + unique: '5', + entityType: 'example', + name: 'Example 5', + icon: 'icon-shape-triangle', + parent: { unique: '6', entityType: 'example-folder' }, + isFolder: false, + hasChildren: false, + }, + { + unique: '6', + entityType: 'example-folder', + name: 'Example Folder 1', + parent: { unique: null, entityType: 'example-root' }, + isFolder: true, + hasChildren: true, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-document-picker-data-source.ts b/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-document-picker-data-source.ts new file mode 100644 index 0000000000..68937ecce7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-document-picker-data-source.ts @@ -0,0 +1,75 @@ +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import { + UmbDocumentItemRepository, + UmbDocumentSearchRepository, + UmbDocumentTreeRepository, + type UmbDocumentSearchRequestArgs, +} from '@umbraco-cms/backoffice/document'; +import { UMB_DOCUMENT_TYPE_ENTITY_TYPE } from '@umbraco-cms/backoffice/document-type'; +import type { + UmbPickerSearchableDataSource, + UmbPickerTreeDataSource, +} from '@umbraco-cms/backoffice/picker-data-source'; +import type { UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import type { + UmbTreeAncestorsOfRequestArgs, + UmbTreeChildrenOfRequestArgs, + UmbTreeRootItemsRequestArgs, +} from '@umbraco-cms/backoffice/tree'; +import { getConfigValue, type UmbConfigCollectionModel } from '@umbraco-cms/backoffice/utils'; + +export class ExampleDocumentPickerPropertyEditorDataSource + extends UmbControllerBase + implements UmbPickerTreeDataSource, UmbPickerSearchableDataSource +{ + #tree = new UmbDocumentTreeRepository(this); + #item = new UmbDocumentItemRepository(this); + #search = new UmbDocumentSearchRepository(this); + #config: UmbConfigCollectionModel = []; + + setConfig(config: UmbConfigCollectionModel) { + // TODO: add examples for all config options + this.#config = config; + } + + getConfig(): UmbConfigCollectionModel { + return this.#config; + } + + requestTreeRoot() { + return this.#tree.requestTreeRoot(); + } + + requestTreeRootItems(args: UmbTreeRootItemsRequestArgs) { + return this.#tree.requestTreeRootItems(args); + } + + requestTreeItemsOf(args: UmbTreeChildrenOfRequestArgs) { + return this.#tree.requestTreeItemsOf(args); + } + + requestTreeItemAncestors(args: UmbTreeAncestorsOfRequestArgs) { + return this.#tree.requestTreeItemAncestors(args); + } + + requestItems(uniques: Array) { + return this.#item.requestItems(uniques); + } + + search(args: UmbSearchRequestArgs) { + const filterString = getConfigValue(this.#config, 'filter'); + const filterArray = filterString ? filterString.split(',') : []; + const allowedContentTypes: UmbDocumentSearchRequestArgs['allowedContentTypes'] = filterArray.map( + (unique: string) => ({ + entityType: UMB_DOCUMENT_TYPE_ENTITY_TYPE, + unique, + }), + ); + + const combinedArgs: UmbDocumentSearchRequestArgs = { ...args, allowedContentTypes }; + + return this.#search.search(combinedArgs); + } +} + +export { ExampleDocumentPickerPropertyEditorDataSource as api }; diff --git a/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-language-picker-data-source.ts b/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-language-picker-data-source.ts new file mode 100644 index 0000000000..867bccd9eb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-language-picker-data-source.ts @@ -0,0 +1,22 @@ +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbCollectionFilterModel } from '@umbraco-cms/backoffice/collection'; +import { UmbLanguageCollectionRepository, UmbLanguageItemRepository } from '@umbraco-cms/backoffice/language'; +import type { UmbPickerCollectionDataSource } from '@umbraco-cms/backoffice/picker-data-source'; + +export class ExampleLanguagePickerPropertyEditorDataSource + extends UmbControllerBase + implements UmbPickerCollectionDataSource +{ + #collection = new UmbLanguageCollectionRepository(this); + #item = new UmbLanguageItemRepository(this); + + requestCollection(args: UmbCollectionFilterModel) { + return this.#collection.requestCollection(args); + } + + requestItems(uniques: Array) { + return this.#item.requestItems(uniques); + } +} + +export { ExampleLanguagePickerPropertyEditorDataSource as api }; diff --git a/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-media-picker-data-source.ts b/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-media-picker-data-source.ts new file mode 100644 index 0000000000..fe62ea3bfd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-media-picker-data-source.ts @@ -0,0 +1,51 @@ +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import { + UmbMediaItemRepository, + UmbMediaSearchRepository, + UmbMediaTreeRepository, +} from '@umbraco-cms/backoffice/media'; +import type { + UmbPickerSearchableDataSource, + UmbPickerTreeDataSource, +} from '@umbraco-cms/backoffice/picker-data-source'; +import type { UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; +import type { + UmbTreeAncestorsOfRequestArgs, + UmbTreeChildrenOfRequestArgs, + UmbTreeRootItemsRequestArgs, +} from '@umbraco-cms/backoffice/tree'; + +export class ExampleMediaPickerPropertyEditorDataSource + extends UmbControllerBase + implements UmbPickerTreeDataSource, UmbPickerSearchableDataSource +{ + #tree = new UmbMediaTreeRepository(this); + #item = new UmbMediaItemRepository(this); + #search = new UmbMediaSearchRepository(this); + + requestTreeRoot() { + return this.#tree.requestTreeRoot(); + } + + requestTreeRootItems(args: UmbTreeRootItemsRequestArgs) { + return this.#tree.requestTreeRootItems(args); + } + + requestTreeItemsOf(args: UmbTreeChildrenOfRequestArgs) { + return this.#tree.requestTreeItemsOf(args); + } + + requestTreeItemAncestors(args: UmbTreeAncestorsOfRequestArgs) { + return this.#tree.requestTreeItemAncestors(args); + } + + requestItems(uniques: Array) { + return this.#item.requestItems(uniques); + } + + search(args: UmbSearchRequestArgs) { + return this.#search.search(args); + } +} + +export { ExampleMediaPickerPropertyEditorDataSource as api }; diff --git a/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-user-picker-data-source.ts b/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-user-picker-data-source.ts new file mode 100644 index 0000000000..b5985c0a77 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-user-picker-data-source.ts @@ -0,0 +1,22 @@ +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbCollectionFilterModel } from '@umbraco-cms/backoffice/collection'; +import type { UmbPickerCollectionDataSource } from '@umbraco-cms/backoffice/picker-data-source'; +import { UmbUserCollectionRepository, UmbUserItemRepository } from '@umbraco-cms/backoffice/user'; + +export class ExampleUserPickerPropertyEditorDataSource + extends UmbControllerBase + implements UmbPickerCollectionDataSource +{ + #collection = new UmbUserCollectionRepository(this); + #item = new UmbUserItemRepository(this); + + requestCollection(args: UmbCollectionFilterModel) { + return this.#collection.requestCollection(args); + } + + requestItems(uniques: Array) { + return this.#item.requestItems(uniques); + } +} + +export { ExampleUserPickerPropertyEditorDataSource as api }; diff --git a/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-webhook-picker-data-source.ts b/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-webhook-picker-data-source.ts new file mode 100644 index 0000000000..65070168f1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/picker-data-source/example-webhook-picker-data-source.ts @@ -0,0 +1,22 @@ +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbCollectionFilterModel } from '@umbraco-cms/backoffice/collection'; +import type { UmbPickerCollectionDataSource } from '@umbraco-cms/backoffice/picker-data-source'; +import { UmbWebhookCollectionRepository, UmbWebhookItemRepository } from '@umbraco-cms/backoffice/webhook'; + +export class ExampleWebhookPickerPropertyEditorDataSource + extends UmbControllerBase + implements UmbPickerCollectionDataSource +{ + #collection = new UmbWebhookCollectionRepository(this); + #item = new UmbWebhookItemRepository(this); + + requestCollection(args: UmbCollectionFilterModel) { + return this.#collection.requestCollection(args); + } + + requestItems(uniques: Array) { + return this.#item.requestItems(uniques); + } +} + +export { ExampleWebhookPickerPropertyEditorDataSource as api }; diff --git a/src/Umbraco.Web.UI.Client/examples/picker-data-source/index.ts b/src/Umbraco.Web.UI.Client/examples/picker-data-source/index.ts new file mode 100644 index 0000000000..fd4d9f3a2f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/picker-data-source/index.ts @@ -0,0 +1,102 @@ +export const manifests: Array = [ + { + type: 'propertyEditorDataSource', + dataSourceType: 'picker', + alias: 'Umb.PropertyEditorDataSource.CustomPickerCollection', + name: 'Custom Picker Collection Data Source', + api: () => import('./example-custom-picker-collection-data-source.js'), + meta: { + label: 'Example Items (Collection)', + icon: 'icon-list', + description: 'Pick example items from a collection', + }, + }, + { + type: 'propertyEditorDataSource', + dataSourceType: 'picker', + alias: 'Umb.PropertyEditorDataSource.CustomPickerTree', + name: 'Custom Picker Tree Data Source', + api: () => import('./example-custom-picker-tree-data-source.js'), + meta: { + label: 'Example Items (Tree)', + icon: 'icon-tree', + description: 'Pick example items from a tree', + }, + }, + { + type: 'propertyEditorDataSource', + dataSourceType: 'picker', + alias: 'Umb.PropertyEditorDataSource.DocumentPicker', + name: 'Document Picker Data Source', + api: () => import('./example-document-picker-data-source.js'), + meta: { + label: 'Documents', + icon: 'icon-document', + description: 'Pick a document', + settings: { + properties: [ + { + alias: 'startNode', + label: 'Node type', + description: '', + propertyEditorUiAlias: 'Umb.PropertyEditorUi.ContentPicker.Source', + }, + { + alias: 'filter', + label: 'Allow items of type', + description: 'Select the applicable types', + propertyEditorUiAlias: 'Umb.PropertyEditorUi.ContentPicker.SourceType', + }, + ], + }, + }, + }, + { + type: 'propertyEditorDataSource', + dataSourceType: 'picker', + alias: 'Umb.PropertyEditorDataSource.MediaPicker', + name: 'Media Picker Data Source', + api: () => import('./example-media-picker-data-source.js'), + meta: { + label: 'Media', + icon: 'icon-document-image', + description: 'Pick a media item', + }, + }, + { + type: 'propertyEditorDataSource', + dataSourceType: 'picker', + alias: 'Umb.PropertyEditorDataSource.LanguagePicker', + name: 'Language Picker Data Source', + api: () => import('./example-language-picker-data-source.js'), + meta: { + label: 'Languages', + icon: 'icon-globe', + description: 'Pick a language', + }, + }, + { + type: 'propertyEditorDataSource', + dataSourceType: 'picker', + alias: 'Umb.PropertyEditorDataSource.WebhookPicker', + name: 'Webhook Picker Data Source', + api: () => import('./example-webhook-picker-data-source.js'), + meta: { + label: 'Webhooks', + icon: 'icon-webhook', + description: 'Pick a webhook', + }, + }, + { + type: 'propertyEditorDataSource', + dataSourceType: 'picker', + alias: 'Umb.PropertyEditorDataSource.UserPicker', + name: 'User Picker Data Source', + api: () => import('./example-user-picker-data-source.js'), + meta: { + label: 'Users', + icon: 'icon-user', + description: 'Pick a user', + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 1d27a7f499..c7805ee9ac 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -3658,10 +3658,6 @@ "resolved": "src/external/rxjs", "link": true }, - "node_modules/@umbraco-backoffice/search": { - "resolved": "src/packages/search", - "link": true - }, "node_modules/@umbraco-backoffice/segment": { "resolved": "src/packages/segment", "link": true @@ -17101,7 +17097,8 @@ "name": "@umbraco-backoffice/rte" }, "src/packages/search": { - "name": "@umbraco-backoffice/search" + "name": "@umbraco-backoffice/search", + "extraneous": true }, "src/packages/segment": { "name": "@umbraco-backoffice/segment" diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index da7b4d3dab..ca5cc8853a 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -76,14 +76,17 @@ "./menu": "./dist-cms/packages/core/menu/index.js", "./modal": "./dist-cms/packages/core/modal/index.js", "./models": "./dist-cms/packages/core/models/index.js", + "./search": "./dist-cms/packages/core/search/index.js", "./multi-url-picker": "./dist-cms/packages/multi-url-picker/index.js", "./notification": "./dist-cms/packages/core/notification/index.js", "./object-type": "./dist-cms/packages/core/object-type/index.js", "./package": "./dist-cms/packages/packages/package/index.js", "./partial-view": "./dist-cms/packages/templating/partial-views/index.js", "./picker-input": "./dist-cms/packages/core/picker-input/index.js", + "./picker-data-source": "./dist-cms/packages/core/picker-data-source/index.js", "./picker": "./dist-cms/packages/core/picker/index.js", "./property-action": "./dist-cms/packages/core/property-action/index.js", + "./property-editor-data-source": "./dist-cms/packages/core/property-editor-data-source/index.js", "./property-editor": "./dist-cms/packages/core/property-editor/index.js", "./property-type": "./dist-cms/packages/content/property-type/index.js", "./property": "./dist-cms/packages/core/property/index.js", @@ -95,7 +98,6 @@ "./router": "./dist-cms/packages/core/router/index.js", "./rte": "./dist-cms/packages/rte/index.js", "./script": "./dist-cms/packages/templating/scripts/index.js", - "./search": "./dist-cms/packages/search/index.js", "./section": "./dist-cms/packages/core/section/index.js", "./segment": "./dist-cms/packages/segment/index.js", "./server-file-system": "./dist-cms/packages/core/server-file-system/index.js", diff --git a/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts b/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts index b284635d58..558792cabc 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts @@ -37,7 +37,6 @@ const CORE_PACKAGES = [ import('../../packages/publish-cache/umbraco-package.js'), import('../../packages/relations/umbraco-package.js'), import('../../packages/rte/umbraco-package.js'), - import('../../packages/search/umbraco-package.js'), import('../../packages/settings/umbraco-package.js'), import('../../packages/static-file/umbraco-package.js'), import('../../packages/sysinfo/umbraco-package.js'), diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/block-type-custom-view-guide/block-type-custom-view-guide.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/block-type-custom-view-guide/block-type-custom-view-guide.element.ts index 40cc20ba8c..d76496870e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/block-type-custom-view-guide/block-type-custom-view-guide.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/block-type-custom-view-guide/block-type-custom-view-guide.element.ts @@ -37,9 +37,8 @@ export class UmbBlockTypeCustomViewGuideElement extends UmbLitElement { await context?.propertyValueByAlias('contentElementTypeKey'), async (value) => { if (!value) return; - const { asObservable } = await this.#repository.requestByUnique(value); this.observe( - asObservable(), + (await this.#repository.requestByUnique(value)).asObservable?.(), (model) => { this.#contentTypeName = model?.name; this.#contentTypeAlias = model?.alias; diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/components/property-type-based-property/property-type-based-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/components/property-type-based-property/property-type-based-property.element.ts index fea5d1fa32..aeff988144 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content/components/property-type-based-property/property-type-based-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/components/property-type-based-property/property-type-based-property.element.ts @@ -54,9 +54,11 @@ export class UmbPropertyTypeBasedPropertyElement extends UmbLitElement { private _propertyEditorSchemaAlias?: string; @state() - private _isUnsupported?: boolean; + private _propertyEditorDataSourceAlias?: string; @state() + private _isUnsupported?: boolean; + private _dataTypeValues?: UmbPropertyEditorConfig; private _dataTypeDetailRepository = new UmbDataTypeDetailRepository(this); @@ -89,6 +91,7 @@ export class UmbPropertyTypeBasedPropertyElement extends UmbLitElement { this._dataTypeValues = dataType?.values; this._propertyEditorUiAlias = dataType?.editorUiAlias || undefined; this._propertyEditorSchemaAlias = dataType?.editorAlias || undefined; + this._propertyEditorDataSourceAlias = dataType?.editorDataSourceAlias || undefined; this._checkSchemaSupport(); // If there is no UI, we will look up the Property editor model to find the default UI alias: @@ -128,6 +131,7 @@ export class UmbPropertyTypeBasedPropertyElement extends UmbLitElement { .description=${this._property.description ?? undefined} .appearance=${this._property.appearance} property-editor-ui-alias=${ifDefined(this._propertyEditorUiAlias)} + property-editor-data-source-alias=${ifDefined(this._propertyEditorDataSourceAlias)} .config=${this._dataTypeValues} .validation=${this._property.validation} ?readonly=${this.readonly}> diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-item-picker-modal/collection-item-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-item-picker-modal/collection-item-picker-modal.element.ts new file mode 100644 index 0000000000..f9081af885 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-item-picker-modal/collection-item-picker-modal.element.ts @@ -0,0 +1,154 @@ +import type { UmbCollectionSelectionConfiguration } from '../types.js'; +import { UmbCollectionItemPickerContext } from './collection-item-picker-modal.context.js'; +import type { UmbCollectionItemPickerModalData, UmbCollectionItemPickerModalValue } from './types.js'; +import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement, state, nothing, ifDefined } from '@umbraco-cms/backoffice/external/lit'; +import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; +import { UmbDeselectedEvent, UmbSelectedEvent } from '@umbraco-cms/backoffice/event'; + +@customElement('umb-collection-item-picker-modal') +export class UmbCollectionItemPickerModalElement extends UmbModalBaseElement< + UmbCollectionItemPickerModalData, + UmbCollectionItemPickerModalValue +> { + @state() + private _selectionConfiguration: UmbCollectionSelectionConfiguration = { + multiple: false, + selectable: true, + selection: [], + }; + + @state() + private _hasSelection: boolean = false; + + @state() + private _searchQuery?: string; + + #pickerContext = new UmbCollectionItemPickerContext(this); + + constructor() { + super(); + this.#pickerContext.selection.setSelectable(true); + this.observe(this.#pickerContext.selection.hasSelection, (hasSelection) => { + this._hasSelection = hasSelection; + }); + this.#observePickerSelection(); + this.#observeSearch(); + } + + protected override async updated(_changedProperties: PropertyValueMap | Map) { + super.updated(_changedProperties); + + if (_changedProperties.has('data')) { + if (this.data?.search) { + this.#pickerContext.search.updateConfig({ + ...this.data.search, + }); + } + + const multiple = this.data?.multiple ?? false; + this.#pickerContext.selection.setMultiple(multiple); + + this._selectionConfiguration = { + ...this._selectionConfiguration, + multiple, + }; + } + + if (_changedProperties.has('value')) { + const selection = this.value?.selection ?? []; + this.#pickerContext.selection.setSelection(selection); + this._selectionConfiguration = { + ...this._selectionConfiguration, + selection: [...selection], + }; + } + } + + #observePickerSelection() { + this.observe( + this.#pickerContext.selection.selection, + (selection) => { + this.updateValue({ selection }); + this.requestUpdate(); + }, + 'umbPickerSelectionObserver', + ); + } + + #observeSearch() { + this.observe( + this.#pickerContext.search.query, + (query) => { + this._searchQuery = query?.query; + }, + 'umbPickerSearchQueryObserver', + ); + } + + #onItemSelected(event: UmbSelectedEvent) { + event.stopPropagation(); + this.#pickerContext.selection.select(event.unique); + this.modalContext?.dispatchEvent(new UmbSelectedEvent(event.unique)); + } + + #onItemDeselected(event: UmbDeselectedEvent) { + event.stopPropagation(); + this.#pickerContext.selection.deselect(event.unique); + this.modalContext?.dispatchEvent(new UmbDeselectedEvent(event.unique)); + } + + override render() { + return html` + + ${this.#renderSearch()} ${this.#renderCollection()} + ${this.#renderActions()} + + `; + } + #renderSearch() { + return html` + + + `; + } + + #renderCollection() { + if (this._searchQuery) { + return nothing; + } + + return html` `; + } + + #renderActions() { + return html` +
+ + +
+ `; + } +} + +export { UmbCollectionItemPickerModalElement as element }; + +declare global { + interface HTMLElementTagNameMap { + 'umb-collection-item-picker-modal': UmbCollectionItemPickerModalElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-item-picker-modal/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-item-picker-modal/constants.ts new file mode 100644 index 0000000000..aed0c8bcc2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-item-picker-modal/constants.ts @@ -0,0 +1 @@ +export const UMB_COLLECTION_ITEM_PICKER_MODAL_ALIAS = 'Umb.Modal.CollectionItemPicker'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-item-picker-modal/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-item-picker-modal/manifests.ts new file mode 100644 index 0000000000..c06fb671c7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-item-picker-modal/manifests.ts @@ -0,0 +1,10 @@ +import { UMB_COLLECTION_ITEM_PICKER_MODAL_ALIAS } from './constants.js'; + +export const manifests: Array = [ + { + type: 'modal', + alias: UMB_COLLECTION_ITEM_PICKER_MODAL_ALIAS, + name: 'Collection Item Picker Modal', + element: () => import('./collection-item-picker-modal.element.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-item-picker-modal/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-item-picker-modal/types.ts new file mode 100644 index 0000000000..88317726bd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-item-picker-modal/types.ts @@ -0,0 +1,15 @@ +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; +import type { UmbPickerModalData, UmbPickerModalValue } from '@umbraco-cms/backoffice/modal'; + +export interface UmbCollectionItemPickerModalData + extends UmbPickerModalData { + collection: UmbCollectionItemPickerModalCollectionConfig; +} + +export interface UmbCollectionItemPickerModalCollectionConfig> { + menuAlias: string; + filterArgs?: FilterArgsType; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface UmbCollectionItemPickerModalValue extends UmbPickerModalValue {} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/constants.ts new file mode 100644 index 0000000000..90c7f72dc0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/constants.ts @@ -0,0 +1,2 @@ +export * from './collection-item-picker-modal/constants.js'; +export * from './menu/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/global-components.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/global-components.ts new file mode 100644 index 0000000000..d567a5dbd8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/global-components.ts @@ -0,0 +1,3 @@ +import './menu/collection-menu.element.js'; + +export * from './menu/collection-menu.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/index.ts index 7ffdd86cf6..5f32ec4ac1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/index.ts @@ -2,11 +2,13 @@ import './default/collection-default.element.js'; import './collection.element.js'; import './components/index.js'; -export * from './default/collection-default.element.js'; +export * from './collection-item-picker-modal/index.js'; export * from './collection.element.js'; export * from './components/index.js'; export * from './conditions/index.js'; -export * from './collection-item-picker-modal/index.js'; +export * from './constants.js'; +export * from './default/collection-default.element.js'; +export * from './global-components.js'; export * from './workspace-view/index.js'; export * from './default/collection-default.context.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/item/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/item/types.ts new file mode 100644 index 0000000000..528282ebdb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/item/types.ts @@ -0,0 +1,7 @@ +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; + +export interface UmbCollectionItemModel extends UmbEntityModel { + unique: string; + name?: string; + icon?: string; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/manifests.ts index c59c5fa9f4..ee82008f76 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/manifests.ts @@ -1,10 +1,14 @@ import type { UmbExtensionManifestKind } from '../extension-registry/registry.js'; -import { manifests as conditionManifests } from './conditions/manifests.js'; import { manifests as actionManifests } from './action/manifests.js'; +import { manifests as conditionManifests } from './conditions/manifests.js'; +import { manifests as menuManifests } from './menu/manifests.js'; +import { manifests as pickerModalManifests } from './collection-item-picker-modal/manifests.js'; import { manifests as workspaceViewManifests } from './workspace-view/manifests.js'; export const manifests: Array = [ ...actionManifests, - ...workspaceViewManifests, ...conditionManifests, + ...menuManifests, + ...pickerModalManifests, + ...workspaceViewManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/collection-menu.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/collection-menu.element.ts new file mode 100644 index 0000000000..7058b09161 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/collection-menu.element.ts @@ -0,0 +1,27 @@ +import type { ManifestCollectionMenu } from './extension/types.js'; +import { customElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbExtensionElementAndApiSlotElementBase } from '@umbraco-cms/backoffice/extension-registry'; + +@customElement('umb-collection-menu') +export class UmbCollectionMenuElement extends UmbExtensionElementAndApiSlotElementBase { + getExtensionType() { + return 'collectionMenu'; + } + + getDefaultElementName() { + return 'umb-default-collection-menu'; + } + + getSelection() { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // TODO: make base interface for a collection menu element + return this._element?.getSelection?.() ?? []; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-collection-menu': UmbCollectionMenuElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/constants.ts new file mode 100644 index 0000000000..5b0fcb8cb6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/constants.ts @@ -0,0 +1 @@ +export { UMB_COLLECTION_MENU_CONTEXT } from './default/default-collection-menu.context.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/default/default-collection-menu.context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/default/default-collection-menu.context.token.ts new file mode 100644 index 0000000000..2ee0654be7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/default/default-collection-menu.context.token.ts @@ -0,0 +1,6 @@ +import type { UmbDefaultCollectionMenuContext } from './default-collection-menu.context.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const UMB_COLLECTION_MENU_CONTEXT = new UmbContextToken( + 'UmbCollectionMenuContext', +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/default/default-collection-menu.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/default/default-collection-menu.context.ts new file mode 100644 index 0000000000..de32faad25 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/default/default-collection-menu.context.ts @@ -0,0 +1,123 @@ +import type { ManifestCollectionMenu } from '../extension/types.js'; +import type { UmbCollectionRepository } from '../../repository/index.js'; +import type { UmbCollectionItemModel } from '../../item/types.js'; +import { UMB_COLLECTION_MENU_CONTEXT } from './default-collection-menu.context.token.js'; +import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbPaginationManager, UmbSelectionManager } from '@umbraco-cms/backoffice/utils'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api'; +import { umbExtensionsRegistry, type ManifestRepository } from '@umbraco-cms/backoffice/extension-registry'; +import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; + +export class UmbDefaultCollectionMenuContext extends UmbContextBase { + public selectableFilter?: (item: UmbCollectionItemModel) => boolean = () => true; + public filter?: (item: UmbCollectionItemModel) => boolean = () => true; + public filterArgs?: Record; + + public readonly selection = new UmbSelectionManager(this); + public readonly pagination = new UmbPaginationManager(); + + #items = new UmbArrayState([], (x) => x.unique); + items = this.#items.asObservable(); + + #manifest?: ManifestCollectionMenu; + #repository?: UmbCollectionRepository; + + #paging = { + skip: 0, + take: 50, + }; + + #initResolver?: () => void; + #initialized = false; + + #init = new Promise((resolve) => { + if (this.#initialized) { + resolve(); + } else { + this.#initResolver = resolve; + } + }); + + constructor(host: UmbControllerHost) { + super(host, UMB_COLLECTION_MENU_CONTEXT); + + this.pagination.setPageSize(this.#paging.take); + //this.#consumeContexts(); + + // listen for page changes on the pagination manager + this.pagination.addEventListener(UmbChangeEvent.TYPE, this.#onPageChange); + + // always load the tree root because we need the root entity to reload the entire tree + this.#loadItems(); + } + + /** + * Sets the manifest + * @param {ManifestTree} manifest + * @memberof UmbDefaultTreeContext + */ + public set manifest(manifest: ManifestCollectionMenu | undefined) { + if (this.#manifest === manifest) return; + this.#manifest = manifest; + this.#observeRepository(this.#manifest?.meta.collectionRepositoryAlias); + } + public get manifest() { + return this.#manifest; + } + + #checkIfInitialized() { + if (this.#repository) { + this.#initialized = true; + this.#initResolver?.(); + } + } + + #onPageChange = (event: UmbChangeEvent) => { + const target = event.target as UmbPaginationManager; + this.#paging.skip = target.getSkip(); + this.#loadItems(true); + }; + + async #loadItems(loadMore = false) { + await this.#init; + + const skip = loadMore ? this.#paging.skip : 0; + const take = loadMore ? this.#paging.take : this.pagination.getCurrentPageNumber() * this.#paging.take; + + const { data } = await this.#repository!.requestCollection({ + ...this.filterArgs, + skip, + take, + }); + + if (data) { + if (loadMore) { + const currentItems = this.#items.getValue(); + this.#items.setValue([...currentItems, ...data.items]); + } else { + this.#items.setValue(data.items); + } + + this.pagination.setTotalItems(data.total); + } + } + + #observeRepository(repositoryAlias?: string) { + if (!repositoryAlias) throw new Error('Collection Menu must have a repository alias.'); + + new UmbExtensionApiInitializer>( + this, + umbExtensionsRegistry, + repositoryAlias, + [this], + (permitted, ctrl) => { + this.#repository = permitted ? ctrl.api : undefined; + this.#checkIfInitialized(); + }, + ); + } +} + +export { UmbDefaultCollectionMenuContext as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/default/default-collection-menu.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/default/default-collection-menu.element.ts new file mode 100644 index 0000000000..a1273792f3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/default/default-collection-menu.element.ts @@ -0,0 +1,162 @@ +import type { UmbCollectionItemModel } from '../../item/types.js'; +import type { UmbCollectionSelectionConfiguration } from '../../types.js'; +import type { UmbDefaultCollectionMenuContext } from './default-collection-menu.context.js'; +import { getItemFallbackIcon, getItemFallbackName } from '@umbraco-cms/backoffice/entity-item'; +import { + html, + customElement, + property, + type PropertyValueMap, + state, + repeat, + nothing, + css, +} from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; + +@customElement('umb-default-collection-menu') +export class UmbDefaultCollectionMenuElement extends UmbLitElement { + private _api: UmbDefaultCollectionMenuContext | undefined; + @property({ type: Object, attribute: false }) + public get api(): UmbDefaultCollectionMenuContext | undefined { + return this._api; + } + public set api(value: UmbDefaultCollectionMenuContext | undefined) { + this._api = value; + + if (this._api) { + this._api.filterArgs = this.#filterArgs; + } + + this.#observeData(); + } + + private _selectionConfiguration: UmbCollectionSelectionConfiguration = { + multiple: false, + selectable: true, + selection: [], + }; + @property({ type: Object, attribute: false }) + selectionConfiguration: UmbCollectionSelectionConfiguration = this._selectionConfiguration; + + @property({ attribute: false }) + selectableFilter: (item: UmbCollectionItemModel) => boolean = () => true; + + @property({ attribute: false }) + filter: (item: UmbCollectionItemModel) => boolean = () => true; + + public get filterArgs(): Record | undefined { + return this.#filterArgs; + } + public set filterArgs(value: Record | undefined) { + this.#filterArgs = value; + + if (this._api) { + this._api.filterArgs = this.#filterArgs; + } + } + + #filterArgs: Record | undefined; + + @state() + private _items: Array = []; + + @state() + private _currentPage = 1; + + @state() + private _totalPages = 1; + + #observeData() { + this.observe(this._api?.items, (items) => (this._items = items ?? [])); + this.observe(this._api?.pagination.currentPage, (value) => (this._currentPage = value ?? 1)); + this.observe(this._api?.pagination.totalPages, (value) => (this._totalPages = value ?? 1)); + } + + protected override async updated( + _changedProperties: PropertyValueMap | Map, + ): Promise { + super.updated(_changedProperties); + if (this._api === undefined) return; + + if (_changedProperties.has('selectionConfiguration')) { + this._selectionConfiguration = this.selectionConfiguration; + + this._api!.selection.setMultiple(this._selectionConfiguration.multiple ?? false); + this._api!.selection.setSelectable(this._selectionConfiguration.selectable ?? true); + this._api!.selection.setSelection(this._selectionConfiguration.selection ?? []); + } + + if (_changedProperties.has('selectableFilter')) { + this._api!.selectableFilter = this.selectableFilter; + } + + if (_changedProperties.has('filter')) { + this._api!.filter = this.filter; + } + } + + #onLoadMoreClick = (event: any) => { + event.stopPropagation(); + const next = (this._currentPage = this._currentPage + 1); + this._api?.pagination.setCurrentPageNumber(next); + }; + + override render() { + return html` + ${repeat( + this._items, + (item) => item.unique, + (item) => this.#renderItem(item), + )} + ${this.#renderPaging()} + `; + } + + #renderItem(item: UmbCollectionItemModel) { + return html` + this._api?.selection.select(item.unique)} + @deselected=${() => this._api?.selection.deselect(item.unique)} + ?selected=${this._api?.selection.isSelected(item.unique)}> + ${item.icon + ? html`` + : html``} + + `; + } + + #renderPaging() { + if (this._totalPages <= 1 || this._currentPage === this._totalPages) { + return nothing; + } + + return html` `; + } + + static override styles = css` + :host { + --uui-menu-item-indent: 0; + --uui-menu-item-flat-structure: 1; + } + + #load-more { + width: 100%; + } + `; +} + +export { UmbDefaultCollectionMenuElement as element }; + +declare global { + interface HTMLElementTagNameMap { + 'umb-default-collection-menu': UmbDefaultCollectionMenuElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/default/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/default/index.ts new file mode 100644 index 0000000000..2400048918 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/default/index.ts @@ -0,0 +1,2 @@ +export { UMB_COLLECTION_MENU_CONTEXT as UMB_COLLECTION_CONTEXT } from './default-collection-menu.context.token.js'; +export { UmbDefaultCollectionMenuContext } from './default-collection-menu.context.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/default/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/default/manifests.ts new file mode 100644 index 0000000000..6c2f94e287 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/default/manifests.ts @@ -0,0 +1,16 @@ +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'kind', + alias: 'Umb.Kind.CollectionMenu.Default', + matchKind: 'default', + matchType: 'collectionMenu', + manifest: { + type: 'collectionMenu', + kind: 'default', + element: () => import('./default-collection-menu.element.js'), + api: () => import('./default-collection-menu.context.js'), + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/extension/collection-menu.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/extension/collection-menu.extension.ts new file mode 100644 index 0000000000..b61540c7e8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/extension/collection-menu.extension.ts @@ -0,0 +1,16 @@ +import type { ManifestElementAndApi } from '@umbraco-cms/backoffice/extension-api'; + +export interface ManifestCollectionMenu extends ManifestElementAndApi { + type: 'collectionMenu'; + meta: MetaCollectionMenu; +} + +export interface MetaCollectionMenu { + collectionRepositoryAlias: string; +} + +declare global { + interface UmbExtensionManifestMap { + UmbCollectionMenu: ManifestCollectionMenu; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/extension/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/extension/types.ts new file mode 100644 index 0000000000..fc15649134 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/extension/types.ts @@ -0,0 +1 @@ +export type * from './collection-menu.extension.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/manifests.ts new file mode 100644 index 0000000000..43d020c74e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/manifests.ts @@ -0,0 +1,4 @@ +import { manifests as defaultManifests } from './default/manifests.js'; +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [...defaultManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/types.ts new file mode 100644 index 0000000000..90c1bd6808 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/menu/types.ts @@ -0,0 +1 @@ +export type * from './extension/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/types.ts index f76d9ef226..67fb479e88 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/types.ts @@ -4,9 +4,12 @@ import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; import type { UmbPaginationManager } from '@umbraco-cms/backoffice/utils'; export type * from './action/create/types.js'; -export type * from './extensions/types.js'; export type * from './conditions/types.js'; +export type * from './extensions/types.js'; +export type * from './item/types.js'; +export type * from './menu/types.js'; export type * from './workspace-view/types.js'; +export type * from './collection-item-picker-modal/types.js'; export interface UmbCollectionConfiguration { unique?: UmbEntityUnique; @@ -33,6 +36,12 @@ export interface UmbCollectionLayoutConfiguration { collectionView: string; } +export type UmbCollectionSelectionConfiguration = { + multiple?: boolean; + selectable?: boolean; + selection?: Array; +}; + export interface UmbCollectionContext { setConfig(config: UmbCollectionConfiguration): void; getConfig(): UmbCollectionConfiguration | undefined; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-entity/input-entity.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-entity/input-entity.element.ts index 5946030eb4..1c4a68b727 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-entity/input-entity.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-entity/input-entity.element.ts @@ -5,8 +5,8 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import type { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; -import type { UmbUniqueItemModel } from '@umbraco-cms/backoffice/models'; import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; +import { getItemFallbackName, type UmbItemModel } from '@umbraco-cms/backoffice/entity-item'; @customElement('umb-input-entity') export class UmbInputEntityElement extends UmbFormControlMixin( @@ -60,7 +60,7 @@ export class UmbInputEntityElement extends UmbFormControlMixin string; + getIcon?: (item: UmbItemModel) => string; @property({ type: String, attribute: 'min-message' }) maxMessage = 'This field exceeds the allowed amount of items'; @@ -93,7 +93,7 @@ export class UmbInputEntityElement extends UmbFormControlMixin; + private _items?: Array; #pickerContext?: UmbPickerInputContext; @@ -133,7 +133,7 @@ export class UmbInputEntityElement extends UmbFormControlMixin + ${when(icon, () => html``)} this.#removeItem(item)} label=${this.localize.term('general_remove')}> diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/entity-item-ref/default-item-ref.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/entity-item-ref/default-item-ref.element.ts index 44c750fe70..e4355dd70d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/entity-item-ref/default-item-ref.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/entity-item-ref/default-item-ref.element.ts @@ -1,11 +1,12 @@ -import type { UmbDefaultItemModel } from '../types.js'; +import type { UmbItemModel } from '../types.js'; +import { getItemFallbackIcon, getItemFallbackName } from '../utils.js'; import { customElement, html, nothing, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @customElement('umb-default-item-ref') export class UmbDefaultItemRefElement extends UmbLitElement { @property({ type: Object }) - item?: UmbDefaultItemModel; + item?: UmbItemModel; @property({ type: Boolean }) standalone = false; @@ -14,16 +15,19 @@ export class UmbDefaultItemRefElement extends UmbLitElement { if (!this.item) return nothing; return html` - + ${this.#renderIcon(this.item)} `; } - #renderIcon(item: UmbDefaultItemModel) { - if (!item.icon) return; - return html``; + #renderIcon(item: UmbItemModel) { + const icon = item.icon || getItemFallbackIcon(); + return html``; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/index.ts index 37c47e5ec3..985ccb3b2e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/index.ts @@ -1,4 +1,7 @@ export * from './item-data-api-get-request-controller/index.js'; export * from './entity-item-ref/index.js'; +export * from './utils.js'; + +export type { UmbItemModel } from './types.js'; export type { UmbItemDataResolver, UmbItemDataResolverConstructor } from './data-resolver/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/types.ts index 3c778ff06b..573e05a7a8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/types.ts @@ -2,6 +2,10 @@ import type { UmbEntityModel, UmbNamedEntityModel } from '@umbraco-cms/backoffic export type * from './item-data-api-get-request-controller/types.js'; export type * from './data-resolver/types.js'; +// TODO: v19 - remove +/** + * @deprecated - Deprecated since v17. Will be removed in v19. Use UmbItemModel instead. + */ export interface UmbDefaultItemModel extends UmbNamedEntityModel { icon?: string; } @@ -9,5 +13,5 @@ export interface UmbDefaultItemModel extends UmbNamedEntityModel { export interface UmbItemModel extends UmbEntityModel { unique: string; name?: string; - icon?: string; + icon?: string | null; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/utils.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/utils.ts new file mode 100644 index 0000000000..d2778809fe --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/utils.ts @@ -0,0 +1,18 @@ +import type { UmbItemModel } from './types.js'; + +/** + * Returns a fallback name for an item + * @param {UmbItemModel} item The item to get the fallback name for + * @returns A fallback name + */ +export function getItemFallbackName(item: UmbItemModel): string { + return `${item.entityType}:${item.unique}`; +} + +/** + * Returns a fallback icon for an item + * @returns A fallback icon + */ +export function getItemFallbackIcon(): string { + return 'icon-circle-dotted'; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entry-point.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entry-point.ts index 09b197e06e..a4ee26214f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entry-point.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entry-point.ts @@ -8,11 +8,13 @@ import { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; import { UmbNotificationContext } from '@umbraco-cms/backoffice/notification'; import type { UmbEntryPointOnInit } from '@umbraco-cms/backoffice/extension-api'; -import './property-action/components/index.js'; -import './menu/components/index.js'; -import './extension-registry/components/index.js'; +import './collection/global-components.js'; import './entity-item/global-components.js'; import './entity-sign/components/index.js'; +import './extension-registry/components/index.js'; +import './menu/components/index.js'; +import './property-action/components/index.js'; +import './property-editor-data-source/global-components.js'; export const onInit: UmbEntryPointOnInit = (host, extensionRegistry) => { new UmbExtensionsApiInitializer(host, extensionRegistry, 'globalContext', [host]); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/extension-element-and-api-slot-element-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/extension-element-and-api-slot-element-base.ts index 95f219131a..03ac8a8bbd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/extension-element-and-api-slot-element-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/extension-element-and-api-slot-element-base.ts @@ -1,7 +1,7 @@ import { umbExtensionsRegistry } from './registry.js'; import { property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import type { ManifestElementAndApi } from '@umbraco-cms/backoffice/extension-api'; +import type { ClassConstructor, ManifestElementAndApi } from '@umbraco-cms/backoffice/extension-api'; import { UmbExtensionElementAndApiInitializer } from '@umbraco-cms/backoffice/extension-api'; export abstract class UmbExtensionElementAndApiSlotElementBase< @@ -42,6 +42,8 @@ export abstract class UmbExtensionElementAndApiSlotElementBase< abstract getExtensionType(): string; abstract getDefaultElementName(): string; + public getDefaultApiConstructor?(): ClassConstructor; + #observeManifest() { if (!this.alias) return; @@ -52,6 +54,7 @@ export abstract class UmbExtensionElementAndApiSlotElementBase< [this], this.#extensionChanged, this.getDefaultElementName(), + this.getDefaultApiConstructor?.(), ); this.#extensionController.elementProps = this.props; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts index 4cccf89abb..bd889e2d9d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts @@ -13,9 +13,11 @@ import { manifests as menuManifests } from './menu/manifests.js'; import { manifests as modalManifests } from './modal/manifests.js'; import { manifests as pickerManifests } from './picker/manifests.js'; import { manifests as propertyActionManifests } from './property-action/manifests.js'; +import { manifests as propertyEditorDataSourceManifests } from './property-editor-data-source/manifests.js'; import { manifests as propertyEditorManifests } from './property-editor/manifests.js'; import { manifests as propertyManifests } from './property/manifests.js'; import { manifests as recycleBinManifests } from './recycle-bin/manifests.js'; +import { manifests as searchManifests } from './search/manifests.js'; import { manifests as sectionManifests } from './section/manifests.js'; import { manifests as serverFileSystemManifests } from './server-file-system/manifests.js'; import { manifests as temporaryFileManifests } from './temporary-file/manifests.js'; @@ -41,9 +43,11 @@ export const manifests: Array = ...modalManifests, ...pickerManifests, ...propertyActionManifests, + ...propertyEditorDataSourceManifests, ...propertyEditorManifests, ...propertyManifests, ...recycleBinManifests, + ...searchManifests, ...sectionManifests, ...serverFileSystemManifests, ...temporaryFileManifests, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/collection-data-source/is-picker-collection-data-source.guard.ts b/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/collection-data-source/is-picker-collection-data-source.guard.ts new file mode 100644 index 0000000000..827a51dcf3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/collection-data-source/is-picker-collection-data-source.guard.ts @@ -0,0 +1,9 @@ +import type { UmbPickerCollectionDataSource } from './types.js'; + +/** + * + * @param dataSource + */ +export function isPickerCollectionDataSource(dataSource: unknown): dataSource is UmbPickerCollectionDataSource { + return (dataSource as UmbPickerCollectionDataSource).requestCollection !== undefined; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/collection-data-source/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/collection-data-source/types.ts new file mode 100644 index 0000000000..c8ef834453 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/collection-data-source/types.ts @@ -0,0 +1,5 @@ +import type { UmbPickerDataSource } from '../data-source/types.js'; +import type { UmbCollectionRepository } from '@umbraco-cms/backoffice/collection'; +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; + +export interface UmbPickerCollectionDataSource extends UmbPickerDataSource, UmbCollectionRepository, UmbApi {} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/data-source/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/data-source/types.ts new file mode 100644 index 0000000000..ee184a09b3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/data-source/types.ts @@ -0,0 +1,8 @@ +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; +import type { UmbItemRepository } from '@umbraco-cms/backoffice/repository'; +import type { UmbConfigCollectionModel } from '@umbraco-cms/backoffice/utils'; + +export interface UmbPickerDataSource extends UmbItemRepository, UmbApi { + setConfig?(config: UmbConfigCollectionModel | undefined): void; + getConfig?(): UmbConfigCollectionModel | undefined; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/index.ts new file mode 100644 index 0000000000..ca0e12ded8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/index.ts @@ -0,0 +1,4 @@ +export * from './collection-data-source/is-picker-collection-data-source.guard.js'; +export * from './searchable-data-source/is-picker-searchable.data-source.guard.js'; +export * from './tree-data-source/is-picker-tree-data-source.guard.js'; +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/searchable-data-source/is-picker-searchable.data-source.guard.ts b/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/searchable-data-source/is-picker-searchable.data-source.guard.ts new file mode 100644 index 0000000000..b502978b39 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/searchable-data-source/is-picker-searchable.data-source.guard.ts @@ -0,0 +1,9 @@ +import type { UmbPickerSearchableDataSource } from './types.js'; + +/** + * + * @param dataSource + */ +export function isPickerSearchableDataSource(dataSource: unknown): dataSource is UmbPickerSearchableDataSource { + return (dataSource as UmbPickerSearchableDataSource).search !== undefined; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/searchable-data-source/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/searchable-data-source/types.ts new file mode 100644 index 0000000000..9baee08655 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/searchable-data-source/types.ts @@ -0,0 +1,4 @@ +import type { UmbPickerDataSource } from '../types.js'; +import type { UmbSearchRepository } from '@umbraco-cms/backoffice/search'; + +export interface UmbPickerSearchableDataSource extends UmbPickerDataSource, UmbSearchRepository {} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/tree-data-source/is-picker-tree-data-source.guard.ts b/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/tree-data-source/is-picker-tree-data-source.guard.ts new file mode 100644 index 0000000000..e8ba157335 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/tree-data-source/is-picker-tree-data-source.guard.ts @@ -0,0 +1,13 @@ +import type { UmbPickerTreeDataSource } from './types.js'; + +/** + * + * @param dataSource + */ +export function isPickerTreeDataSource(dataSource: unknown): dataSource is UmbPickerTreeDataSource { + return ( + (dataSource as UmbPickerTreeDataSource).requestTreeRoot !== undefined && + (dataSource as UmbPickerTreeDataSource).requestTreeRootItems !== undefined && + (dataSource as UmbPickerTreeDataSource).requestTreeItemsOf !== undefined + ); +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/tree-data-source/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/tree-data-source/types.ts new file mode 100644 index 0000000000..ce5cb9d63b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/tree-data-source/types.ts @@ -0,0 +1,5 @@ +import type { UmbPickerDataSource } from '../data-source/types.js'; +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; +import type { UmbTreeRepository } from '@umbraco-cms/backoffice/tree'; + +export interface UmbPickerTreeDataSource extends UmbPickerDataSource, UmbTreeRepository, UmbApi {} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/types.ts new file mode 100644 index 0000000000..573c9dbca2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/picker-data-source/types.ts @@ -0,0 +1,4 @@ +export type * from './collection-data-source/types.js'; +export type * from './searchable-data-source/types.js'; +export type * from './tree-data-source/types.js'; +export type * from './data-source/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/picker-input/picker-input.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/picker-input/picker-input.context.ts index e3d1483b99..eb86ba1879 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/picker-input/picker-input.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/picker-input/picker-input.context.ts @@ -12,16 +12,15 @@ import { type UmbPickerModalData, type UmbPickerModalValue, } from '@umbraco-cms/backoffice/modal'; - -type PickerItemBaseType = { name: string; unique: string }; +import type { UmbItemModel } from '@umbraco-cms/backoffice/entity-item'; export class UmbPickerInputContext< - PickedItemType extends PickerItemBaseType = PickerItemBaseType, - PickerItemType extends PickerItemBaseType = PickedItemType, + PickedItemType extends UmbItemModel = UmbItemModel, + PickerItemType extends UmbItemModel = UmbItemModel, PickerModalConfigType extends UmbPickerModalData = UmbPickerModalData, PickerModalValueType extends UmbPickerModalValue = UmbPickerModalValue, > extends UmbContextBase { - modalAlias: string | UmbModalToken, PickerModalValueType>; + modalAlias?: string | UmbModalToken, PickerModalValueType>; repository?: UmbItemRepository; #itemManager; @@ -33,6 +32,7 @@ export class UmbPickerInputContext< /** * Define a minimum amount of selected items in this input, for this input to be valid. + * @returns {number} The minimum number of items required. */ public get max() { return this._max; @@ -44,6 +44,7 @@ export class UmbPickerInputContext< /** * Define a maximum amount of selected items in this input, for this input to be valid. + * @returns {number} The minimum number of items required. */ public get min() { return this._min; @@ -63,10 +64,13 @@ export class UmbPickerInputContext< constructor( host: UmbControllerHost, repositoryAlias: string, - modalAlias: string | UmbModalToken, PickerModalValueType>, + modalAlias?: string | UmbModalToken, PickerModalValueType>, ) { super(host, UMB_PICKER_INPUT_CONTEXT); - this.modalAlias = modalAlias; + + if (modalAlias) { + this.modalAlias = modalAlias; + } this.#itemManager = new UmbRepositoryItemsManager(this, repositoryAlias); @@ -86,6 +90,11 @@ export class UmbPickerInputContext< async openPicker(pickerData?: Partial) { await this.#itemManager.init; + + if (!this.modalAlias) { + throw new Error('No modal alias defined for the picker input context.'); + } + const modalValue = await umbOpenModal(this, this.modalAlias, { data: { multiple: this._max === 1 ? false : true, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/picker/search/picker-search-result.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/picker/search/picker-search-result.element.ts index 4fcbe790de..242d58b00a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/picker/search/picker-search-result.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/picker/search/picker-search-result.element.ts @@ -1,12 +1,14 @@ import { UMB_PICKER_CONTEXT } from '../picker.context.token.js'; import type { UmbPickerContext } from '../picker.context.js'; +import { UmbDefaultPickerSearchResultItemElement } from './result-item/default/default-picker-search-result-item.element.js'; import type { ManifestPickerSearchResultItem } from './result-item/picker-search-result-item.extension.js'; +import { UmbDefaultPickerSearchResultItemContext } from './result-item/default/default-picker-search-result-item.context.js'; import { customElement, html, nothing, property, repeat, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import type { UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; -import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; +import type { UmbSearchRequestArgs, UmbSearchResultItemModel } from '@umbraco-cms/backoffice/search'; +import type { UmbItemModel } from '@umbraco-cms/backoffice/entity-item'; -type PickableFilterMethodType = (item: T) => boolean; +type PickableFilterMethodType = (item: T) => boolean; @customElement('umb-picker-search-result') export class UmbPickerSearchResultElement extends UmbLitElement { @@ -17,7 +19,7 @@ export class UmbPickerSearchResultElement extends UmbLitElement { private _searching: boolean = false; @state() - private _items: UmbEntityModel[] = []; + private _items: UmbItemModel[] = []; @state() private _isSearchable: boolean = false; @@ -67,7 +69,7 @@ export class UmbPickerSearchResultElement extends UmbLitElement { return html`No result for "${this._query?.query}".`; } - #renderResultItem(item: UmbEntityModel) { + #renderResultItem(item: UmbSearchResultItemModel) { return html` + }} + .fallbackRenderMethod=${() => this.#renderFallbackResultItem(item)}> `; } + + #renderFallbackResultItem(item: UmbSearchResultItemModel) { + const element = new UmbDefaultPickerSearchResultItemElement(); + element.item = item; + element.disabled = this.pickableFilter ? !this.pickableFilter(item) : undefined; + new UmbDefaultPickerSearchResultItemContext(element); + return element; + } } declare global { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/picker/search/result-item/default/default-picker-search-result-item.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/picker/search/result-item/default/default-picker-search-result-item.element.ts index a936ff88d7..6cc772b24e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/picker/search/result-item/default/default-picker-search-result-item.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/picker/search/result-item/default/default-picker-search-result-item.element.ts @@ -1,4 +1,5 @@ import { UmbPickerSearchResultItemElementBase } from '../picker-search-result-item-element-base.js'; +import { getItemFallbackIcon, getItemFallbackName } from '@umbraco-cms/backoffice/entity-item'; import { customElement, html, nothing } from '@umbraco-cms/backoffice/external/lit'; import type { UmbSearchResultItemModel } from '@umbraco-cms/backoffice/search'; @@ -9,9 +10,9 @@ export class UmbDefaultPickerSearchResultItemElement extends UmbPickerSearchResu if (!item) return nothing; return html` extends UmbLitElement { +export abstract class UmbPickerSearchResultItemElementBase extends UmbLitElement { #item: ItemType | undefined; protected pickerContext?: UmbPickerContext; @@ -50,7 +50,7 @@ export abstract class UmbPickerSearchResultItemElementBase +{ + async getCollection(args: UmbPropertyEditorDataSourceCollectionFilterModel) { + const extensions = umbExtensionsRegistry.getByType('propertyEditorDataSource'); + + const extensionsWithAllowedDataSourceTypes = extensions.filter((ext) => { + if (args.dataSourceTypes && args.dataSourceTypes.length > 0) { + return args.dataSourceTypes.includes(ext.dataSourceType); + } + return true; + }); + + const filtered = extensionsWithAllowedDataSourceTypes.filter((manifest) => + manifest.name.toLowerCase().includes(args.filter?.toLowerCase() ?? ''), + ); + + const extensionsOrderedByLabel = filtered.sort((a, b) => a.name.localeCompare(b.name)); + + const skip = args.skip ?? 0; + const take = args.take ?? 100; + + const paged = extensionsOrderedByLabel.slice(skip, skip + take); + + const items: Array = paged.map((manifest) => ({ + entityType: UMB_PROPERTY_EDITOR_DATA_SOURCE_ENTITY_TYPE, + unique: manifest.alias, + name: manifest.meta.label ?? manifest.name, + icon: manifest.meta.icon ?? 'icon-box', + })); + + return { + data: { + items, + total: filtered.length, + }, + }; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/data/collection.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/data/collection.repository.ts new file mode 100644 index 0000000000..31e1e85f05 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/data/collection.repository.ts @@ -0,0 +1,17 @@ +import { UmbPropertyEditorDataSourceCollectionExtensionRegistryDataSource } from './collection.extension-registry.data-source.js'; +import type { UmbPropertyEditorDataSourceCollectionFilterModel } from './types.js'; +import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository'; +import type { UmbCollectionRepository } from '@umbraco-cms/backoffice/collection'; + +export class UmbPropertyEditorDataSourceCollectionRepository + extends UmbRepositoryBase + implements UmbCollectionRepository +{ + #collectionSource = new UmbPropertyEditorDataSourceCollectionExtensionRegistryDataSource(this); + + async requestCollection(filter: UmbPropertyEditorDataSourceCollectionFilterModel) { + return this.#collectionSource.getCollection(filter); + } +} + +export { UmbPropertyEditorDataSourceCollectionRepository as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/data/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/data/constants.ts new file mode 100644 index 0000000000..a19c686c08 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/data/constants.ts @@ -0,0 +1,2 @@ +export const UMB_PROPERTY_EDITOR_DATA_SOURCE_COLLECTION_REPOSITORY_ALIAS = + 'Umb.Repository.PropertyEditorDataSourceCollection'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/data/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/data/index.ts new file mode 100644 index 0000000000..3fe6f285aa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/data/index.ts @@ -0,0 +1,2 @@ +export { UMB_PROPERTY_EDITOR_DATA_SOURCE_COLLECTION_REPOSITORY_ALIAS } from './constants.js'; +export { UmbPropertyEditorDataSourceCollectionRepository } from './collection.repository.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/data/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/data/manifests.ts new file mode 100644 index 0000000000..44c18ffa01 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/data/manifests.ts @@ -0,0 +1,10 @@ +import { UMB_PROPERTY_EDITOR_DATA_SOURCE_COLLECTION_REPOSITORY_ALIAS } from './constants.js'; + +export const manifests: Array = [ + { + type: 'repository', + alias: UMB_PROPERTY_EDITOR_DATA_SOURCE_COLLECTION_REPOSITORY_ALIAS, + name: 'Property Editor Data Source Collection Repository', + api: () => import('./collection.repository.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/data/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/data/types.ts new file mode 100644 index 0000000000..d77deee224 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/data/types.ts @@ -0,0 +1,20 @@ +import type { + UmbCollectionDataSource, + UmbCollectionFilterModel, + UmbCollectionItemModel, +} from '@umbraco-cms/backoffice/collection'; + +export type UmbLanguageCollectionDataSource = UmbCollectionDataSource< + UmbPropertyEditorDataSourceCollectionItemModel, + UmbPropertyEditorDataSourceCollectionFilterModel +>; + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface UmbPropertyEditorDataSourceCollectionItemModel extends UmbCollectionItemModel { + unique: string; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface UmbPropertyEditorDataSourceCollectionFilterModel extends UmbCollectionFilterModel { + dataSourceTypes?: Array; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/index.ts new file mode 100644 index 0000000000..1894eb8a5a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/index.ts @@ -0,0 +1 @@ +export * from './data/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/manifests.ts new file mode 100644 index 0000000000..b336d2e822 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/manifests.ts @@ -0,0 +1,4 @@ +import { manifests as dataManifests } from './data/manifests.js'; +import { manifests as menuManifests } from './menu/manifests.js'; + +export const manifests: Array = [...dataManifests, ...menuManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/menu/constants.ts new file mode 100644 index 0000000000..46ea9699d0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/menu/constants.ts @@ -0,0 +1 @@ +export const UMB_PROPERTY_EDITOR_DATA_SOURCE_COLLECTION_MENU_ALIAS = 'Umb.CollectionMenu.PropertyEditorDataSource'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/menu/manifests.ts new file mode 100644 index 0000000000..61aadf347c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/menu/manifests.ts @@ -0,0 +1,15 @@ +import { UMB_PROPERTY_EDITOR_DATA_SOURCE_COLLECTION_REPOSITORY_ALIAS } from '../data/constants.js'; +import { UMB_PROPERTY_EDITOR_DATA_SOURCE_COLLECTION_MENU_ALIAS } from './constants.js'; +import type { ManifestCollectionMenu } from '@umbraco-cms/backoffice/collection'; + +export const manifests: Array = [ + { + type: 'collectionMenu', + kind: 'default', + name: 'Property Editor Data Source Collection Menu', + alias: UMB_PROPERTY_EDITOR_DATA_SOURCE_COLLECTION_MENU_ALIAS, + meta: { + collectionRepositoryAlias: UMB_PROPERTY_EDITOR_DATA_SOURCE_COLLECTION_REPOSITORY_ALIAS, + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/types.ts new file mode 100644 index 0000000000..185b39805d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/collection/types.ts @@ -0,0 +1 @@ +export type * from './data/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/constants.ts new file mode 100644 index 0000000000..24c976d964 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/constants.ts @@ -0,0 +1,4 @@ +export * from './collection/constants.js'; +export * from './item/constants.js'; +export * from './entity.js'; +export * from './search/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/entity.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/entity.ts new file mode 100644 index 0000000000..b1a1587ce8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/entity.ts @@ -0,0 +1,3 @@ +export const UMB_PROPERTY_EDITOR_DATA_SOURCE_ENTITY_TYPE = 'property-editor-data-source'; + +export type UmbPropertyEditorDataSourceEntityType = typeof UMB_PROPERTY_EDITOR_DATA_SOURCE_ENTITY_TYPE; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/extension/property-editor-data-source.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/extension/property-editor-data-source.extension.ts new file mode 100644 index 0000000000..27b2c7aedb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/extension/property-editor-data-source.extension.ts @@ -0,0 +1,22 @@ +import type { PropertyEditorSettings } from '../../property-editor/extensions/types.js'; +import type { ManifestApi } from '@umbraco-cms/backoffice/extension-api'; + +// TODO: base ManifestApiType on dataSourceType +export interface ManifestPropertyEditorDataSource extends ManifestApi { + type: 'propertyEditorDataSource'; + dataSourceType: string; + meta: MetaPropertyEditorDataSource; +} + +export interface MetaPropertyEditorDataSource { + label: string; + description?: string; + icon?: string; + settings?: PropertyEditorSettings; +} + +declare global { + interface UmbExtensionManifestMap { + UmbPropertyEditorDataSource: ManifestPropertyEditorDataSource; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/extension/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/extension/types.ts new file mode 100644 index 0000000000..220c886b41 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/extension/types.ts @@ -0,0 +1 @@ +export type * from './property-editor-data-source.extension.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/global-components.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/global-components.ts new file mode 100644 index 0000000000..e2c4befc69 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/global-components.ts @@ -0,0 +1,3 @@ +import './input/input-property-editor-data-source.element.js'; + +export { UmbInputPropertyEditorDataSourceElement } from './input/input-property-editor-data-source.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/index.ts new file mode 100644 index 0000000000..60262374d5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/index.ts @@ -0,0 +1,4 @@ +export * from './collection/index.js'; +export * from './constants.js'; +export * from './global-components.js'; +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/input/input-property-editor-data-source.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/input/input-property-editor-data-source.context.ts new file mode 100644 index 0000000000..fd18b160b9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/input/input-property-editor-data-source.context.ts @@ -0,0 +1,67 @@ +import type { UmbPropertyEditorDataSourceItemModel } from '../item/types.js'; +import { UMB_PROPERTY_EDITOR_DATA_SOURCE_ITEM_REPOSITORY_ALIAS } from '../item/constants.js'; +import { UMB_PROPERTY_EDITOR_DATA_SOURCE_COLLECTION_MENU_ALIAS } from '../collection/constants.js'; +import { UMB_PROPERTY_EDITOR_DATA_SOURCE_SEARCH_PROVIDER_ALIAS } from '../search/constants.js'; +import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; +import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { + UMB_COLLECTION_ITEM_PICKER_MODAL_ALIAS, + type UmbCollectionItemPickerModalData, + type UmbCollectionItemPickerModalValue, +} from '@umbraco-cms/backoffice/collection'; + +const modalToken = new UmbModalToken( + UMB_COLLECTION_ITEM_PICKER_MODAL_ALIAS, + { + modal: { + type: 'sidebar', + size: 'small', + }, + }, +); + +export class UmbPropertyEditorDataSourcePickerInputContext extends UmbPickerInputContext< + UmbPropertyEditorDataSourceItemModel, + UmbPropertyEditorDataSourceItemModel, + UmbCollectionItemPickerModalData, + UmbCollectionItemPickerModalValue +> { + constructor(host: UmbControllerHost) { + super(host, UMB_PROPERTY_EDITOR_DATA_SOURCE_ITEM_REPOSITORY_ALIAS, modalToken); + } + + #dataSourceTypes: Array = []; + + setDataSourceTypes(value: Array) { + this.#dataSourceTypes = value; + } + + getDataSourceTypes(): Array { + return this.#dataSourceTypes; + } + + override async openPicker( + pickerData?: Partial>, + ) { + const combinedPickerData: Partial> = { + ...pickerData, + collection: { + menuAlias: UMB_PROPERTY_EDITOR_DATA_SOURCE_COLLECTION_MENU_ALIAS, + filterArgs: { + dataSourceTypes: this.getDataSourceTypes(), + ...pickerData?.collection?.filterArgs, + }, + }, + search: { + providerAlias: UMB_PROPERTY_EDITOR_DATA_SOURCE_SEARCH_PROVIDER_ALIAS, + queryParams: { + dataSourceTypes: this.getDataSourceTypes(), + ...pickerData?.search?.queryParams, + }, + }, + }; + + await super.openPicker(combinedPickerData); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/input/input-property-editor-data-source.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/input/input-property-editor-data-source.element.ts new file mode 100644 index 0000000000..9687e685c6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/input/input-property-editor-data-source.element.ts @@ -0,0 +1,233 @@ +import type { UmbPropertyEditorDataSourceItemModel } from '../item/data/types.js'; + +import { UmbPropertyEditorDataSourcePickerInputContext } from './input-property-editor-data-source.context.js'; +import { css, html, customElement, property, state, repeat, nothing, when } from '@umbraco-cms/backoffice/external/lit'; +import { splitStringToArray } from '@umbraco-cms/backoffice/utils'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; +import { UUIFormControlMixin } from '@umbraco-cms/backoffice/external/uui'; +import type { UmbRepositoryItemsStatus } from '@umbraco-cms/backoffice/repository'; + +@customElement('umb-input-property-editor-data-source') +export class UmbInputPropertyEditorDataSourceElement extends UUIFormControlMixin(UmbLitElement, '') { + #sorter = new UmbSorterController(this, { + getUniqueOfElement: (element) => { + return element.id; + }, + getUniqueOfModel: (modelEntry) => { + return modelEntry; + }, + identifier: 'Umb.SorterIdentifier.InputPropertyEditorDataSource', + itemSelector: 'uui-ref-node', + containerSelector: 'uui-ref-list', + onChange: ({ model }) => { + this.selection = model; + this.dispatchEvent(new UmbChangeEvent()); + }, + }); + + /** + * This is a minimum amount of selected items in this input. + * @type {number} + * @attr + * @default + */ + @property({ type: Number }) + public set min(value: number) { + this.#pickerInputContext.min = value; + } + public get min(): number { + return this.#pickerInputContext.min; + } + + /** + * 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 + */ + @property({ type: Number }) + public set max(value: number) { + this.#pickerInputContext.max = value; + } + public get max(): number { + return this.#pickerInputContext.max; + } + + /** + * Max validation message. + * @type {boolean} + * @attr + * @default + */ + @property({ type: String, attribute: 'max-message' }) + maxMessage = 'This field exceeds the allowed amount of items'; + + @property({ type: Array }) + public set selection(uniques: Array) { + this.#pickerInputContext.setSelection(uniques); + this.#sorter.setModel(uniques); + } + public get selection(): Array { + return this.#pickerInputContext.getSelection(); + } + + @property() + public override set value(uniques: string) { + this.selection = splitStringToArray(uniques); + } + public override get value(): string { + return this.selection.join(','); + } + + /** + * Sets the input to readonly mode, meaning value cannot be changed but still able to read and select its content. + * @type {boolean} + * @attr + * @default + */ + @property({ type: Boolean, reflect: true }) + public get readonly() { + return this.#readonly; + } + public set readonly(value) { + this.#readonly = value; + + if (this.#readonly) { + this.#sorter.disable(); + } else { + this.#sorter.enable(); + } + } + #readonly = false; + + @property({ type: Array, attribute: 'data-source-types' }) + public set dataSourceTypes(value: Array) { + this.#pickerInputContext.setDataSourceTypes(value); + } + public get dataSourceTypes(): Array { + return this.#pickerInputContext.getDataSourceTypes(); + } + + @state() + private _items: Array = []; + + @state() + private _statuses?: Array; + + #pickerInputContext = new UmbPropertyEditorDataSourcePickerInputContext(this); + + constructor() { + super(); + + this.addValidator( + 'rangeUnderflow', + () => this.minMessage, + () => !!this.min && this.#pickerInputContext.getSelection().length < this.min, + ); + + this.addValidator( + 'rangeOverflow', + () => this.maxMessage, + () => !!this.max && this.#pickerInputContext.getSelection().length > this.max, + ); + + this.observe( + this.#pickerInputContext.selection, + (selection) => (this.value = selection.join(',')), + '_observeSelection', + ); + this.observe( + this.#pickerInputContext.selectedItems, + (selectedItems) => (this._items = selectedItems), + '_observerItems', + ); + + this.observe(this.#pickerInputContext.statuses, (statuses) => (this._statuses = statuses), '_observerStatuses'); + } + + protected override getFormElement() { + return undefined; + } + + #openPicker() { + this.#pickerInputContext.openPicker(); + } + + #onRemove(unique: string) { + this.#pickerInputContext.requestRemoveItem(unique); + } + + override render() { + return html`${this.#renderItems()} ${this.#renderAddButton()}`; + } + + #renderAddButton() { + if (this.max > 0 && this.selection.length >= this.max) return nothing; + return html` + + `; + } + + #renderItems() { + if (!this._statuses) return; + return html` + + ${repeat( + this._statuses, + (status) => status.unique, + (status) => { + const unique = status.unique; + const item = this._items?.find((x) => x.unique === unique); + return html` + ${when( + !this.readonly, + () => html` + + this.#onRemove(unique)}> + + `, + )} + `; + }, + )} + + `; + } + + static override styles = [ + css` + #btn-add { + width: 100%; + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-input-property-editor-data-source': UmbInputPropertyEditorDataSourceElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/constants.ts new file mode 100644 index 0000000000..1d1e114de8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/constants.ts @@ -0,0 +1 @@ +export * from './data/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/constants.ts new file mode 100644 index 0000000000..6ea1a1a23e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/constants.ts @@ -0,0 +1,3 @@ +export const UMB_PROPERTY_EDITOR_DATA_SOURCE_ITEM_REPOSITORY_ALIAS = 'Umb.Repository.PropertyEditorDataSourceItem'; +export const UMB_PROPERTY_EDITOR_DATA_SOURCE_ITEM_STORE_ALIAS = 'Umb.Store.PropertyEditorDataSourceItem'; +export { UMB_PROPERTY_EDITOR_DATA_SOURCE_ITEM_STORE_CONTEXT } from './item.store.context-token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/index.ts new file mode 100644 index 0000000000..fb21e00f3e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/index.ts @@ -0,0 +1 @@ +export { UmbPropertyEditorDataSourceItemRepository } from './item.repository.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/item.extension-registry.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/item.extension-registry.data-source.ts new file mode 100644 index 0000000000..f0cf06c1d8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/item.extension-registry.data-source.ts @@ -0,0 +1,31 @@ +import { UMB_PROPERTY_EDITOR_DATA_SOURCE_ENTITY_TYPE } from '../../entity.js'; +import type { UmbPropertyEditorDataSourceItemModel } from './types.js'; +import type { UmbItemDataSource } from '@umbraco-cms/backoffice/repository'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; + +/** + * A server data source for Property Editor Data Source items + * @class UmbPropertyEditorDataSourceItemExtensionRegistryDataSource + * @implements {UmbItemDataSource} + */ +export class UmbPropertyEditorDataSourceItemExtensionRegistryDataSource + implements UmbItemDataSource +{ + async getItems(uniques: Array) { + if (!uniques) throw new Error('Uniques are missing'); + + const extensions = umbExtensionsRegistry.getByType('propertyEditorDataSource'); + + const items: Array = extensions + .filter((manifest) => uniques.includes(manifest.alias)) + .map((manifest) => ({ + entityType: UMB_PROPERTY_EDITOR_DATA_SOURCE_ENTITY_TYPE, + unique: manifest.alias, + name: manifest.meta.label ?? manifest.name, + icon: manifest.meta.icon ?? 'icon-database', + description: manifest.meta?.description ?? '', + })); + + return { data: items }; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/item.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/item.repository.ts new file mode 100644 index 0000000000..3049951d30 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/item.repository.ts @@ -0,0 +1,17 @@ +import { UmbPropertyEditorDataSourceItemExtensionRegistryDataSource } from './item.extension-registry.data-source.js'; +import { UMB_PROPERTY_EDITOR_DATA_SOURCE_ITEM_STORE_CONTEXT } from './item.store.context-token.js'; +import type { UmbPropertyEditorDataSourceItemModel } from './types.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbItemRepositoryBase } from '@umbraco-cms/backoffice/repository'; + +export class UmbPropertyEditorDataSourceItemRepository extends UmbItemRepositoryBase { + constructor(host: UmbControllerHost) { + super( + host, + UmbPropertyEditorDataSourceItemExtensionRegistryDataSource, + UMB_PROPERTY_EDITOR_DATA_SOURCE_ITEM_STORE_CONTEXT, + ); + } +} + +export { UmbPropertyEditorDataSourceItemRepository as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/item.store.context-token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/item.store.context-token.ts new file mode 100644 index 0000000000..3b78654005 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/item.store.context-token.ts @@ -0,0 +1,5 @@ +import type { UmbPropertyEditorDataSourceItemStore } from './item.store.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const UMB_PROPERTY_EDITOR_DATA_SOURCE_ITEM_STORE_CONTEXT = + new UmbContextToken('UmbPropertyEditorDataSourceItemStore'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/item.store.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/item.store.ts new file mode 100644 index 0000000000..350e454a06 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/item.store.ts @@ -0,0 +1,23 @@ +import { UMB_PROPERTY_EDITOR_DATA_SOURCE_ITEM_STORE_CONTEXT } from './item.store.context-token.js'; +import type { UmbPropertyEditorDataSourceItemModel } from './types.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbItemStoreBase } from '@umbraco-cms/backoffice/store'; + +/** + * @class UmbPropertyEditorDataSourceItemStore + * @augments {UmbStoreBase} + * @description - Data Store for Property Editor Data Source items + */ + +export class UmbPropertyEditorDataSourceItemStore extends UmbItemStoreBase { + /** + * Creates an instance of UmbPropertyEditorDataSourceItemStore. + * @param {UmbControllerHost} host - The controller host for this controller to be appended to + * @memberof UmbPropertyEditorDataSourceItemStore + */ + constructor(host: UmbControllerHost) { + super(host, UMB_PROPERTY_EDITOR_DATA_SOURCE_ITEM_STORE_CONTEXT.toString()); + } +} + +export { UmbPropertyEditorDataSourceItemStore as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/manifests.ts new file mode 100644 index 0000000000..0f615bef2c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/manifests.ts @@ -0,0 +1,19 @@ +import { + UMB_PROPERTY_EDITOR_DATA_SOURCE_ITEM_REPOSITORY_ALIAS, + UMB_PROPERTY_EDITOR_DATA_SOURCE_ITEM_STORE_ALIAS, +} from './constants.js'; + +export const manifests: Array = [ + { + type: 'repository', + alias: UMB_PROPERTY_EDITOR_DATA_SOURCE_ITEM_REPOSITORY_ALIAS, + name: 'Property Editor Data Source Item Repository', + api: () => import('./item.repository.js'), + }, + { + type: 'itemStore', + alias: UMB_PROPERTY_EDITOR_DATA_SOURCE_ITEM_STORE_ALIAS, + name: 'Property Editor Data Source Item Store', + api: () => import('./item.store.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/types.ts new file mode 100644 index 0000000000..806e86633a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/data/types.ts @@ -0,0 +1,5 @@ +import type { UmbItemModel } from '@umbraco-cms/backoffice/entity-item'; +export interface UmbPropertyEditorDataSourceItemModel extends UmbItemModel { + description?: string; + name: string; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/manifests.ts new file mode 100644 index 0000000000..4007d5d37f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/manifests.ts @@ -0,0 +1,4 @@ +import { manifests as dataManifests } from './data/manifests.js'; +import { manifests as refManifests } from './ref/manifests.js'; + +export const manifests = [...dataManifests, ...refManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/ref/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/ref/manifests.ts new file mode 100644 index 0000000000..7cfbd4bf16 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/ref/manifests.ts @@ -0,0 +1,11 @@ +import { UMB_PROPERTY_EDITOR_DATA_SOURCE_ENTITY_TYPE } from '../../entity.js'; + +export const manifests: Array = [ + { + type: 'entityItemRef', + alias: 'Umb.EntityItemRef.PropertyEditorDataSource', + name: 'Property Editor Data Source Item Reference', + element: () => import('./property-editor-data-source-item-ref.element.js'), + forEntityTypes: [UMB_PROPERTY_EDITOR_DATA_SOURCE_ENTITY_TYPE], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/ref/property-editor-data-source-item-ref.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/ref/property-editor-data-source-item-ref.element.ts new file mode 100644 index 0000000000..a2874cbd5a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/ref/property-editor-data-source-item-ref.element.ts @@ -0,0 +1,53 @@ +import type { UmbPropertyEditorDataSourceItemModel } from '../types.js'; +import { css, customElement, html, ifDefined, nothing, property } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; + +@customElement('umb-property-editor-data-source-item-ref') +export class UmbPropertyEditorDataSourceItemRefElement extends UmbLitElement { + #item?: UmbPropertyEditorDataSourceItemModel | undefined; + + @property({ type: Object }) + public get item(): UmbPropertyEditorDataSourceItemModel | undefined { + return this.#item; + } + public set item(value: UmbPropertyEditorDataSourceItemModel | undefined) { + this.#item = value; + } + + @property({ type: Boolean }) + readonly = false; + + @property({ type: Boolean }) + standalone = false; + + override render() { + if (!this.item) return nothing; + + return html` + + + + + `; + } + + static override styles = [ + css` + umb-user-avatar { + font-size: var(--uui-size-4); + } + `, + ]; +} + +export { UmbPropertyEditorDataSourceItemRefElement as element }; + +declare global { + interface HTMLElementTagNameMap { + 'umb-property-editor-data-source-item-ref': UmbPropertyEditorDataSourceItemRefElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/types.ts new file mode 100644 index 0000000000..185b39805d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/item/types.ts @@ -0,0 +1 @@ +export type * from './data/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/manifests.ts new file mode 100644 index 0000000000..c76a1b7eff --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/manifests.ts @@ -0,0 +1,5 @@ +import { manifests as collectionManifests } from './collection/manifests.js'; +import { manifests as itemManifests } from './item/manifests.js'; +import { manifests as searchManifests } from './search/manifests.js'; + +export const manifests: Array = [...collectionManifests, ...itemManifests, ...searchManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/constants.ts new file mode 100644 index 0000000000..aef1a96e17 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/constants.ts @@ -0,0 +1 @@ +export const UMB_PROPERTY_EDITOR_DATA_SOURCE_SEARCH_PROVIDER_ALIAS = 'Umb.SearchProvider.PropertyEditorDataSource'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/index.ts new file mode 100644 index 0000000000..4f07201dcf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/index.ts @@ -0,0 +1 @@ +export * from './constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/manifests.ts new file mode 100644 index 0000000000..4d324f2a22 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/manifests.ts @@ -0,0 +1,20 @@ +import { UMB_PROPERTY_EDITOR_DATA_SOURCE_ENTITY_TYPE } from '../entity.js'; +import { UMB_PROPERTY_EDITOR_DATA_SOURCE_SEARCH_PROVIDER_ALIAS } from './constants.js'; + +export const manifests: Array = [ + { + type: 'searchProvider', + alias: UMB_PROPERTY_EDITOR_DATA_SOURCE_SEARCH_PROVIDER_ALIAS, + name: 'Property Editor Data Source Search Provider', + api: () => import('./search-provider.js'), + weight: 600, + }, + + { + type: 'pickerSearchResultItem', + kind: 'default', + alias: 'Umb.PickerSearchResultItem.PropertyEditorDataSource', + name: 'Property Editor Data Source Picker Search Result Item', + forEntityTypes: [UMB_PROPERTY_EDITOR_DATA_SOURCE_ENTITY_TYPE], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/search-provider.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/search-provider.ts new file mode 100644 index 0000000000..8df415d236 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/search-provider.ts @@ -0,0 +1,21 @@ +import type { UmbPropertyEditorDataSourceItemModel } from '../item/types.js'; +import { UmbPropertyEditorDataSourceSearchRepository } from './search.repository.js'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbSearchProvider, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; + +export class UmbPropertyEditorDataSourceSearchProvider + extends UmbControllerBase + implements UmbSearchProvider +{ + #repository = new UmbPropertyEditorDataSourceSearchRepository(this); + + async search(args: UmbSearchRequestArgs) { + return this.#repository.search(args); + } + + override destroy(): void { + this.#repository.destroy(); + } +} + +export { UmbPropertyEditorDataSourceSearchProvider as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/search.extension-registry.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/search.extension-registry.data-source.ts new file mode 100644 index 0000000000..b97cbccaa4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/search.extension-registry.data-source.ts @@ -0,0 +1,53 @@ +import type { UmbPropertyEditorDataSourceItemModel } from '../item/types.js'; +import { UMB_PROPERTY_EDITOR_DATA_SOURCE_ENTITY_TYPE } from '../entity.js'; +import type { UmbPropertyEditorDataSourceSearchRequestArgs } from './types.js'; +import type { UmbSearchDataSource } from '@umbraco-cms/backoffice/search'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; + +/** + * A data source for the Property Editor Data Source search, that uses the Extension Registry to find available data sources. + * @class UmbPropertyEditorDataSourceSearchExtensionRegistryDataSource + * @augments {UmbControllerBase} + * @implements {UmbSearchDataSource} + */ +export class UmbPropertyEditorDataSourceSearchExtensionRegistryDataSource + extends UmbControllerBase + implements UmbSearchDataSource +{ + async search(args: UmbPropertyEditorDataSourceSearchRequestArgs) { + const extensions = umbExtensionsRegistry.getByType('propertyEditorDataSource'); + + const extensionsWithAllowedDataSourceTypes = extensions.filter((ext) => { + if (args.dataSourceTypes && args.dataSourceTypes.length > 0) { + return args.dataSourceTypes.includes(ext.dataSourceType); + } + return true; + }); + + const lowerCaseQuery = args.query.toLowerCase(); + + // Simple filter by name or alias + const filteredExtensions = extensionsWithAllowedDataSourceTypes.filter( + (item) => + item.meta.label.toLowerCase().includes(lowerCaseQuery) || + item.name.toLowerCase().includes(lowerCaseQuery) || + item.alias.toLowerCase().includes(lowerCaseQuery), + ); + + const items: UmbPropertyEditorDataSourceItemModel[] = filteredExtensions.map((extension) => ({ + entityType: UMB_PROPERTY_EDITOR_DATA_SOURCE_ENTITY_TYPE, + unique: extension.alias, + name: extension.meta.label, + icon: extension.meta?.icon ?? 'icon-database', + description: extension.meta?.description ?? '', + })); + + return { + data: { + items: items, + total: items.length, + }, + }; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/search.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/search.repository.ts new file mode 100644 index 0000000000..9a8e94cdf7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/search.repository.ts @@ -0,0 +1,23 @@ +import type { UmbPropertyEditorDataSourceItemModel } from '../item/types.js'; +import { UmbPropertyEditorDataSourceSearchExtensionRegistryDataSource } from './search.extension-registry.data-source.js'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbSearchRepository, UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; + +export class UmbPropertyEditorDataSourceSearchRepository + extends UmbControllerBase + implements UmbSearchRepository, UmbApi +{ + #dataSource: UmbPropertyEditorDataSourceSearchExtensionRegistryDataSource; + + constructor(host: UmbControllerHost) { + super(host); + + this.#dataSource = new UmbPropertyEditorDataSourceSearchExtensionRegistryDataSource(this); + } + + search(args: UmbSearchRequestArgs) { + return this.#dataSource.search(args); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/types.ts new file mode 100644 index 0000000000..2a7db547d2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/search/types.ts @@ -0,0 +1,5 @@ +import type { UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; + +export interface UmbPropertyEditorDataSourceSearchRequestArgs extends UmbSearchRequestArgs { + dataSourceTypes?: Array; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/types.ts new file mode 100644 index 0000000000..90c1bd6808 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor-data-source/types.ts @@ -0,0 +1 @@ +export type * from './extension/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/extensions/property-editor-ui-element.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/extensions/property-editor-ui-element.interface.ts index 836b06bfd3..6b25c6cc0b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/extensions/property-editor-ui-element.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/extensions/property-editor-ui-element.interface.ts @@ -5,6 +5,7 @@ export interface UmbPropertyEditorUiElement extends HTMLElement { manifest?: ManifestPropertyEditorUi; name?: string; value?: unknown; + dataSourceAlias?: string; config?: UmbPropertyEditorConfigCollection; readonly?: boolean; mandatory?: boolean; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/extensions/property-editor.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/extensions/property-editor.extension.ts index 708b590b56..31ff43ed7b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/extensions/property-editor.extension.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/extensions/property-editor.extension.ts @@ -25,6 +25,19 @@ export interface MetaPropertyEditorUi { propertyEditorSchemaAlias?: string; settings?: PropertyEditorSettings; supportsReadOnly?: boolean; + supportsDataSource?: { + /** + * Whether the property editor UI is enabled for use with data sources. + * @type {boolean} + */ + enabled: boolean; + /** + * A list of allowed property editor data source kinds that can be used with this property editor UI. + * If not specified, any data sources can be used. + * @example ["pickerCollection", "pickerTree"] + */ + forDataSourceTypes: string[]; + }; } // Model @@ -53,6 +66,7 @@ export interface PropertyEditorSettingsProperty { description?: string; alias: string; propertyEditorUiAlias: string; + propertyEditorDataSourceAlias?: string; config?: UmbPropertyEditorConfig; weight?: number; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/components/property/property.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/components/property/property.context.ts index 0fe89b19f7..424c5196cc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/components/property/property.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/components/property/property.context.ts @@ -60,6 +60,9 @@ export class UmbPropertyContext extends UmbContextBase { #editorManifest = new UmbBasicState(undefined); public readonly editorManifest = this.#editorManifest.asObservable(); + #editorDataSourceAlias = new UmbStringState(undefined); + public readonly editorDataSourceAlias = this.#editorDataSourceAlias.asObservable(); + public readonly readOnlyState = new UmbReadOnlyStateManager(this); public readonly isReadOnly = this.readOnlyState.isReadOnly; @@ -95,6 +98,22 @@ export class UmbPropertyContext extends UmbContextBase { return this.#editorManifest.getValue(); } + /** + * Set the editor data source alias for this property. + * @param {string | undefined} dataSourceAlias The data source alias to set + */ + setEditorDataSourceAlias(dataSourceAlias: string | undefined) { + this.#editorDataSourceAlias.setValue(dataSourceAlias ?? undefined); + } + + /** + * Get the editor data source alias for this property. + * @returns {string | undefined} The editor data source alias for this property. + */ + getEditorDataSourceAlias(): string | undefined { + return this.#editorDataSourceAlias.getValue(); + } + // property variant ID: #variantId = new UmbClassState(undefined); public readonly variantId = this.#variantId.asObservable(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/components/property/property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/components/property/property.element.ts index c46ecc5059..4684e09c9c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/components/property/property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/components/property/property.element.ts @@ -99,6 +99,18 @@ export class UmbPropertyElement extends UmbLitElement { } private _propertyEditorUiAlias?: string; + @property({ type: String, attribute: 'property-editor-data-source-alias' }) + public set propertyEditorDataSourceAlias(value: string | undefined) { + this.#propertyContext.setEditorDataSourceAlias(value); + + if (this._element) { + this._element.dataSourceAlias = value; + } + } + public get propertyEditorDataSourceAlias(): string | undefined { + return this.#propertyContext.getEditorDataSourceAlias(); + } + /** * Config. Configuration to pass to the Property Editor UI. This is also the configuration data stored on the Data Type. * @public @@ -344,6 +356,7 @@ export class UmbPropertyElement extends UmbLitElement { this._element.manifest = manifest; this._element.mandatory = this._mandatory; this._element.name = this._label; + this._element.dataSourceAlias = this.#propertyContext.getEditorDataSourceAlias(); // No need for a controller alias, as the clean is handled via the observer prop: this.#valueObserver = this.observe( diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repository/detail/read/read-detail-repository.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repository/detail/read/read-detail-repository.interface.ts index a19f530b19..a20af58788 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/repository/detail/read/read-detail-repository.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/repository/detail/read/read-detail-repository.interface.ts @@ -4,5 +4,5 @@ import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; export interface UmbReadDetailRepository extends UmbApi { requestByUnique(unique: string): Promise>; - byUnique(unique: string): Promise>; + byUnique?(unique: string): Promise>; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-repository.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-repository.interface.ts index 71e25909a2..58d8590939 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-repository.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/repository/item/item-repository.interface.ts @@ -4,5 +4,5 @@ import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; export interface UmbItemRepository extends UmbApi { requestItems: (uniques: string[]) => Promise>; - items: (uniques: string[]) => Promise> | undefined>; + items?: (uniques: string[]) => Promise> | undefined>; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-items.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-items.manager.ts index f2352d1407..445e37955c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-items.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-items.manager.ts @@ -15,6 +15,8 @@ export class UmbRepositoryItemsManager exte repository?: UmbItemRepository; #init: Promise; + #initResolve!: (value: unknown) => void; + #currentRequest?: Promise; #eventContext?: typeof UMB_ACTION_EVENT_CONTEXT.TYPE; @@ -39,18 +41,16 @@ export class UmbRepositoryItemsManager exte * @param {string} repositoryAlias - The alias of the repository to use. * @memberof UmbRepositoryItemsManager */ - constructor(host: UmbControllerHost, repositoryAlias: string) { + constructor(host: UmbControllerHost, repositoryAlias?: string) { super(host); - this.#init = new UmbExtensionApiInitializer>>( - this, - umbExtensionsRegistry, - repositoryAlias, - [this], - (permitted, repository) => { - this.repository = permitted ? repository.api : undefined; - }, - ).asPromise(); + this.#init = new Promise((resolve) => { + this.#initResolve = resolve; + }); + + if (repositoryAlias) { + this.#initItemRepository(repositoryAlias); + } this.observe( this.uniques, @@ -90,6 +90,25 @@ export class UmbRepositoryItemsManager exte }); } + /** + * Sets the item repository to use for this manager. + * @param {(UmbItemRepository | undefined)} itemRepository - The item repository to set. + * @memberof UmbRepositoryItemsManager + */ + setItemRepository(itemRepository: UmbItemRepository | undefined) { + this.repository = itemRepository; + this.#initResolve(undefined); + } + + /** + * Gets the item repository used by this manager. + * @returns {(UmbItemRepository | undefined)} The item repository. + * @memberof UmbRepositoryItemsManager + */ + getItemRepository(): UmbItemRepository | undefined { + return this.repository; + } + getUniques(): Array { return this.#uniques.getValue(); } @@ -196,6 +215,8 @@ export class UmbRepositoryItemsManager exte }, ObserveRepositoryAlias, ); + } else if (data) { + this.#items.setValue(data); } } @@ -251,6 +272,19 @@ export class UmbRepositoryItemsManager exte this.#reloadItem(item.unique); }; + async #initItemRepository(itemRepositoryAlias: string) { + new UmbExtensionApiInitializer>>( + this, + umbExtensionsRegistry, + itemRepositoryAlias, + [this], + (permitted, repository) => { + this.repository = permitted ? repository.api : undefined; + this.#initResolve(undefined); + }, + ); + } + override destroy(): void { this.#eventContext?.removeEventListener( UmbEntityUpdatedEvent.TYPE, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repository/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repository/types.ts index 83bafd3c7d..8643aa5894 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/repository/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/repository/types.ts @@ -31,7 +31,7 @@ export interface UmbRepositoryItemsStatus { * @template T$ - The type of items returned by the asObservable method, defaults to T. You should only use this if you want to return a different type from the asObservable method. */ export interface UmbRepositoryResponseWithAsObservable extends UmbRepositoryResponse { - asObservable: () => Observable | undefined; + asObservable?: () => Observable | undefined; } export type * from './data-mapper/mapping/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/constants.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/constants.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/constants.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/constants.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/constants.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/constants.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/dashboard-examine-management.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/dashboard-examine-management.element.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/dashboard-examine-management.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/dashboard-examine-management.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/manifests.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/manifests.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/manifests.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/modal/constants.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/constants.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/modal/constants.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-settings/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/modal/fields-settings/constants.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-settings/constants.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/modal/fields-settings/constants.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-settings/examine-fields-settings-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/modal/fields-settings/examine-fields-settings-modal.element.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-settings/examine-fields-settings-modal.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/modal/fields-settings/examine-fields-settings-modal.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-settings/examine-fields-settings-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/modal/fields-settings/examine-fields-settings-modal.token.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-settings/examine-fields-settings-modal.token.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/modal/fields-settings/examine-fields-settings-modal.token.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-viewer/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/modal/fields-viewer/constants.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-viewer/constants.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/modal/fields-viewer/constants.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-viewer/examine-fields-viewer-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/modal/fields-viewer/examine-fields-viewer-modal.element.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-viewer/examine-fields-viewer-modal.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/modal/fields-viewer/examine-fields-viewer-modal.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-viewer/examine-fields-viewer-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/modal/fields-viewer/examine-fields-viewer-modal.token.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-viewer/examine-fields-viewer-modal.token.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/modal/fields-viewer/examine-fields-viewer-modal.token.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/modal/manifests.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/manifests.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/modal/manifests.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/section-view-examine-indexers.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/views/section-view-examine-indexers.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/section-view-examine-indexers.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/views/section-view-examine-indexers.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/section-view-examine-overview.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/views/section-view-examine-overview.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/section-view-examine-overview.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/views/section-view-examine-overview.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/section-view-examine-searchers.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/views/section-view-examine-searchers.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/section-view-examine-searchers.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/examine-management-dashboard/views/section-view-examine-searchers.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/extensions/search-provider.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/extensions/search-provider.extension.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/extensions/search-provider.extension.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/extensions/search-provider.extension.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/extensions/search-result-item.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/extensions/search-result-item.extension.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/extensions/search-result-item.extension.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/extensions/search-result-item.extension.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/extensions/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/extensions/types.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/extensions/types.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/extensions/types.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/global-search/global-search-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/global-search/global-search-base.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/global-search/global-search-base.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/global-search/global-search-base.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/global-search/global-search.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/global-search/global-search.extension.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/global-search/global-search.extension.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/global-search/global-search.extension.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/global-search/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/global-search/index.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/global-search/index.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/global-search/index.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/global-search/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/global-search/types.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/global-search/types.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/global-search/types.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/index.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/index.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/index.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/manifests.ts similarity index 86% rename from src/Umbraco.Web.UI.Client/src/packages/search/manifests.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/manifests.ts index bcdee40ae3..c5fd2f6a8b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/search/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/search/manifests.ts @@ -1,6 +1,5 @@ import { manifests as examineManifests } from './examine-management-dashboard/manifests.js'; import { UMB_SECTION_ALIAS_CONDITION_ALIAS } from '@umbraco-cms/backoffice/section'; -import { UMB_SETTINGS_SECTION_ALIAS } from '@umbraco-cms/backoffice/settings'; export const manifests: Array = [ { @@ -34,7 +33,10 @@ export const manifests: Array = [ conditions: [ { alias: UMB_SECTION_ALIAS_CONDITION_ALIAS, - match: UMB_SETTINGS_SECTION_ALIAS, + /* TODO: kept as a magic string to avoid illegal import outside of core + Move dashboard implementation out of core + */ + match: 'Umb.Section.Settings', }, ], }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/search-data-source.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/search-data-source.interface.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/search-data-source.interface.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/search-data-source.interface.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/search-modal/search-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/search-modal/search-modal.element.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/search-modal/search-modal.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/search-modal/search-modal.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/search-modal/search-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/search-modal/search-modal.token.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/search-modal/search-modal.token.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/search-modal/search-modal.token.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/search-repository.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/search-repository.interface.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/search-repository.interface.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/search-repository.interface.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/search-result/search-result-item.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/search-result/search-result-item.element.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/search-result/search-result-item.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/search-result/search-result-item.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/types.ts similarity index 85% rename from src/Umbraco.Web.UI.Client/src/packages/search/types.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/types.ts index 664ca34f3a..7792da53cf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/search/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/search/types.ts @@ -1,4 +1,5 @@ import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; +import type { UmbItemModel } from '@umbraco-cms/backoffice/entity-item'; import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; import type { FieldPresentationModel, SearchResultResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; import type { UmbPagedModel, UmbRepositoryResponse } from '@umbraco-cms/backoffice/repository'; @@ -11,14 +12,9 @@ export type * from './global-search/types.js'; export type UmbSearchResultModel = SearchResultResponseModel; -// TODO: lower requirement for search provider item type -export type UmbSearchResultItemModel = { - entityType: string; - icon?: string | null; - name: string; - unique: string; - href: string; -}; +export interface UmbSearchResultItemModel extends UmbItemModel { + href?: string; +} export type UmbSearchRequestArgs = { query: string; diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/umb-search-header-app.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/search/umb-search-header-app.element.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/search/umb-search-header-app.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/search/umb-search-header-app.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/components/input-section/input-section.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/components/input-section/input-section.context.ts index adc42d14d7..f74644a0ab 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/components/input-section/input-section.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/components/input-section/input-section.context.ts @@ -4,7 +4,7 @@ import { UMB_SECTION_ITEM_REPOSITORY_ALIAS } from '../../constants.js'; import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -export class UmbSectionPickerInputContext extends UmbPickerInputContext { +export class UmbSectionPickerInputContext extends UmbPickerInputContext { constructor(host: UmbControllerHost) { super(host, UMB_SECTION_ITEM_REPOSITORY_ALIAS, UMB_SECTION_PICKER_MODAL); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/repository/item/section-item.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/repository/item/section-item.repository.ts index 97772fc132..c76caf3d3d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/repository/item/section-item.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/repository/item/section-item.repository.ts @@ -51,6 +51,8 @@ const sectionItemsByUniquesObservable = (uniques: Array) => const itemMapper = (manifest: ManifestSection): UmbSectionItemModel => { return { ...manifest, + // TODO: introduce const + entityType: 'section', unique: manifest.alias, }; }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/repository/item/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/repository/item/types.ts index 358477d5a3..a4162b8c36 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/repository/item/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/repository/item/types.ts @@ -1,5 +1,7 @@ import type { ManifestSection } from '../../extensions/index.js'; +import type { UmbItemModel } from '@umbraco-cms/backoffice/entity-item'; -export interface UmbSectionItemModel extends ManifestSection { - unique: string; +// TODO: remove extension of ManifestSection +export interface UmbSectionItemModel extends UmbItemModel, ManifestSection { + name: string; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/data/tree-repository.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/data/tree-repository.interface.ts index f2aa523e7e..d110f1c5ce 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/data/tree-repository.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/data/tree-repository.interface.ts @@ -62,7 +62,7 @@ export interface UmbTreeRepository< * @memberof UmbTreeRepository * @deprecated Use `requestTreeRootItems` instead. It will be removed in Umbraco 18. */ - rootTreeItems: () => Promise>; + rootTreeItems?: () => Promise>; /** * Returns an observable of the children of the given parent item. @@ -70,5 +70,5 @@ export interface UmbTreeRepository< * @memberof UmbTreeRepository * @deprecated Use `requestTreeItemsOf` instead. It will be removed in Umbraco 18. */ - treeItemsOf: (parentUnique: string | null) => Promise>; + treeItemsOf?: (parentUnique: string | null) => Promise>; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item.element.ts index bd85ac9068..d1714bdb2b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item.element.ts @@ -1,4 +1,5 @@ import type { ManifestTreeItem } from '../extensions/types.js'; +import UmbDefaultTreeItemContext from './tree-item-default/tree-item-default.context.js'; import { customElement, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbExtensionElementAndApiSlotElementBase, @@ -18,6 +19,15 @@ export class UmbTreeItemElement extends UmbExtensionElementAndApiSlotElementBase } #entityType?: string; + @property({ type: Object, attribute: false }) + override set props(newVal: Record | undefined) { + super.props = newVal; + this.#assignProps(); + } + override get props() { + return super.props; + } + #observeEntityType() { if (!this.#entityType) return; @@ -26,6 +36,8 @@ export class UmbTreeItemElement extends UmbExtensionElementAndApiSlotElementBase return manifest.forEntityTypes.includes(this.#entityType); }; + // Check if we can find a matching tree item for the current entity type. + // If we can, we will use that one, if not we will render a fallback tree item. this.observe( // TODO: what should we do if there are multiple tree items for an entity type? // This method gets all extensions based on a type, then filters them based on the entity type. and then we get the alias of the first one [NL] @@ -35,11 +47,27 @@ export class UmbTreeItemElement extends UmbExtensionElementAndApiSlotElementBase ), (alias) => { this.alias = alias; + + // If we don't find any registered tree items for this specific entity type, we will render a fallback tree item. + // This is on purpose not done with the extension initializer since we don't want to spin up a real extension unless we have to. + if (!alias) { + this.#renderFallbackItem(); + } }, 'umbObserveAlias', ); } + #renderFallbackItem() { + // TODO: make creating of elements with apis a shared function. + const element = document.createElement('umb-default-tree-item'); + const api = new UmbDefaultTreeItemContext(element); + element.api = api; + this._element = element; + this.#assignProps(); + this.requestUpdate('_element'); + } + getExtensionType() { return 'treeItem'; } @@ -47,6 +75,18 @@ export class UmbTreeItemElement extends UmbExtensionElementAndApiSlotElementBase getDefaultElementName() { return 'umb-default-tree-item'; } + + #assignProps() { + if (!this._element || !this.props) return; + + Object.keys(this.props).forEach((key) => { + (this._element as any)[key] = this.props![key]; + }); + } + + override getDefaultApiConstructor() { + return UmbDefaultTreeItemContext; + } } declare global { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker-modal/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker-modal/index.ts index 7cbd303dac..96c06454e6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker-modal/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker-modal/index.ts @@ -1,3 +1,4 @@ export { UMB_TREE_PICKER_MODAL_ALIAS } from './constants.js'; -export type { UmbTreePickerModalData, UmbTreePickerModalValue } from './tree-picker-modal.token.js'; export { UMB_TREE_PICKER_MODAL } from './tree-picker-modal.token.js'; + +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker-modal/tree-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker-modal/tree-picker-modal.element.ts index fb71dd349d..da5e0e8fc3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker-modal/tree-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker-modal/tree-picker-modal.element.ts @@ -1,7 +1,7 @@ import { UmbTreeItemPickerContext } from '../tree-item-picker/index.js'; import type { UmbTreeElement } from '../tree.element.js'; import type { UmbTreeItemModelBase, UmbTreeSelectionConfiguration } from '../types.js'; -import type { UmbTreePickerModalData, UmbTreePickerModalValue } from './tree-picker-modal.token.js'; +import type { UmbTreePickerModalData, UmbTreePickerModalValue } from './types.js'; import { customElement, html, ifDefined, nothing, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbDeselectedEvent, UmbSelectedEvent } from '@umbraco-cms/backoffice/event'; import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker-modal/tree-picker-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker-modal/tree-picker-modal.token.ts index e6acaf9979..36463e0870 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker-modal/tree-picker-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker-modal/tree-picker-modal.token.ts @@ -1,33 +1,6 @@ -import type { UmbTreeItemModel, UmbTreeStartNode } from '../types.js'; import { UMB_TREE_PICKER_MODAL_ALIAS } from './constants.js'; -import type { UmbPickerModalData, UmbPickerModalValue } from '@umbraco-cms/backoffice/modal'; -import type { UmbWorkspaceModalData } from '@umbraco-cms/backoffice/workspace'; +import type { UmbTreePickerModalData, UmbTreePickerModalValue } from './types.js'; import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; -import type { UmbPathPattern, UmbPathPatternParamsType } from '@umbraco-cms/backoffice/router'; - -export interface UmbTreePickerModalCreateActionData { - label: string; - modalData: UmbWorkspaceModalData; - modalToken?: UmbModalToken; - extendWithPathPattern: UmbPathPattern; - extendWithPathParams: PathPatternParamsType; -} - -export interface UmbTreePickerModalData< - TreeItemType = UmbTreeItemModel, - PathPatternParamsType extends UmbPathPatternParamsType = UmbPathPatternParamsType, -> extends UmbPickerModalData { - hideTreeRoot?: boolean; - expandTreeRoot?: boolean; - treeAlias?: string; - // Consider if it makes sense to move this into the UmbPickerModalData interface, but for now this is a TreePicker feature. [NL] - createAction?: UmbTreePickerModalCreateActionData; - startNode?: UmbTreeStartNode; - foldersOnly?: boolean; -} - -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface UmbTreePickerModalValue extends UmbPickerModalValue {} export const UMB_TREE_PICKER_MODAL = new UmbModalToken( UMB_TREE_PICKER_MODAL_ALIAS, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker-modal/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker-modal/types.ts new file mode 100644 index 0000000000..3703665f6b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker-modal/types.ts @@ -0,0 +1,28 @@ +import type { UmbTreeItemModel, UmbTreeStartNode } from '../types.js'; +import type { UmbPathPattern, UmbPathPatternParamsType } from '@umbraco-cms/backoffice/router'; +import type { UmbModalToken, UmbPickerModalData, UmbPickerModalValue } from '@umbraco-cms/backoffice/modal'; +import type { UmbWorkspaceModalData } from '@umbraco-cms/backoffice/workspace'; + +export interface UmbTreePickerModalCreateActionData { + label: string; + modalData: UmbWorkspaceModalData; + modalToken?: UmbModalToken; + extendWithPathPattern: UmbPathPattern; + extendWithPathParams: PathPatternParamsType; +} + +export interface UmbTreePickerModalData< + TreeItemType = UmbTreeItemModel, + PathPatternParamsType extends UmbPathPatternParamsType = UmbPathPatternParamsType, +> extends UmbPickerModalData { + hideTreeRoot?: boolean; + expandTreeRoot?: boolean; + treeAlias?: string; + // TODO: create action should be replaces by entity actions in the pickers. Then we also open up for creating folders, choosing where to place items etc. [MR] + createAction?: UmbTreePickerModalCreateActionData; + startNode?: UmbTreeStartNode; + foldersOnly?: boolean; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface UmbTreePickerModalValue extends UmbPickerModalValue {} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/config-collection/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/config-collection/index.ts new file mode 100644 index 0000000000..b4f03f1155 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/config-collection/index.ts @@ -0,0 +1,12 @@ +import type { UmbConfigCollectionModel } from './types.js'; + +/** + * Get a value from a config collection by its alias. + * @param {UmbConfigCollectionModel | undefined} config - The config collection to get the value from. + * @param {string} alias - The alias of the value to get. + * @returns {T | undefined} The value with the specified alias, or undefined if not found or if the config is undefined. + */ +export function getConfigValue(config: UmbConfigCollectionModel | undefined, alias: string): T | undefined { + const entry = config?.find((entry) => entry.alias === alias); + return entry?.value as T | undefined; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/config-collection/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/config-collection/types.ts new file mode 100644 index 0000000000..736c411346 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/config-collection/types.ts @@ -0,0 +1,6 @@ +export interface UmbConfigCollectionEntryModel { + alias: string; + value: unknown; +} + +export type UmbConfigCollectionModel = Array; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts index 33deb40984..12d0240363 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts @@ -1,5 +1,6 @@ export * from './array/index.js'; export * from './bytes/bytes.function.js'; +export * from './config-collection/index.js'; export * from './date/index.js'; export * from './debounce/debounce.function.js'; export * from './deprecation/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/type/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/type/index.ts index 613d220897..8e33fb6a58 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/utils/type/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/type/index.ts @@ -1,3 +1,4 @@ export type * from './deep-partial-object.type.js'; export type * from './diff.type.js'; export type * from './partial-some.type.js'; +export type * from '../config-collection/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/vite.config.ts b/src/Umbraco.Web.UI.Client/src/packages/core/vite.config.ts index b77da682d3..5ba008ff34 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/vite.config.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/vite.config.ts @@ -46,8 +46,10 @@ export default defineConfig({ 'notification/index': './notification/index.ts', 'object-type/index': './object-type/index.ts', 'picker-input/index': './picker-input/index.ts', + 'picker-data-source/index': './picker-data-source/index.ts', 'picker/index': './picker/index.ts', 'property-action/index': './property-action/index.ts', + 'property-editor-data-source/index': './property-editor-data-source/index.ts', 'property-editor/index': './property-editor/index.ts', 'property/index': './property/index.ts', 'recycle-bin/index': './recycle-bin/index.ts', @@ -57,6 +59,7 @@ export default defineConfig({ 'section/index': './section/index.ts', 'server-file-system/index': './server-file-system/index.ts', 'server/index': './server/index.ts', + 'search/index': './search/index.ts', 'shortcut/index': './shortcut/index.ts', 'sorter/index': './sorter/index.ts', 'store/index': './store/index.ts', diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/components/property-editor-config/property-editor-config.element.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/components/property-editor-config/property-editor-config.element.ts index 25abcce916..4ee552be0c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/components/property-editor-config/property-editor-config.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/components/property-editor-config/property-editor-config.element.ts @@ -51,6 +51,7 @@ export class UmbPropertyEditorConfigElement extends UmbLitElement { description=${ifDefined(property.description)} alias=${property.alias} property-editor-ui-alias=${property.propertyEditorUiAlias} + property-editor-data-source-alias=${ifDefined(property.propertyEditorDataSourceAlias)} .config=${property.config}>`, ) : html` { if (dataType) { this.name = dataType.name ?? ''; diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.data-source.ts index a79c9437c5..9b6d4ae062 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/server-data-source/data-type-detail.server.data-source.ts @@ -20,6 +20,7 @@ export class UmbDataTypeServerDataSource implements UmbDetailDataSource { #detailRequestManager = new UmbManagementApiDataTypeDetailDataRequestManager(this); + #editorDataSourceAlias = 'umbEditorDataSource'; /** * Creates a new Data Type scaffold @@ -69,6 +70,17 @@ export class UmbDataTypeServerDataSource if (!model.editorAlias) throw new Error('Property Editor Alias is missing'); if (!model.editorUiAlias) throw new Error('Property Editor UI Alias is missing'); + const values = [...model.values]; + + if (model.editorDataSourceAlias) { + const editorDataSourceValue = { + alias: this.#editorDataSourceAlias, + value: model.editorDataSourceAlias, + }; + + values.unshift(editorDataSourceValue); + } + // TODO: make data mapper to prevent errors const body: CreateDataTypeRequestModel = { id: model.unique, @@ -76,7 +88,7 @@ export class UmbDataTypeServerDataSource name: model.name, editorAlias: model.editorAlias, editorUiAlias: model.editorUiAlias, - values: model.values, + values, }; const { data, error } = await this.#detailRequestManager.create(body); @@ -96,12 +108,23 @@ export class UmbDataTypeServerDataSource if (!model.editorAlias) throw new Error('Property Editor Alias is missing'); if (!model.editorUiAlias) throw new Error('Property Editor UI Alias is missing'); + const values = [...model.values]; + + if (model.editorDataSourceAlias) { + const editorDataSourceValue = { + alias: this.#editorDataSourceAlias, + value: model.editorDataSourceAlias, + }; + + values.unshift(editorDataSourceValue); + } + // TODO: make data mapper to prevent errors const body: UpdateDataTypeRequestModel = { name: model.name, editorAlias: model.editorAlias, editorUiAlias: model.editorUiAlias, - values: model.values, + values, }; const { data, error } = await this.#detailRequestManager.update(model.unique, body); @@ -122,13 +145,26 @@ export class UmbDataTypeServerDataSource // TODO: change this to a mapper extension when the endpoints returns a $type for DataTypeResponseModel #mapServerResponseModelToEntityDetailModel(data: DataTypeResponseModel): UmbDataTypeDetailModel { + let values = data.values as Array; + const index = values?.findIndex((x) => x.alias === this.#editorDataSourceAlias); + + let editorDataSourceAlias; + + /* Remove the editorDataSourceAlias from the values collection + to prevent it from being treated as a regular config value. */ + if (index !== -1) { + editorDataSourceAlias = values?.[index].value as string | null; + values = values?.filter((value) => value.alias !== this.#editorDataSourceAlias) ?? []; + } + return { entityType: UMB_DATA_TYPE_ENTITY_TYPE, unique: data.id, name: data.name, editorAlias: data.editorAlias, editorUiAlias: data.editorUiAlias || null, - values: data.values as Array, + editorDataSourceAlias: editorDataSourceAlias, + values, }; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/types.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/types.ts index 38607afc84..571da307cd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/types.ts @@ -7,6 +7,7 @@ export interface UmbDataTypeDetailModel { name: string; editorAlias: string | undefined; editorUiAlias: string | null; + editorDataSourceAlias?: string | null; values: Array; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts index e9176832de..19d507f51e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts @@ -20,6 +20,7 @@ import type { PropertyEditorSettingsProperty, } from '@umbraco-cms/backoffice/property-editor'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import type { ManifestPropertyEditorDataSource } from '@umbraco-cms/backoffice/property-editor-data-source'; type EntityType = UmbDataTypeDetailModel; @@ -46,6 +47,9 @@ export class UmbDataTypeWorkspaceContext { readonly propertyEditorUiAlias = this._data.createObservablePartOfCurrent((data) => data?.editorUiAlias); readonly propertyEditorSchemaAlias = this._data.createObservablePartOfCurrent((data) => data?.editorAlias); + readonly propertyEditorDataSourceAlias = this._data.createObservablePartOfCurrent( + (data) => data?.editorDataSourceAlias, + ); readonly values = this._data.createObservablePartOfCurrent((data) => data?.values); async getValues() { @@ -59,9 +63,11 @@ export class UmbDataTypeWorkspaceContext #propertyEditorSchemaSettingsDefaultData: Array = []; #propertyEditorUISettingsDefaultData: Array = []; + #propertyEditorDataSourceSettingsDefaultData: Array = []; #propertyEditorSchemaSettingsProperties: Array = []; #propertyEditorUISettingsProperties: Array = []; + #propertyEditorDataSourceSettingsProperties: Array = []; #propertyEditorSchemaConfigDefaultUIAlias: string | null = null; @@ -82,6 +88,7 @@ export class UmbDataTypeWorkspaceContext this.#observePropertyEditorSchemaAlias(); this.#observePropertyEditorUIAlias(); + this.#observePropertyEditorDataSourceAlias(); this.routes.setRoutes([ { @@ -116,6 +123,8 @@ export class UmbDataTypeWorkspaceContext this.#propertyEditorUISettingsProperties = []; this.#propertyEditorSchemaSettingsDefaultData = []; this.#propertyEditorUISettingsDefaultData = []; + this.#propertyEditorDataSourceSettingsProperties = []; + this.#propertyEditorDataSourceSettingsDefaultData = []; this.#settingsDefaultData = undefined; this.#mergeConfigProperties(); } @@ -151,6 +160,18 @@ export class UmbDataTypeWorkspaceContext ); } + #observePropertyEditorDataSourceAlias() { + return this.observe( + this.propertyEditorDataSourceAlias, + (propertyEditorDataSourceAlias) => { + this.#propertyEditorDataSourceSettingsProperties = []; + this.#propertyEditorDataSourceSettingsDefaultData = []; + this.#observePropertyEditorDataSourceManifest(propertyEditorDataSourceAlias); + }, + 'dataSourceAlias', + ); + } + #observePropertyEditorSchemaManifest(propertyEditorSchemaAlias?: string) { if (!propertyEditorSchemaAlias) { this.removeUmbControllerByAlias('schema'); @@ -205,12 +226,38 @@ export class UmbDataTypeWorkspaceContext ); } + #observePropertyEditorDataSourceManifest(propertyEditorDataSourceAlias: string | null | undefined) { + if (!propertyEditorDataSourceAlias) { + this.removeUmbControllerByAlias('dataSource'); + this.#mergeConfigProperties(); + return; + } + this.observe( + umbExtensionsRegistry.byAlias(propertyEditorDataSourceAlias), + (manifest) => { + // Maps properties to have a weight, so they can be sorted, notice data source properties have a +2000 weight compared to schema properties. + this.#propertyEditorDataSourceSettingsProperties = (manifest?.meta.settings?.properties ?? []).map((x, i) => ({ + ...x, + weight: x.weight ?? 2000 + i, + })); + this.#propertyEditorDataSourceSettingsDefaultData = manifest?.meta.settings?.defaultData || []; + this.#mergeConfigProperties(); + }, + 'dataSource', + ); + } + #mergeConfigProperties() { - if (this.#propertyEditorSchemaSettingsProperties && this.#propertyEditorUISettingsProperties) { - // Reset the value to this array, and then afterwards append: - this.#properties.setValue(this.#propertyEditorSchemaSettingsProperties); - // Append the UI settings properties to the schema properties, so they can override the schema properties: - this.#properties.append(this.#propertyEditorUISettingsProperties); + const settings = [ + this.#propertyEditorSchemaSettingsProperties, + this.#propertyEditorUISettingsProperties, + this.#propertyEditorDataSourceSettingsProperties, + ].filter((x) => Array.isArray(x) && x.length > 0); + + const mergedSettings = settings.flat(); + + if (mergedSettings) { + this.#properties.setValue(mergedSettings); // If new or if the alias was changed then set default values. This 'complexity' to prevent setting default data when initialized [NL] const previousPropertyEditorUIAlias = this.#lastPropertyEditorUIAlias; @@ -225,7 +272,12 @@ export class UmbDataTypeWorkspaceContext } #transferConfigDefaultData() { - if (!this.#propertyEditorSchemaSettingsDefaultData || !this.#propertyEditorUISettingsDefaultData) return; + if ( + !this.#propertyEditorSchemaSettingsDefaultData || + !this.#propertyEditorUISettingsDefaultData || + !this.#propertyEditorDataSourceSettingsDefaultData + ) + return; const data = this._data.getCurrent(); if (!data) return; @@ -236,6 +288,7 @@ export class UmbDataTypeWorkspaceContext this.#settingsDefaultData = [ ...this.#propertyEditorSchemaSettingsDefaultData, ...this.#propertyEditorUISettingsDefaultData, + ...this.#propertyEditorDataSourceSettingsDefaultData, ] satisfies Array; const values: Array = []; @@ -285,6 +338,14 @@ export class UmbDataTypeWorkspaceContext this._data.updateCurrent({ editorUiAlias: alias }); } + getPropertyEditorDataSourceAlias() { + return this._data.getCurrent()?.editorDataSourceAlias; + } + + setPropertyEditorDataSourceAlias(alias?: string) { + this._data.updateCurrent({ editorDataSourceAlias: alias }); + } + /** * @function propertyValueByAlias * @param {string} propertyAlias diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/views/details/data-type-details-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/views/details/data-type-details-workspace-view.element.ts index c49b12a2ca..e8a6f2767a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/views/details/data-type-details-workspace-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/views/details/data-type-details-workspace-view.element.ts @@ -4,6 +4,7 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UMB_MISSING_PROPERTY_EDITOR_UI_ALIAS } from '@umbraco-cms/backoffice/property-editor'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { umbBindToValidation } from '@umbraco-cms/backoffice/validation'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/workspace'; @customElement('umb-data-type-details-workspace-view') @@ -20,6 +21,15 @@ export class UmbDataTypeDetailsWorkspaceViewEditElement extends UmbLitElement im @state() private _propertyEditorSchemaAlias?: string; + @state() + private _propertyEditorDataSourceAlias?: string | null = null; + + @state() + private _supportsDataSource = false; + + @state() + private _supportedDataSourceTypes: Array = []; + #workspaceContext?: typeof UMB_DATA_TYPE_WORKSPACE_CONTEXT.TYPE; constructor() { @@ -38,6 +48,7 @@ export class UmbDataTypeDetailsWorkspaceViewEditElement extends UmbLitElement im this.observe(this.#workspaceContext.propertyEditorUiAlias, (value) => { this._propertyEditorUiAlias = value ?? undefined; + this.#observePropertyEditorUIManifest(); }); this.observe(this.#workspaceContext.propertyEditorSchemaAlias, (value) => { @@ -51,6 +62,24 @@ export class UmbDataTypeDetailsWorkspaceViewEditElement extends UmbLitElement im this.observe(this.#workspaceContext.propertyEditorUiIcon, (value) => { this._propertyEditorUiIcon = value ?? undefined; }); + + this.observe(this.#workspaceContext.propertyEditorDataSourceAlias, (value) => { + this._propertyEditorDataSourceAlias = value; + }); + } + + #observePropertyEditorUIManifest() { + if (!this._propertyEditorUiAlias) return; + + this.observe(umbExtensionsRegistry.byTypeAndAlias('propertyEditorUi', this._propertyEditorUiAlias), (manifest) => { + this._supportsDataSource = manifest?.meta?.supportsDataSource?.enabled ?? false; + this._supportedDataSourceTypes = manifest?.meta?.supportsDataSource?.forDataSourceTypes ?? []; + }); + } + + #onDataSourceChange(event: CustomEvent) { + const value = (event.target as HTMLInputElement).value; + this.#workspaceContext?.setPropertyEditorDataSourceAlias(value || undefined); } override render() { @@ -70,24 +99,40 @@ export class UmbDataTypeDetailsWorkspaceViewEditElement extends UmbLitElement im ${umbBindToValidation(this, '$.editorUiAlias', this._propertyEditorUiAlias)}> + ${this.#renderDataSourceInput()} ${this.#renderSettings()} `; } + #renderDataSourceInput() { + if (!this._supportsDataSource) return nothing; + + return html` + + + + `; + } + #renderSettings() { if ( !this._propertyEditorUiAlias || !this._propertyEditorUiName || !this._propertyEditorSchemaAlias || this._propertyEditorUiAlias === UMB_MISSING_PROPERTY_EDITOR_UI_ALIAS - ) + ) { return nothing; - return html` - - - - `; + } + + return html` + + `; } static override styles = [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/views/info/workspace-view-data-type-info.element.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/views/info/workspace-view-data-type-info.element.ts index fb9188218b..c48593fb0c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/views/info/workspace-view-data-type-info.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/views/info/workspace-view-data-type-info.element.ts @@ -1,6 +1,6 @@ import { UMB_DATA_TYPE_WORKSPACE_CONTEXT } from '../../data-type-workspace.context-token.js'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement, state, nothing } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/workspace'; @@ -15,6 +15,9 @@ export class UmbWorkspaceViewDataTypeInfoElement extends UmbLitElement implement @state() private _uiAlias?: string | null; + @state() + private _dataSourceAlias?: string | null; + private _workspaceContext?: typeof UMB_DATA_TYPE_WORKSPACE_CONTEXT.TYPE; constructor() { @@ -40,6 +43,10 @@ export class UmbWorkspaceViewDataTypeInfoElement extends UmbLitElement implement this.observe(this._workspaceContext.propertyEditorUiAlias, (editorUiAlias) => { this._uiAlias = editorUiAlias; }); + + this.observe(this._workspaceContext.propertyEditorDataSourceAlias, (dataSourceAlias) => { + this._dataSourceAlias = dataSourceAlias; + }); } override render() { @@ -66,10 +73,22 @@ export class UmbWorkspaceViewDataTypeInfoElement extends UmbLitElement implement Property Editor UI Alias ${this._uiAlias} + ${this.#renderDataSourceInfo()} `; } + #renderDataSourceInfo() { + if (!this._dataSourceAlias) return nothing; + + return html` +
+ Property Editor Data Source Alias + ${this._dataSourceAlias} +
+ `; + } + static override styles = [ UmbTextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/index.ts index 317a4ffda7..47e72a7b23 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/index.ts @@ -13,6 +13,7 @@ export * from './publishing/index.js'; export * from './recycle-bin/index.js'; export * from './reference/index.js'; export * from './repository/index.js'; +export * from './search/index.js'; export * from './tree/index.js'; export * from './url/index.js'; export * from './user-permissions/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/search/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/search/index.ts index 4f07201dcf..ac0ad7d244 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/search/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/search/index.ts @@ -1 +1,2 @@ export * from './constants.js'; +export * from './document-search.repository.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/types.ts index 6ac1773e53..94763610fe 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/types.ts @@ -17,6 +17,7 @@ export type * from './modals/types.js'; export type * from './preview/types.js'; export type * from './publishing/types.js'; export type * from './recycle-bin/types.js'; +export type * from './search/types.js'; export type * from './repository/types.js'; export type * from './tree/types.js'; export type * from './url/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/language/components/input-language/input-language.context.ts b/src/Umbraco.Web.UI.Client/src/packages/language/components/input-language/input-language.context.ts index caf52fdb90..d0db74ad8d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/language/components/input-language/input-language.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/language/components/input-language/input-language.context.ts @@ -4,7 +4,11 @@ import { UMB_LANGUAGE_PICKER_MODAL } from '../../modals/language-picker/constant import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; -export class UmbLanguagePickerInputContext extends UmbPickerInputContext { +export class UmbLanguagePickerInputContext extends UmbPickerInputContext< + UmbLanguageItemModel, + // TODO: add UmbLanguageCollectionItemModel when it's created + UmbLanguageItemModel +> { constructor(host: UmbControllerHost) { super(host, UMB_LANGUAGE_ITEM_REPOSITORY_ALIAS, UMB_LANGUAGE_PICKER_MODAL); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts index 7642a23e83..8974dc6ebb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts @@ -37,7 +37,7 @@ const root: UmbMediaPathModel = { name: 'Media', unique: null, entityType: UMB_M // TODO: investigate how we can reuse the picker-search-field element, picker context etc. @customElement('umb-media-picker-modal') export class UmbMediaPickerModalElement extends UmbPickerModalBaseElement< - UmbMediaItemModel, + UmbMediaTreeItemModel, UmbMediaPickerModalData, UmbMediaPickerModalValue > { @@ -110,6 +110,9 @@ export class UmbMediaPickerModalElement extends UmbPickerModalBaseElement< override async connectedCallback(): Promise { super.connectedCallback(); if (this.data?.pickableFilter) { + // TODO: investigate why we need the ts-ignore here + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore this._selectableFilter = this.data?.pickableFilter; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/media-picker-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/media-picker-modal.token.ts index aebece4e35..91ba37a784 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/media-picker-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/media-picker-modal.token.ts @@ -1,8 +1,8 @@ -import type { UmbMediaItemModel } from '../../repository/types.js'; +import type { UmbMediaTreeItemModel } from '../../tree/types.js'; import type { UmbTreePickerModalData } from '@umbraco-cms/backoffice/tree'; import { UmbModalToken, type UmbPickerModalValue } from '@umbraco-cms/backoffice/modal'; -export type UmbMediaPickerModalData = UmbTreePickerModalData; +export type UmbMediaPickerModalData = UmbTreePickerModalData; export type UmbMediaPickerModalValue = UmbPickerModalValue; export const UMB_MEDIA_PICKER_MODAL = new UmbModalToken( diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/search/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/search/index.ts index 8442641321..b7ce4ba6d3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/search/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/search/index.ts @@ -1 +1,2 @@ export * from './media.search-provider.js'; +export * from './media-search.repository.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-group/components/input-member-group/input-member-group.context.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-group/components/input-member-group/input-member-group.context.ts index 4f56828544..465a7a4eff 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-group/components/input-member-group/input-member-group.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-group/components/input-member-group/input-member-group.context.ts @@ -4,7 +4,11 @@ import { UMB_MEMBER_GROUP_PICKER_MODAL } from '../member-group-picker-modal/memb import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; -export class UmbMemberGroupPickerInputContext extends UmbPickerInputContext { +export class UmbMemberGroupPickerInputContext extends UmbPickerInputContext< + UmbMemberGroupItemModel, + // TODO: Change to UmbMemberGroupCollectionItemModel when it exists + UmbMemberGroupItemModel +> { constructor(host: UmbControllerHostElement) { super(host, UMB_MEMBER_GROUP_ITEM_REPOSITORY_ALIAS, UMB_MEMBER_GROUP_PICKER_MODAL); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-group/repository/item/types.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-group/repository/item/types.ts index 76290819a6..10f3d84206 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-group/repository/item/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-group/repository/item/types.ts @@ -1,7 +1,5 @@ -import type { UmbMemberGroupEntityType } from '../../entity.js'; +import type { UmbItemModel } from '@umbraco-cms/backoffice/entity-item'; -export interface UmbMemberGroupItemModel { - entityType: UmbMemberGroupEntityType; - unique: string; +export interface UmbMemberGroupItemModel extends UmbItemModel { name: string; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/constants.ts new file mode 100644 index 0000000000..589e5fc9af --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/constants.ts @@ -0,0 +1,4 @@ +export * from './picker-collection/constants.js'; +export * from './picker-item/constants.js'; +export * from './picker-search/constants.js'; +export * from './picker-tree/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/input/entity-data-picker-data-source.context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/input/entity-data-picker-data-source.context.token.ts new file mode 100644 index 0000000000..4af8533cfd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/input/entity-data-picker-data-source.context.token.ts @@ -0,0 +1,9 @@ +import type { UmbEntityDataPickerDataSourceApiContext } from './entity-data-picker-data-source.context.js'; +import type { UmbPickerDataSource } from '@umbraco-cms/backoffice/picker-data-source'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +const contextAlias = 'UmbEntityDataPickerDataSourceApiContext'; + +export const UMB_ENTITY_DATA_PICKER_DATA_SOURCE_API_CONTEXT = new UmbContextToken< + UmbEntityDataPickerDataSourceApiContext +>(contextAlias); diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/input/entity-data-picker-data-source.context.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/input/entity-data-picker-data-source.context.ts new file mode 100644 index 0000000000..08774beafd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/input/entity-data-picker-data-source.context.ts @@ -0,0 +1,24 @@ +import { UMB_ENTITY_DATA_PICKER_DATA_SOURCE_API_CONTEXT } from './entity-data-picker-data-source.context.token.js'; +import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbBasicState } from '@umbraco-cms/backoffice/observable-api'; +import type { UmbPickerDataSource } from '@umbraco-cms/backoffice/picker-data-source'; + +export class UmbEntityDataPickerDataSourceApiContext< + DataSourceApiType extends UmbPickerDataSource, +> extends UmbContextBase { + #dataSourceApi = new UmbBasicState(undefined); + public readonly dataSourceApi = this.#dataSourceApi.asObservable(); + + constructor(host: UmbControllerHost) { + super(host, UMB_ENTITY_DATA_PICKER_DATA_SOURCE_API_CONTEXT); + } + + setDataSourceApi(dataSourceApi: DataSourceApiType) { + this.#dataSourceApi.setValue(dataSourceApi); + } + + getDataSourceApi(): DataSourceApiType | undefined { + return this.#dataSourceApi.getValue(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/input/input-entity-data.context.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/input/input-entity-data.context.ts new file mode 100644 index 0000000000..3b4b62595d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/input/input-entity-data.context.ts @@ -0,0 +1,179 @@ +import { UMB_ENTITY_DATA_PICKER_COLLECTION_MENU_ALIAS } from '../picker-collection/constants.js'; +import { UMB_ENTITY_DATA_PICKER_TREE_ALIAS } from '../picker-tree/constants.js'; +import { UMB_ENTITY_DATA_PICKER_SEARCH_PROVIDER_ALIAS } from '../picker-search/constants.js'; +import { UMB_ENTITY_DATA_PICKER_ITEM_REPOSITORY_ALIAS } from '../constants.js'; +import { UmbEntityDataPickerDataSourceApiContext } from './entity-data-picker-data-source.context.js'; +import { + UMB_COLLECTION_ITEM_PICKER_MODAL_ALIAS, + type UmbCollectionItemPickerModalData, + type UmbCollectionItemPickerModalValue, +} from '@umbraco-cms/backoffice/collection'; +import type { ManifestPropertyEditorDataSource } from '@umbraco-cms/backoffice/property-editor-data-source'; +import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import { UmbModalToken, type UmbPickerModalData } from '@umbraco-cms/backoffice/modal'; +import { + UMB_TREE_PICKER_MODAL_ALIAS, + type UmbTreePickerModalData, + type UmbTreePickerModalValue, +} from '@umbraco-cms/backoffice/tree'; +import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { + isPickerCollectionDataSource, + isPickerSearchableDataSource, + isPickerTreeDataSource, + type UmbPickerCollectionDataSource, + type UmbPickerDataSource, + type UmbPickerTreeDataSource, +} from '@umbraco-cms/backoffice/picker-data-source'; +import type { UmbItemModel } from '@umbraco-cms/backoffice/entity-item'; +import type { UmbConfigCollectionModel } from '@umbraco-cms/backoffice/utils'; + +export class UmbEntityDataPickerInputContext extends UmbPickerInputContext { + #dataSourceAlias?: string; + #dataSourceApiInitializer?: UmbExtensionApiInitializer; + #dataSourceApi?: UmbPickerDataSource; + #dataSourceConfig?: UmbConfigCollectionModel | undefined; + + #dataSourceApiContext = new UmbEntityDataPickerDataSourceApiContext(this); + + constructor(host: UmbControllerHost) { + super(host, UMB_ENTITY_DATA_PICKER_ITEM_REPOSITORY_ALIAS); + } + + /** + * Sets the data source alias for the input context. + * @param {(string | undefined)} alias + * @memberof UmbEntityDataPickerInputContext + */ + setDataSourceAlias(alias: string | undefined) { + this.#dataSourceAlias = alias; + this.#createDataSourceApi(alias); + } + + /** + * Gets the data source alias for the input context. + * @returns {(string | undefined)} The data source alias. + * @memberof UmbEntityDataPickerInputContext + */ + getDataSourceAlias(): string | undefined { + return this.#dataSourceAlias; + } + + /** + * Sets the data source config for the input context. + * @param {(UmbPropertyEditorDataSourceConfigModel | undefined)} config The data source config. + * @memberof UmbEntityDataPickerInputContext + */ + setDataSourceConfig(config: UmbConfigCollectionModel | undefined) { + this.#dataSourceConfig = config; + + if (this.#dataSourceApi) { + this.#dataSourceApi.setConfig?.(config); + } + } + + /** + * Gets the data source config for the input context. + * @returns {(UmbPropertyEditorDataSourceConfigModel | undefined)} The data source config. + * @memberof UmbEntityDataPickerInputContext + */ + getDataSourceConfig(): UmbConfigCollectionModel | undefined { + return this.#dataSourceConfig; + } + + override async openPicker(pickerData?: Partial>) { + this.modalAlias = this.#getModalToken(); + await super.openPicker(pickerData); + } + + #createDataSourceApi(dataSourceAlias: string | undefined) { + if (!dataSourceAlias) { + this.#dataSourceApiInitializer?.destroy(); + return; + } + + this.#dataSourceApiInitializer = new UmbExtensionApiInitializer< + ManifestPropertyEditorDataSource, + UmbExtensionApiInitializer, + UmbPickerDataSource + >(this, umbExtensionsRegistry, dataSourceAlias, [this._host], (permitted, ctrl) => { + if (!permitted) { + // TODO: clean up if not permitted + return; + } + + // TODO: Check if it is a picker data source + this.#dataSourceApi = ctrl.api as UmbPickerDataSource; + this.#dataSourceApi.setConfig?.(this.#dataSourceConfig); + + this.#dataSourceApiContext.setDataSourceApi(this.#dataSourceApi); + }); + } + + #getModalToken() { + if (!this.#dataSourceApi) return; + + const dataSourceApi = this.#dataSourceApi; + + const isTreeDataSource = isPickerTreeDataSource(dataSourceApi); + const isCollectionDataSource = isPickerCollectionDataSource(dataSourceApi); + + // Choose the picker type based on what the data source supports + if (isTreeDataSource) { + return this.#createTreeItemPickerModalToken(dataSourceApi); + } else if (isCollectionDataSource) { + return this.#createCollectionItemPickerModalToken(dataSourceApi); + } else { + throw new Error('The data source API is not a supported type of picker data source.'); + } + } + + #createTreeItemPickerModalToken(api: UmbPickerTreeDataSource) { + const supportsSearch = isPickerSearchableDataSource(api); + + return new UmbModalToken, UmbTreePickerModalValue>( + UMB_TREE_PICKER_MODAL_ALIAS, + { + modal: { + type: 'sidebar', + size: 'small', + }, + data: { + treeAlias: UMB_ENTITY_DATA_PICKER_TREE_ALIAS, + expandTreeRoot: true, + search: supportsSearch + ? { + providerAlias: UMB_ENTITY_DATA_PICKER_SEARCH_PROVIDER_ALIAS, + } + : undefined, + }, + }, + ); + } + + #createCollectionItemPickerModalToken(api: UmbPickerCollectionDataSource) { + const supportsSearch = isPickerSearchableDataSource(api); + + return new UmbModalToken, UmbCollectionItemPickerModalValue>( + UMB_COLLECTION_ITEM_PICKER_MODAL_ALIAS, + { + modal: { + type: 'sidebar', + size: 'small', + }, + data: { + collection: { + menuAlias: UMB_ENTITY_DATA_PICKER_COLLECTION_MENU_ALIAS, + }, + search: supportsSearch + ? { + providerAlias: UMB_ENTITY_DATA_PICKER_SEARCH_PROVIDER_ALIAS, + } + : undefined, + }, + }, + ); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/input/input-entity-data.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/input/input-entity-data.element.ts new file mode 100644 index 0000000000..08514a7729 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/input/input-entity-data.element.ts @@ -0,0 +1,237 @@ +import { UmbEntityDataPickerInputContext } from './input-entity-data.context.js'; +import { css, html, customElement, property, state, repeat, nothing, when } from '@umbraco-cms/backoffice/external/lit'; +import { splitStringToArray, type UmbConfigCollectionModel } from '@umbraco-cms/backoffice/utils'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; +import { UUIFormControlMixin } from '@umbraco-cms/backoffice/external/uui'; +import type { UmbRepositoryItemsStatus } from '@umbraco-cms/backoffice/repository'; +import type { UmbItemModel } from '@umbraco-cms/backoffice/entity-item'; + +@customElement('umb-input-entity-data') +export class UmbInputEntityDataElement extends UUIFormControlMixin(UmbLitElement, '') { + #sorter = new UmbSorterController(this, { + getUniqueOfElement: (element) => { + return element.id; + }, + getUniqueOfModel: (modelEntry) => { + return modelEntry; + }, + identifier: 'Umb.SorterIdentifier.InputEntityData', + itemSelector: 'umb-entity-item-ref', + containerSelector: 'uui-ref-list', + onChange: ({ model }) => { + this.selection = model; + this.dispatchEvent(new UmbChangeEvent()); + }, + }); + + public set dataSourceAlias(value: string | undefined) { + this.#pickerInputContext.setDataSourceAlias(value); + } + public get dataSourceAlias(): string | undefined { + return this.#pickerInputContext.getDataSourceAlias(); + } + + public set dataSourceConfig(config: UmbConfigCollectionModel | undefined) { + this.#pickerInputContext.setDataSourceConfig(config); + } + public get dataSourceConfig(): UmbConfigCollectionModel | undefined { + return this.#pickerInputContext.getDataSourceConfig(); + } + + /** + * This is a minimum amount of selected items in this input. + * @type {number} + * @attr + * @default + */ + @property({ type: Number }) + public set min(value: number) { + this.#pickerInputContext.min = value; + } + public get min(): number { + return this.#pickerInputContext.min; + } + + /** + * 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 + */ + @property({ type: Number }) + public set max(value: number) { + this.#pickerInputContext.max = value; + } + public get max(): number { + return this.#pickerInputContext.max; + } + + /** + * Max validation message. + * @type {boolean} + * @attr + * @default + */ + @property({ type: String, attribute: 'max-message' }) + maxMessage = 'This field exceeds the allowed amount of items'; + + @property({ type: Array }) + public set selection(uniques: Array) { + this.#pickerInputContext.setSelection(uniques); + this.#sorter.setModel(uniques); + } + public get selection(): Array { + return this.#pickerInputContext.getSelection(); + } + + @property() + public override set value(uniques: string) { + this.selection = splitStringToArray(uniques); + } + public override get value(): string { + return this.selection.join(','); + } + + /** + * Sets the input to readonly mode, meaning value cannot be changed but still able to read and select its content. + * @type {boolean} + * @attr + * @default + */ + @property({ type: Boolean, reflect: true }) + public get readonly() { + return this.#readonly; + } + public set readonly(value) { + this.#readonly = value; + + if (this.#readonly) { + this.#sorter.disable(); + } else { + this.#sorter.enable(); + } + } + #readonly = false; + + @state() + private _items: Array = []; + + @state() + private _statuses?: Array; + + #pickerInputContext = new UmbEntityDataPickerInputContext(this); + + constructor() { + super(); + + this.addValidator( + 'rangeUnderflow', + () => this.minMessage, + () => !!this.min && this.#pickerInputContext.getSelection().length < this.min, + ); + + this.addValidator( + 'rangeOverflow', + () => this.maxMessage, + () => !!this.max && this.#pickerInputContext.getSelection().length > this.max, + ); + + this.observe( + this.#pickerInputContext.selection, + (selection) => (this.value = selection.join(',')), + '_observeSelection', + ); + + this.observe( + this.#pickerInputContext.selectedItems, + (selectedItems) => (this._items = selectedItems), + '_observerItems', + ); + + this.observe(this.#pickerInputContext.statuses, (statuses) => (this._statuses = statuses), '_observerStatuses'); + } + + protected override getFormElement() { + return undefined; + } + + #onRemove(unique: string) { + this.#pickerInputContext.requestRemoveItem(unique); + } + + override render() { + return html`${this.#renderItems()} ${this.#renderAddButton()}`; + } + + #renderAddButton() { + if (this.max > 0 && this.selection.length >= this.max) return nothing; + if (this.readonly && this.selection.length > 0) return nothing; + return html` + this.#pickerInputContext.openPicker()} + label="${this.localize.term('general_choose')}" + ?disabled=${this.readonly}> + `; + } + + #renderItems() { + if (!this._statuses) return; + return html` + + ${repeat( + this._statuses, + (status) => status.unique, + (status) => { + const unique = status.unique; + const item = this._items?.find((x) => x.unique === unique); + return html` + ${when( + !this.readonly, + () => html` + + this.#onRemove(unique)}> + + `, + )} + `; + }, + )} + + `; + } + + static override styles = [ + css` + #btn-add { + width: 100%; + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-input-entity-data': UmbInputEntityDataElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/manifests.ts new file mode 100644 index 0000000000..5f995de664 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/manifests.ts @@ -0,0 +1,13 @@ +import { manifests as pickerCollectionMenuManifests } from './picker-collection/manifests.js'; +import { manifests as pickerItemManifests } from './picker-item/manifests.js'; +import { manifests as pickerSearchManifests } from './picker-search/manifests.js'; +import { manifests as pickerTreeManifests } from './picker-tree/manifests.js'; +import { manifests as propertyEditorManifests } from './property-editor/manifests.js'; + +export const manifests: Array = [ + ...pickerCollectionMenuManifests, + ...pickerItemManifests, + ...pickerSearchManifests, + ...pickerTreeManifests, + ...propertyEditorManifests, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-collection/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-collection/constants.ts new file mode 100644 index 0000000000..c49bfcc2d9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-collection/constants.ts @@ -0,0 +1,2 @@ +export const UMB_ENTITY_DATA_PICKER_COLLECTION_MENU_ALIAS = 'Umb.collectionMenu.EntityDataPicker'; +export const UMB_ENTITY_DATA_PICKER_COLLECTION_REPOSITORY_ALIAS = 'Umb.Repository.EntityDataPickerCollection'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-collection/entity-data-picker-collection.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-collection/entity-data-picker-collection.repository.ts new file mode 100644 index 0000000000..382134e4c1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-collection/entity-data-picker-collection.repository.ts @@ -0,0 +1,40 @@ +import { UMB_ENTITY_DATA_PICKER_DATA_SOURCE_API_CONTEXT } from '../input/entity-data-picker-data-source.context.token.js'; +import type { UmbCollectionFilterModel, UmbCollectionRepository } from '@umbraco-cms/backoffice/collection'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; +import type { UmbPickerCollectionDataSource } from '@umbraco-cms/backoffice/picker-data-source'; +import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository'; + +export class UmbEntityDataPickerCollectionRepository + extends UmbRepositoryBase + implements UmbCollectionRepository, UmbApi +{ + #init: Promise<[any]>; + #pickerDataSourceContext?: typeof UMB_ENTITY_DATA_PICKER_DATA_SOURCE_API_CONTEXT.TYPE; + + constructor(host: UmbControllerHost) { + super(host); + + this.#init = Promise.all([ + this.consumeContext(UMB_ENTITY_DATA_PICKER_DATA_SOURCE_API_CONTEXT, (context) => { + this.#pickerDataSourceContext = context; + }).asPromise(), + ]); + } + + async requestCollection(filter: UmbCollectionFilterModel) { + await this.#init; + const api = await this.#getApi(); + return api.requestCollection(filter); + } + + async #getApi() { + const api = (await this.observe( + this.#pickerDataSourceContext?.dataSourceApi, + )?.asPromise()) as UmbPickerCollectionDataSource; + if (!api) throw new Error('No data source API set'); + return api; + } +} + +export { UmbEntityDataPickerCollectionRepository as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-collection/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-collection/manifests.ts new file mode 100644 index 0000000000..5382b02974 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-collection/manifests.ts @@ -0,0 +1,22 @@ +import { + UMB_ENTITY_DATA_PICKER_COLLECTION_MENU_ALIAS, + UMB_ENTITY_DATA_PICKER_COLLECTION_REPOSITORY_ALIAS, +} from './constants.js'; + +export const manifests: Array = [ + { + type: 'repository', + alias: UMB_ENTITY_DATA_PICKER_COLLECTION_REPOSITORY_ALIAS, + name: 'Entity Data Picker Collection Repository', + api: () => import('./entity-data-picker-collection.repository.js'), + }, + { + type: 'collectionMenu', + kind: 'default', + alias: UMB_ENTITY_DATA_PICKER_COLLECTION_MENU_ALIAS, + name: 'Entity Data Picker Collection Menu', + meta: { + collectionRepositoryAlias: UMB_ENTITY_DATA_PICKER_COLLECTION_REPOSITORY_ALIAS, + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-item/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-item/constants.ts new file mode 100644 index 0000000000..898592120c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-item/constants.ts @@ -0,0 +1 @@ +export const UMB_ENTITY_DATA_PICKER_ITEM_REPOSITORY_ALIAS = 'Umb.Repository.EntityDataPickerItem'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-item/entity-data-picker-item.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-item/entity-data-picker-item.repository.ts new file mode 100644 index 0000000000..e6ee64bffe --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-item/entity-data-picker-item.repository.ts @@ -0,0 +1,37 @@ +import { UMB_ENTITY_DATA_PICKER_DATA_SOURCE_API_CONTEXT } from '../input/entity-data-picker-data-source.context.token.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbItemModel } from '@umbraco-cms/backoffice/entity-item'; +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; +import { UmbRepositoryBase, type UmbItemRepository } from '@umbraco-cms/backoffice/repository'; + +export class UmbEntityDataPickerItemRepository + extends UmbRepositoryBase + implements UmbItemRepository, UmbApi +{ + #init: Promise<[any]>; + #pickerDataSourceContext?: typeof UMB_ENTITY_DATA_PICKER_DATA_SOURCE_API_CONTEXT.TYPE; + + constructor(host: UmbControllerHost) { + super(host); + + this.#init = Promise.all([ + this.consumeContext(UMB_ENTITY_DATA_PICKER_DATA_SOURCE_API_CONTEXT, (context) => { + this.#pickerDataSourceContext = context; + }).asPromise(), + ]); + } + + async requestItems(uniques: Array) { + await this.#init; + const api = await this.#getApi(); + return api.requestItems(uniques); + } + + async #getApi() { + const api = await this.observe(this.#pickerDataSourceContext?.dataSourceApi)?.asPromise(); + if (!api) throw new Error('No data source API set'); + return api; + } +} + +export { UmbEntityDataPickerItemRepository as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-item/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-item/manifests.ts new file mode 100644 index 0000000000..8b7fda2c2c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-item/manifests.ts @@ -0,0 +1,10 @@ +import { UMB_ENTITY_DATA_PICKER_ITEM_REPOSITORY_ALIAS } from './constants.js'; + +export const manifests: Array = [ + { + type: 'repository', + alias: UMB_ENTITY_DATA_PICKER_ITEM_REPOSITORY_ALIAS, + name: 'Entity Data Picker Item Repository', + api: () => import('./entity-data-picker-item.repository.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-search/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-search/constants.ts new file mode 100644 index 0000000000..d1a84f9925 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-search/constants.ts @@ -0,0 +1 @@ +export const UMB_ENTITY_DATA_PICKER_SEARCH_PROVIDER_ALIAS = 'Umb.SearchProvider.EntityDataPicker'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-search/entity-data-picker.search-provider.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-search/entity-data-picker.search-provider.ts new file mode 100644 index 0000000000..7bde770a79 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-search/entity-data-picker.search-provider.ts @@ -0,0 +1,40 @@ +import { UMB_ENTITY_DATA_PICKER_DATA_SOURCE_API_CONTEXT } from '../input/entity-data-picker-data-source.context.token.js'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; +import type { UmbPickerSearchableDataSource } from '@umbraco-cms/backoffice/picker-data-source'; +import type { UmbSearchProvider, UmbSearchRequestArgs, UmbSearchResultItemModel } from '@umbraco-cms/backoffice/search'; + +export class UmbEntityDataPickerSearchProvider + extends UmbControllerBase + implements UmbSearchProvider, UmbApi +{ + #init: Promise<[any]>; + #pickerDataSourceContext?: typeof UMB_ENTITY_DATA_PICKER_DATA_SOURCE_API_CONTEXT.TYPE; + + constructor(host: UmbControllerHost) { + super(host); + + this.#init = Promise.all([ + this.consumeContext(UMB_ENTITY_DATA_PICKER_DATA_SOURCE_API_CONTEXT, (context) => { + this.#pickerDataSourceContext = context; + }).asPromise(), + ]); + } + + async search(args: UmbSearchRequestArgs) { + await this.#init; + const api = await this.#getApi(); + return api.search?.(args); + } + + async #getApi() { + const api = (await this.observe(this.#pickerDataSourceContext?.dataSourceApi)?.asPromise()) as + | UmbPickerSearchableDataSource + | undefined; + if (!api) throw new Error('No data source API set'); + return api; + } +} + +export { UmbEntityDataPickerSearchProvider as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-search/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-search/manifests.ts new file mode 100644 index 0000000000..99da18f628 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-search/manifests.ts @@ -0,0 +1,10 @@ +import { UMB_ENTITY_DATA_PICKER_SEARCH_PROVIDER_ALIAS } from './constants.js'; + +export const manifests: Array = [ + { + type: 'searchProvider', + alias: UMB_ENTITY_DATA_PICKER_SEARCH_PROVIDER_ALIAS, + name: 'Entity Data Picker Search Provider', + api: () => import('./entity-data-picker.search-provider.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-tree/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-tree/constants.ts new file mode 100644 index 0000000000..eb6c10fd47 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-tree/constants.ts @@ -0,0 +1 @@ +export const UMB_ENTITY_DATA_PICKER_TREE_ALIAS = 'Umb.Tree.EntityDataPicker'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-tree/entity-data-picker-tree.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-tree/entity-data-picker-tree.repository.ts new file mode 100644 index 0000000000..39ee8a1d50 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-tree/entity-data-picker-tree.repository.ts @@ -0,0 +1,60 @@ +import { UMB_ENTITY_DATA_PICKER_DATA_SOURCE_API_CONTEXT } from '../input/entity-data-picker-data-source.context.token.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; +import type { UmbPickerTreeDataSource } from '@umbraco-cms/backoffice/picker-data-source'; +import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository'; +import type { + UmbTreeAncestorsOfRequestArgs, + UmbTreeChildrenOfRequestArgs, + UmbTreeRepository, + UmbTreeRootItemsRequestArgs, +} from '@umbraco-cms/backoffice/tree'; + +export class UmbEntityDataPickerTreeRepository extends UmbRepositoryBase implements UmbTreeRepository, UmbApi { + #init: Promise<[any]>; + #pickerDataSourceContext?: typeof UMB_ENTITY_DATA_PICKER_DATA_SOURCE_API_CONTEXT.TYPE; + + constructor(host: UmbControllerHost) { + super(host); + + this.#init = Promise.all([ + this.consumeContext(UMB_ENTITY_DATA_PICKER_DATA_SOURCE_API_CONTEXT, (context) => { + this.#pickerDataSourceContext = context; + }).asPromise(), + ]); + } + + async requestTreeRoot() { + await this.#init; + const api = await this.#getApi(); + return api.requestTreeRoot(); + } + + async requestTreeRootItems(args: UmbTreeRootItemsRequestArgs) { + await this.#init; + const api = await this.#getApi(); + return api.requestTreeRootItems(args); + } + + async requestTreeItemsOf(args: UmbTreeChildrenOfRequestArgs) { + await this.#init; + const api = await this.#getApi(); + return api.requestTreeItemsOf(args); + } + + async requestTreeItemAncestors(args: UmbTreeAncestorsOfRequestArgs) { + await this.#init; + const api = await this.#getApi(); + return api.requestTreeItemAncestors(args); + } + + async #getApi() { + const api = (await this.observe( + this.#pickerDataSourceContext?.dataSourceApi, + )?.asPromise()) as UmbPickerTreeDataSource; + if (!api) throw new Error('No data source API set'); + return api; + } +} + +export { UmbEntityDataPickerTreeRepository as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-tree/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-tree/manifests.ts new file mode 100644 index 0000000000..fb6869c07a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/picker-tree/manifests.ts @@ -0,0 +1,21 @@ +import { UMB_ENTITY_DATA_PICKER_TREE_ALIAS } from './constants.js'; + +const repositoryAlias = 'Umb.Repository.EntityDataPickerTree'; + +export const manifests: Array = [ + { + type: 'repository', + alias: repositoryAlias, + name: 'Entity Data Picker Tree Repository', + api: () => import('./entity-data-picker-tree.repository.js'), + }, + { + type: 'tree', + kind: 'default', + alias: UMB_ENTITY_DATA_PICKER_TREE_ALIAS, + name: 'Entity Data Picker Tree', + meta: { + repositoryAlias, + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/property-editor/entity-data-picker-property-editor-ui.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/property-editor/entity-data-picker-property-editor-ui.element.ts new file mode 100644 index 0000000000..71d5e95c7f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/property-editor/entity-data-picker-property-editor-ui.element.ts @@ -0,0 +1,139 @@ +import type { UmbInputEntityDataElement } from '../input/input-entity-data.element.js'; +import type { UmbEntityDataPickerPropertyEditorValue } from './types.js'; +import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; +import type { + UmbPropertyEditorConfigCollection, + UmbPropertyEditorUiElement, +} from '@umbraco-cms/backoffice/property-editor'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; + +// import of local component +import '../input/input-entity-data.element.js'; +import type { UmbConfigCollectionModel } from '@umbraco-cms/backoffice/utils'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import type { ManifestPropertyEditorDataSource } from '@umbraco-cms/backoffice/property-editor-data-source'; +import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models'; + +@customElement('umb-entity-data-picker-property-editor-ui') +export class UmbEntityDataPickerPropertyEditorUIElement + extends UmbFormControlMixin( + UmbLitElement, + undefined, + ) + implements UmbPropertyEditorUiElement +{ + /** + * The data source alias to use for this property editor. + * @type {string} + * @memberof UmbEntityDataPickerPropertyEditorUIElement + */ + @property({ type: String }) + private _dataSourceAlias?: string | undefined; + public get dataSourceAlias(): string | undefined { + return this._dataSourceAlias; + } + public set dataSourceAlias(value: string | undefined) { + this._dataSourceAlias = value; + this.#extractDataSourceConfig(); + } + + /** + * Sets the input to readonly mode, meaning value cannot be changed but still able to read and select its content. + * @type {boolean} + * @attr + * @default false + */ + @property({ type: Boolean, reflect: true }) + readonly = false; + + @state() + private _min = 0; + + @state() + private _minMessage = ''; + + @state() + private _max = Infinity; + + @state() + private _maxMessage = ''; + + @state() + private _dataSourceConfig?: UmbConfigCollectionModel; + + public set config(config: UmbPropertyEditorConfigCollection | undefined) { + this.#propertyEditorConfigCollection = config; + + const minMax = config?.getValueByAlias('validationLimit'); + this._min = minMax?.min ?? 0; + this._max = minMax?.max ?? Infinity; + + this._minMessage = `${this.localize.term('validation_minCount')} ${this._min} ${this.localize.term('validation_items')}`; + this._maxMessage = `${this.localize.term('validation_maxCount')} ${this._max} ${this.localize.term('validation_itemsSelected')}`; + + this._dataSourceConfig = this.#extractDataSourceConfig(); + } + + #propertyEditorConfigCollection?: UmbPropertyEditorConfigCollection; + + #extractDataSourceConfig() { + if (!this._dataSourceAlias || !this.#propertyEditorConfigCollection) { + this._dataSourceConfig = undefined; + return; + } + + const dataSourceExtension = umbExtensionsRegistry.getByAlias( + this._dataSourceAlias, + ); + + if (!dataSourceExtension) { + throw new Error(`Data source with alias ${this._dataSourceAlias} not found`); + } + + const aliases = dataSourceExtension.meta?.settings?.properties.map((property) => property.alias); + const configAliasMatch = this.#propertyEditorConfigCollection.filter((configEntry) => + aliases?.includes(configEntry.alias), + ); + + const dataSourceConfig: UmbConfigCollectionModel | undefined = configAliasMatch?.map((configEntry) => { + return { + alias: configEntry.alias, + value: configEntry.value, + }; + }); + + return dataSourceConfig; + } + + override focus() { + return this.shadowRoot?.querySelector('umb-input-entity-data')?.focus(); + } + + #onChange(event: CustomEvent & { target: UmbInputEntityDataElement }) { + this.value = event.target.selection; + this.dispatchEvent(new UmbChangeEvent()); + } + + override render() { + return html``; + } +} + +export { UmbEntityDataPickerPropertyEditorUIElement as element }; + +declare global { + interface HTMLElementTagNameMap { + 'umb-entity-data-property-editor-ui': UmbEntityDataPickerPropertyEditorUIElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/property-editor/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/property-editor/manifests.ts new file mode 100644 index 0000000000..677f71af17 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/property-editor/manifests.ts @@ -0,0 +1,33 @@ +import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/property-editor'; + +const manifest: ManifestPropertyEditorUi = { + type: 'propertyEditorUi', + alias: 'Umb.PropertyEditorUi.EntityDataPicker', + name: 'Entity Data Picker Property Editor UI', + element: () => import('./entity-data-picker-property-editor-ui.element.js'), + meta: { + label: 'Entity Data Picker', + icon: 'icon-page-add', + group: 'pickers', + propertyEditorSchemaAlias: 'Umbraco.Plain.Json', + supportsReadOnly: true, + supportsDataSource: { + enabled: true, + forDataSourceTypes: ['picker'], + }, + settings: { + properties: [ + // TODO: Move this to schema manifest when server can validate it + { + alias: 'validationLimit', + label: 'Amount', + propertyEditorUiAlias: 'Umb.PropertyEditorUi.NumberRange', + config: [{ alias: 'validationRange', value: { min: 0, max: Infinity } }], + weight: 100, + }, + ], + }, + }, +}; + +export const manifests: Array = [manifest]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/property-editor/types.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/property-editor/types.ts new file mode 100644 index 0000000000..fa9bbf6564 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/entity-data-picker/property-editor/types.ts @@ -0,0 +1 @@ +export type UmbEntityDataPickerPropertyEditorValue = Array; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/manifests.ts index 948ce907b6..b464b9d80a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/manifests.ts @@ -25,6 +25,7 @@ import { manifests as textareaManifests } from './textarea/manifests.js'; import { manifests as textBoxManifests } from './text-box/manifests.js'; import { manifests as toggleManifests } from './toggle/manifests.js'; import { manifests as contentPickerManifests } from './content-picker/manifests.js'; +import { manifests as entityDataPickerManifests } from './entity-data-picker/manifests.js'; export const manifests: Array = [ ...checkboxListManifests, @@ -45,6 +46,7 @@ export const manifests: Array = [ ...textBoxManifests, ...toggleManifests, ...contentPickerManifests, + ...entityDataPickerManifests, acceptedType, colorEditor, dimensions, diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/package.json b/src/Umbraco.Web.UI.Client/src/packages/search/package.json deleted file mode 100644 index dce223609a..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/search/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@umbraco-backoffice/search", - "private": true, - "type": "module", - "scripts": { - "build": "vite build" - } -} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/umbraco-package.ts b/src/Umbraco.Web.UI.Client/src/packages/search/umbraco-package.ts deleted file mode 100644 index d669951eaa..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/search/umbraco-package.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const name = 'Umbraco.Core.Search'; -export const extensions = [ - { - name: 'Search Bundle', - alias: 'Umb.Bundle.Search', - type: 'bundle', - js: () => import('./manifests.js'), - }, -]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/vite.config.ts b/src/Umbraco.Web.UI.Client/src/packages/search/vite.config.ts deleted file mode 100644 index ce6964cd3b..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/search/vite.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { defineConfig } from 'vite'; -import { rmSync } from 'fs'; -import { getDefaultConfig } from '../../vite-config-base'; - -const dist = '../../../dist-cms/packages/search'; - -// delete the unbundled dist folder -rmSync(dist, { recursive: true, force: true }); - -export default defineConfig({ - ...getDefaultConfig({ dist }), -}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/static-file/entity.ts b/src/Umbraco.Web.UI.Client/src/packages/static-file/entity.ts new file mode 100644 index 0000000000..06e41eb0be --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/static-file/entity.ts @@ -0,0 +1,3 @@ +export const UMB_STATIC_FILE_ENTITY_TYPE = 'static-file'; + +export type UmbStaticFileEntityType = typeof UMB_STATIC_FILE_ENTITY_TYPE; diff --git a/src/Umbraco.Web.UI.Client/src/packages/static-file/repository/item/static-file-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/static-file/repository/item/static-file-item.server.data-source.ts index 82e552edd2..6c41bc552e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/static-file/repository/item/static-file-item.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/static-file/repository/item/static-file-item.server.data-source.ts @@ -1,3 +1,4 @@ +import { UMB_STATIC_FILE_ENTITY_TYPE } from '../../entity.js'; import type { UmbStaticFileItemModel } from './types.js'; import { UmbManagementApiStaticFileItemDataRequestManager } from './static-file-item.server.request-manager.js'; import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository'; @@ -41,6 +42,7 @@ export class UmbStaticFileItemServerDataSource extends UmbItemServerDataSourceBa const mapper = (item: StaticFileItemResponseModel): UmbStaticFileItemModel => { const serializer = new UmbServerFilePathUniqueSerializer(); return { + entityType: UMB_STATIC_FILE_ENTITY_TYPE, isFolder: item.isFolder, name: item.name, parentUnique: item.parent ? serializer.toUnique(item.parent.path) : null, diff --git a/src/Umbraco.Web.UI.Client/src/packages/static-file/repository/item/types.ts b/src/Umbraco.Web.UI.Client/src/packages/static-file/repository/item/types.ts index 58c91fcabd..ca1fc3df39 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/static-file/repository/item/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/static-file/repository/item/types.ts @@ -1,4 +1,7 @@ +/* TODO: This interface has a parenUnique. It looks more like a Tree Item than an Item? Investigate how this is used, +rename and extend The correct base model (tree or item) */ export interface UmbStaticFileItemModel { + entityType: string; isFolder: boolean; name: string; parentUnique: string | null; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/global-components/stylesheet-input/stylesheet-input.context.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/global-components/stylesheet-input/stylesheet-input.context.ts index 9ef3ef4b6f..87a84c215c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/global-components/stylesheet-input/stylesheet-input.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/global-components/stylesheet-input/stylesheet-input.context.ts @@ -1,14 +1,15 @@ import { UMB_STYLESHEET_ITEM_REPOSITORY_ALIAS } from '../../repository/index.js'; import type { UmbStylesheetItemModel } from '../../types.js'; +import type { UmbStylesheetTreeItemModel } from '../../tree/types.js'; import { UMB_STYLESHEET_PICKER_MODAL } from './stylesheet-picker-modal.token.js'; import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -export class UmbStylesheetPickerInputContext extends UmbPickerInputContext { +export class UmbStylesheetPickerInputContext extends UmbPickerInputContext< + UmbStylesheetItemModel, + UmbStylesheetTreeItemModel +> { constructor(host: UmbControllerHost) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - // TODO: Item and tree item types collide super(host, UMB_STYLESHEET_ITEM_REPOSITORY_ALIAS, UMB_STYLESHEET_PICKER_MODAL); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/table/modals/table-properties-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/table/modals/table-properties-modal.element.ts index 33c5a127cb..82122ab203 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/table/modals/table-properties-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/table/modals/table-properties-modal.element.ts @@ -2,7 +2,7 @@ import type { UmbTiptapTablePropertiesModalData, UmbTiptapTablePropertiesModalValue, } from './table-properties-modal.token.js'; -import { css, customElement, html, repeat, when } from '@umbraco-cms/backoffice/external/lit'; +import { css, customElement, html, ifDefined, repeat, when } from '@umbraco-cms/backoffice/external/lit'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; import type { UmbPropertyDatasetElement, UmbPropertyValueData } from '@umbraco-cms/backoffice/property'; import type { UmbPropertyEditorConfig } from '@umbraco-cms/backoffice/property-editor'; @@ -13,6 +13,7 @@ type UmbProperty = { description?: string; label: string; propertyEditorUiAlias: string; + propertyEditorDataSourceAlias?: string; }; @customElement('umb-tiptap-table-properties-modal') @@ -113,6 +114,7 @@ export class UmbTiptapTablePropertiesModalElement extends UmbModalBaseElement< alias=${property.alias} label=${property.label} property-editor-ui-alias=${property.propertyEditorUiAlias} + property-editor-data-source-alias=${ifDefined(property.propertyEditorDataSourceAlias)} .appearance=${this.#appearance} .config=${property.config}> diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/item/types.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/item/types.ts index 3a7b84b6d9..4f145f9920 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/item/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/item/types.ts @@ -1,5 +1,6 @@ -export interface UmbUserGroupItemModel { - unique: string; +import type { UmbItemModel } from '@umbraco-cms/backoffice/entity-item'; + +export interface UmbUserGroupItemModel extends UmbItemModel { name: string; icon: string | null; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/item/user-group-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/item/user-group-item.server.data-source.ts index 16055e1aa5..ff82509188 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/item/user-group-item.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/item/user-group-item.server.data-source.ts @@ -1,3 +1,4 @@ +import { UMB_USER_GROUP_ENTITY_TYPE } from '../../entity.js'; import type { UmbUserGroupItemModel } from './types.js'; import { UmbManagementApiUserGroupItemDataRequestManager } from './user-group-item.server.request-manager.js'; import type { UserGroupItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; @@ -37,6 +38,7 @@ export class UmbUserGroupItemServerDataSource extends UmbItemServerDataSourceBas const mapper = (item: UserGroupItemResponseModel): UmbUserGroupItemModel => { return { + entityType: UMB_USER_GROUP_ENTITY_TYPE, unique: item.id, name: item.name, icon: item.icon || null, diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/components/user-input/user-input.context.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/components/user-input/user-input.context.ts index 007d716eec..ead8931e58 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/components/user-input/user-input.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/components/user-input/user-input.context.ts @@ -1,10 +1,10 @@ -import { UMB_USER_ITEM_REPOSITORY_ALIAS } from '../../repository/index.js'; +import { UMB_USER_ITEM_REPOSITORY_ALIAS, type UmbUserItemModel } from '../../repository/index.js'; import type { UmbUserDetailModel } from '../../types.js'; import { UMB_USER_PICKER_MODAL } from '../../modals/user-picker/user-picker-modal.token.js'; import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -export class UmbUserPickerInputContext extends UmbPickerInputContext { +export class UmbUserPickerInputContext extends UmbPickerInputContext { constructor(host: UmbControllerHost) { super(host, UMB_USER_ITEM_REPOSITORY_ALIAS, UMB_USER_PICKER_MODAL); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/entity.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/entity.ts index 51932a19c6..b04b01a0b8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/webhook/entity.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/entity.ts @@ -4,6 +4,7 @@ export const UMB_WEBHOOK_WORKSPACE_ALIAS = 'Umb.Workspace.Webhook'; */ export const UMB_WEBHOOK_WORKSPACE = 'Umb.Workspace.Webhook'; +// TODO: move into each module export const UMB_WEBHOOK_ENTITY_TYPE = 'webhook'; export const UMB_WEBHOOK_ROOT_ENTITY_TYPE = 'webhook-root'; export const UMB_WEBHOOK_DELIVERY_ENTITY_TYPE = 'webhook-delivery'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/repository/item/types.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/repository/item/types.ts index de42a3a1e5..ecfc2b0c2a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/repository/item/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/repository/item/types.ts @@ -1,4 +1,5 @@ -export interface UmbWebhookItemModel { +import type { UmbNamedEntityModel } from '@umbraco-cms/backoffice/entity'; + +export interface UmbWebhookItemModel extends UmbNamedEntityModel { unique: string; - name: string; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/repository/item/webhook-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/repository/item/webhook-item.server.data-source.ts index f6115c194f..ca65024f61 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/repository/item/webhook-item.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/webhook/repository/item/webhook-item.server.data-source.ts @@ -1,3 +1,4 @@ +import { UMB_WEBHOOK_ENTITY_TYPE } from '../../../entity.js'; import type { UmbWebhookItemModel } from './types.js'; import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository'; import type { WebhookItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; @@ -42,7 +43,8 @@ export class UmbWebhookItemServerDataSource extends UmbItemServerDataSourceBase< const mapper = (item: WebhookItemResponseModel): UmbWebhookItemModel => { return { - unique: item.name, + unique: item.id, name: item.name, + entityType: UMB_WEBHOOK_ENTITY_TYPE, }; }; diff --git a/src/Umbraco.Web.UI.Client/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json index ef64a1e410..e1928fbeec 100644 --- a/src/Umbraco.Web.UI.Client/tsconfig.json +++ b/src/Umbraco.Web.UI.Client/tsconfig.json @@ -105,14 +105,19 @@ DON'T EDIT THIS FILE DIRECTLY. It is generated by /devops/tsconfig/index.js "@umbraco-cms/backoffice/menu": ["./src/packages/core/menu/index.ts"], "@umbraco-cms/backoffice/modal": ["./src/packages/core/modal/index.ts"], "@umbraco-cms/backoffice/models": ["./src/packages/core/models/index.ts"], + "@umbraco-cms/backoffice/search": ["./src/packages/core/search/index.ts"], "@umbraco-cms/backoffice/multi-url-picker": ["./src/packages/multi-url-picker/index.ts"], "@umbraco-cms/backoffice/notification": ["./src/packages/core/notification/index.ts"], "@umbraco-cms/backoffice/object-type": ["./src/packages/core/object-type/index.ts"], "@umbraco-cms/backoffice/package": ["./src/packages/packages/package/index.ts"], "@umbraco-cms/backoffice/partial-view": ["./src/packages/templating/partial-views/index.ts"], "@umbraco-cms/backoffice/picker-input": ["./src/packages/core/picker-input/index.ts"], + "@umbraco-cms/backoffice/picker-data-source": ["./src/packages/core/picker-data-source/index.ts"], "@umbraco-cms/backoffice/picker": ["./src/packages/core/picker/index.ts"], "@umbraco-cms/backoffice/property-action": ["./src/packages/core/property-action/index.ts"], + "@umbraco-cms/backoffice/property-editor-data-source": [ + "./src/packages/core/property-editor-data-source/index.ts" + ], "@umbraco-cms/backoffice/property-editor": ["./src/packages/core/property-editor/index.ts"], "@umbraco-cms/backoffice/property-type": ["./src/packages/content/property-type/index.ts"], "@umbraco-cms/backoffice/property": ["./src/packages/core/property/index.ts"], @@ -124,7 +129,6 @@ DON'T EDIT THIS FILE DIRECTLY. It is generated by /devops/tsconfig/index.js "@umbraco-cms/backoffice/router": ["./src/packages/core/router/index.ts"], "@umbraco-cms/backoffice/rte": ["./src/packages/rte/index.ts"], "@umbraco-cms/backoffice/script": ["./src/packages/templating/scripts/index.ts"], - "@umbraco-cms/backoffice/search": ["./src/packages/search/index.ts"], "@umbraco-cms/backoffice/section": ["./src/packages/core/section/index.ts"], "@umbraco-cms/backoffice/segment": ["./src/packages/segment/index.ts"], "@umbraco-cms/backoffice/server-file-system": ["./src/packages/core/server-file-system/index.ts"],