diff --git a/src/Umbraco.Web.UI.Client/.storybook/preview.js b/src/Umbraco.Web.UI.Client/.storybook/preview.js index 822d951f3a..e55d5c7558 100644 --- a/src/Umbraco.Web.UI.Client/.storybook/preview.js +++ b/src/Umbraco.Web.UI.Client/.storybook/preview.js @@ -13,7 +13,7 @@ import { UmbDocumentTypeStore } from '../src/backoffice/documents/document-types import { UmbDocumentStore } from '../src/backoffice/documents/documents/repository/document.store.ts'; import { UmbDocumentTreeStore } from '../src/backoffice/documents/documents/repository/document.tree.store.ts'; -import customElementManifests from '../custom-elements.json'; +import customElementManifests from '../dist/libs/custom-elements.json'; import { UmbIconStore } from '../src/core/stores/icon/icon.store'; import { onUnhandledRequest } from '../src/core/mocks/browser'; import { handlers } from '../src/core/mocks/browser-handlers'; diff --git a/src/Umbraco.Web.UI.Client/libs/entity-action/actions/delete-folder/delete-folder.action.ts b/src/Umbraco.Web.UI.Client/libs/entity-action/actions/delete-folder/delete-folder.action.ts index 9a2adadb0b..594d0be5f4 100644 --- a/src/Umbraco.Web.UI.Client/libs/entity-action/actions/delete-folder/delete-folder.action.ts +++ b/src/Umbraco.Web.UI.Client/libs/entity-action/actions/delete-folder/delete-folder.action.ts @@ -2,9 +2,10 @@ import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal'; +import { UmbFolderRepository, UmbItemRepository } from '@umbraco-cms/backoffice/repository'; export class UmbDeleteFolderEntityAction< - T extends { deleteFolder(unique: string): Promise; requestTreeItems(uniques: Array): any } + T extends UmbItemRepository & UmbFolderRepository > extends UmbEntityActionBase { #modalContext?: UmbModalContext; @@ -19,7 +20,7 @@ export class UmbDeleteFolderEntityAction< async execute() { if (!this.repository || !this.#modalContext) return; - const { data } = await this.repository.requestTreeItems([this.unique]); + const { data } = await this.repository.requestItems([this.unique]); if (data) { const item = data[0]; diff --git a/src/Umbraco.Web.UI.Client/libs/entity-action/actions/delete/delete.action.ts b/src/Umbraco.Web.UI.Client/libs/entity-action/actions/delete/delete.action.ts index d290a1f912..c53a418502 100644 --- a/src/Umbraco.Web.UI.Client/libs/entity-action/actions/delete/delete.action.ts +++ b/src/Umbraco.Web.UI.Client/libs/entity-action/actions/delete/delete.action.ts @@ -2,9 +2,10 @@ import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal'; +import { UmbDetailRepository, UmbItemRepository } from '@umbraco-cms/backoffice/repository'; export class UmbDeleteEntityAction< - T extends { delete(unique: string): Promise; requestTreeItems(uniques: Array): any } + T extends UmbDetailRepository & UmbItemRepository > extends UmbEntityActionBase { #modalContext?: UmbModalContext; @@ -19,7 +20,7 @@ export class UmbDeleteEntityAction< async execute() { if (!this.repository || !this.#modalContext) return; - const { data } = await this.repository.requestTreeItems([this.unique]); + const { data } = await this.repository.requestItems([this.unique]); if (data) { const item = data[0]; diff --git a/src/Umbraco.Web.UI.Client/libs/entity-action/actions/move/move.action.ts b/src/Umbraco.Web.UI.Client/libs/entity-action/actions/move/move.action.ts index 2744029e82..dd4a7f2905 100644 --- a/src/Umbraco.Web.UI.Client/libs/entity-action/actions/move/move.action.ts +++ b/src/Umbraco.Web.UI.Client/libs/entity-action/actions/move/move.action.ts @@ -1,6 +1,8 @@ import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; +// TODO: investigate what we need to finish the generic move action. We would need to open a picker, which requires a modal token, +// maybe we can use kinds to make a specific manifest to the move action. export class UmbMoveEntityAction }> extends UmbEntityActionBase { constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) { super(host, repositoryAlias, unique); diff --git a/src/Umbraco.Web.UI.Client/libs/entity-action/actions/trash/trash.action.ts b/src/Umbraco.Web.UI.Client/libs/entity-action/actions/trash/trash.action.ts index 2b86b4d56c..1ed2a69beb 100644 --- a/src/Umbraco.Web.UI.Client/libs/entity-action/actions/trash/trash.action.ts +++ b/src/Umbraco.Web.UI.Client/libs/entity-action/actions/trash/trash.action.ts @@ -2,9 +2,10 @@ import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal'; +import { UmbItemRepository } from '@umbraco-cms/backoffice/repository'; export class UmbTrashEntityAction< - T extends { trash(unique: Array): Promise; requestTreeItems(uniques: Array): any } + T extends UmbItemRepository & { trash(unique: Array): Promise } > extends UmbEntityActionBase { #modalContext?: UmbModalContext; @@ -19,7 +20,7 @@ export class UmbTrashEntityAction< async execute() { if (!this.repository) return; - const { data } = await this.repository.requestTreeItems([this.unique]); + const { data } = await this.repository.requestItems([this.unique]); if (data) { const item = data[0]; diff --git a/src/Umbraco.Web.UI.Client/libs/extensions-registry/collection-view.models.ts b/src/Umbraco.Web.UI.Client/libs/extensions-registry/collection-view.models.ts index 2eb599c1b8..823ca3bc21 100644 --- a/src/Umbraco.Web.UI.Client/libs/extensions-registry/collection-view.models.ts +++ b/src/Umbraco.Web.UI.Client/libs/extensions-registry/collection-view.models.ts @@ -6,8 +6,24 @@ export interface ManifestCollectionView extends ManifestElement, ManifestWithCon } export interface MetaCollectionView { + /** + * The friendly name of the collection view + */ label: string; + + /** + * An icon to represent the collection view + * + * @examples [ + * "umb:box", + * "umb:grid" + * ] + */ icon: string; + + /** + * The URL pathname for this collection view that can be deep linked to by sharing the url + */ pathName: string; } diff --git a/src/Umbraco.Web.UI.Client/libs/extensions-registry/dashboard.models.ts b/src/Umbraco.Web.UI.Client/libs/extensions-registry/dashboard.models.ts index cf40a2748c..033e554623 100644 --- a/src/Umbraco.Web.UI.Client/libs/extensions-registry/dashboard.models.ts +++ b/src/Umbraco.Web.UI.Client/libs/extensions-registry/dashboard.models.ts @@ -6,10 +6,34 @@ export interface ManifestDashboard extends ManifestElement, ManifestWithConditio } export interface MetaDashboard { + /** + * This is the URL path for the dashboard which is used for navigating or deep linking directly to the dashboard + * https://yoursite.com/section/settings/dashboard/my-dashboard-path + * + * @example my-dashboard-path + * @examples [ + * "my-dashboard-path" + * ] + */ pathname: string; + + /** + * The displayed name (label) for the tab of the dashboard + */ label?: string; } export interface ConditionsDashboard { + /** + * An array of section aliases that the dashboard should be available in + * + * @uniqueItems true + * @minItems 1 + * @items.examples [ + * "Umb.Section.Content", + * "Umb.Section.Settings" + * ] + * + */ sections: string[]; } diff --git a/src/Umbraco.Web.UI.Client/libs/extensions-registry/entity-action.models.ts b/src/Umbraco.Web.UI.Client/libs/extensions-registry/entity-action.models.ts index dbea8f833f..4816f084fd 100644 --- a/src/Umbraco.Web.UI.Client/libs/extensions-registry/entity-action.models.ts +++ b/src/Umbraco.Web.UI.Client/libs/extensions-registry/entity-action.models.ts @@ -1,5 +1,9 @@ import type { ManifestElement } from './models'; +/** + * An action to perform on an entity + * For example for content you may wish to create a new document etc + */ export interface ManifestEntityAction extends ManifestElement { type: 'entityAction'; meta: MetaEntityAction; @@ -7,9 +11,38 @@ export interface ManifestEntityAction extends ManifestElement { } export interface MetaEntityAction { + /** + * An icon to represent the action to be performed + * + * @examples [ + * "umb:box", + * "umb:grid" + * ] + */ icon?: string; + + /** + * The friendly name of the action to perform + * + * @examples [ + * "Create", + * "Create Content Template" + * ] + */ label: string; + + /** + * @TJS-ignore + */ api: any; // create interface + + /** + * The alias for the repsoitory of the entity type this action is for + * such as 'Umb.Repository.Documents' + * @examples [ + * "Umb.Repository.Documents" + * ] + */ repositoryAlias: string; } diff --git a/src/Umbraco.Web.UI.Client/libs/extensions-registry/entity-bulk-action.models.ts b/src/Umbraco.Web.UI.Client/libs/extensions-registry/entity-bulk-action.models.ts index b7bd23a997..5564bb5b5d 100644 --- a/src/Umbraco.Web.UI.Client/libs/extensions-registry/entity-bulk-action.models.ts +++ b/src/Umbraco.Web.UI.Client/libs/extensions-registry/entity-bulk-action.models.ts @@ -1,13 +1,33 @@ import type { ManifestElement, ManifestWithConditions } from './models'; +/** + * An action to perform on multiple entities + * For example for content you may wish to move one or more documents in bulk + */ export interface ManifestEntityBulkAction extends ManifestElement, ManifestWithConditions { type: 'entityBulkAction'; meta: MetaEntityBulkAction; } export interface MetaEntityBulkAction { + /** + * A friendly label for the action + */ label: string; + + /** + * @TJS-ignore + */ api: any; // create interface + + /** + * The alias for the repsoitory of the entity type this action is for + * such as 'Umb.Repository.Documents' + * + * @examples [ + * "Umb.Repository.Documents" + * ] + */ repositoryAlias: string; } diff --git a/src/Umbraco.Web.UI.Client/libs/extensions-registry/header-app.models.ts b/src/Umbraco.Web.UI.Client/libs/extensions-registry/header-app.models.ts index 4834a8d9bf..e6e990c591 100644 --- a/src/Umbraco.Web.UI.Client/libs/extensions-registry/header-app.models.ts +++ b/src/Umbraco.Web.UI.Client/libs/extensions-registry/header-app.models.ts @@ -1,10 +1,15 @@ import type { ManifestElement } from './models'; +/** + * Header apps are displayed in the top right corner of the backoffice + * The two provided header apps are the search and the user menu + */ export interface ManifestHeaderApp extends ManifestElement { type: 'headerApp'; //meta: MetaHeaderApp; } +// TODO: Warren these don't seem to be used anywhere export interface MetaHeaderApp { pathname: string; label: string; diff --git a/src/Umbraco.Web.UI.Client/libs/extensions-registry/models.ts b/src/Umbraco.Web.UI.Client/libs/extensions-registry/models.ts index 387abf6331..4434d79ab4 100644 --- a/src/Umbraco.Web.UI.Client/libs/extensions-registry/models.ts +++ b/src/Umbraco.Web.UI.Client/libs/extensions-registry/models.ts @@ -24,7 +24,7 @@ import type { ManifestWorkspaceView } from './workspace-view.models'; import type { ManifestWorkspaceViewCollection } from './workspace-view-collection.models'; import type { ManifestRepository } from './repository.models'; import type { ManifestModal } from './modal.models'; -import type { ManifestStore, ManifestTreeStore } from './store.models'; +import type { ManifestStore, ManifestTreeStore, ManifestItemStore } from './store.models'; import type { ClassConstructor } from '@umbraco-cms/backoffice/models'; export * from './collection-view.models'; @@ -89,6 +89,7 @@ export type ManifestTypes = | ManifestModal | ManifestStore | ManifestTreeStore + | ManifestItemStore | ManifestBase; export type ManifestStandardTypes = ManifestTypes['type']; @@ -101,10 +102,31 @@ export type SpecificManifestTypeOrManifestBase { + /** + * Set the conditions for when the extension should be loaded + */ conditions: ConditionsType; } export interface ManifestWithLoader extends ManifestBase { + /** + * @TJS-ignore + */ loader?: () => Promise; } +/** + * The type of extension such as dashboard etc... + */ export interface ManifestClass extends ManifestWithLoader { //type: ManifestStandardTypes; + + /** + * The file location of the javascript file to load + * @TJS-required + */ js?: string; + + /** + * @TJS-ignore + */ className?: string; + + /** + * @TJS-ignore + */ class?: ClassConstructor; //loader?: () => Promise; } @@ -138,10 +182,26 @@ export interface ManifestClassWithClassConstructor extends ManifestClass { export interface ManifestElement extends ManifestWithLoader { //type: ManifestStandardTypes; + + /** + * The file location of the javascript file to load + * + * @TJS-require + */ js?: string; + + /** + * The HTML web component name to use such as 'my-dashboard' + * Note it is NOT but just the name + */ elementName?: string; + //loader?: () => Promise; - meta?: any; + + /** + * This contains properties specific to the type of extension + */ + meta?: unknown; } export interface ManifestWithView extends ManifestElement { @@ -155,22 +215,29 @@ export interface MetaManifestWithView { } export interface ManifestElementWithElementName extends ManifestElement { + /** + * The HTML web component name to use such as 'my-dashboard' + * Note it is NOT but just the name + */ elementName: string; } -// TODO: Remove Custom as it has no purpose currently: -/* -export interface ManifestCustom extends ManifestBase { - type: 'custom'; - meta?: unknown; -} -*/ - export interface ManifestWithMeta extends ManifestBase { + /** + * This contains properties specific to the type of extension + */ meta: unknown; } +/** + * This type of extension gives full control and will simply load the specified JS file + * You could have custom logic to decide which extensions to load/register by using extensionRegistry + */ export interface ManifestEntrypoint extends ManifestBase { type: 'entrypoint'; + + /** + * The file location of the javascript file to load in the backoffice + */ js: string; } diff --git a/src/Umbraco.Web.UI.Client/libs/extensions-registry/store.models.ts b/src/Umbraco.Web.UI.Client/libs/extensions-registry/store.models.ts index 18e8c10c17..dbca5d6956 100644 --- a/src/Umbraco.Web.UI.Client/libs/extensions-registry/store.models.ts +++ b/src/Umbraco.Web.UI.Client/libs/extensions-registry/store.models.ts @@ -1,5 +1,5 @@ import type { ManifestClass } from './models'; -import { UmbStoreBase, UmbTreeStore } from '@umbraco-cms/backoffice/store'; +import { UmbItemStore, UmbStoreBase, UmbTreeStore } from '@umbraco-cms/backoffice/store'; export interface ManifestStore extends ManifestClass { type: 'store'; @@ -8,3 +8,7 @@ export interface ManifestStore extends ManifestClass { export interface ManifestTreeStore extends ManifestClass { type: 'treeStore'; } + +export interface ManifestItemStore extends ManifestClass { + type: 'itemStore'; +} diff --git a/src/Umbraco.Web.UI.Client/libs/extensions-registry/theme.models.ts b/src/Umbraco.Web.UI.Client/libs/extensions-registry/theme.models.ts index 81b84ee0bb..0e18a0164a 100644 --- a/src/Umbraco.Web.UI.Client/libs/extensions-registry/theme.models.ts +++ b/src/Umbraco.Web.UI.Client/libs/extensions-registry/theme.models.ts @@ -1,7 +1,17 @@ import type { ManifestWithLoader } from './models'; // TODO: make or find type for JS Module with default export: Would be nice to support css file directly. + +/** + * Theme manifest for styling the backoffice of Umbraco such as dark, high contrast etc + */ export interface ManifestTheme extends ManifestWithLoader { type: 'theme'; + + /** + * File location of the CSS file of the theme + * + * @examples ["themes/dark.theme.css"] + */ css?: string; } diff --git a/src/Umbraco.Web.UI.Client/libs/extensions-registry/umbraco-package.ts b/src/Umbraco.Web.UI.Client/libs/extensions-registry/umbraco-package.ts new file mode 100644 index 0000000000..c594bada93 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/libs/extensions-registry/umbraco-package.ts @@ -0,0 +1,28 @@ +import type { ManifestTypes } from './models'; + +/** + * Umbraco package manifest JSON + */ +export class UmbracoPackage { + /** + * @title The name of the Umbraco package + */ + name?: string; + + /** + * @title The version of the Umbraco package in the style of semver + * @examples ["0.1.0"] + */ + version?: string; + + /** + * @title Decides if the package sends telemetry data for collection + * @default true + */ + allowTelemetry?: boolean; + + /** + * @title An array of Umbraco package manifest types that will be installed + */ + extensions?: ManifestTypes[]; +} diff --git a/src/Umbraco.Web.UI.Client/libs/extensions-registry/workspace-action.models.ts b/src/Umbraco.Web.UI.Client/libs/extensions-registry/workspace-action.models.ts index d55d4f4f89..2cb7595388 100644 --- a/src/Umbraco.Web.UI.Client/libs/extensions-registry/workspace-action.models.ts +++ b/src/Umbraco.Web.UI.Client/libs/extensions-registry/workspace-action.models.ts @@ -1,6 +1,5 @@ import type { InterfaceColor, InterfaceLook } from '@umbraco-ui/uui-base/lib/types/index'; import type { ManifestElement } from './models'; -import { UmbWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; import type { ClassConstructor } from '@umbraco-cms/backoffice/models'; export interface ManifestWorkspaceAction extends ManifestElement { @@ -13,7 +12,7 @@ export interface MetaWorkspaceAction { label?: string; //TODO: Use or implement additional label-key look?: InterfaceLook; color?: InterfaceColor; - api: ClassConstructor; + api: ClassConstructor; } export interface ConditionsWorkspaceAction { diff --git a/src/Umbraco.Web.UI.Client/libs/modal/token/data-type-picker-modal.token.ts b/src/Umbraco.Web.UI.Client/libs/modal/token/data-type-picker-modal.token.ts new file mode 100644 index 0000000000..51d90eb871 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/libs/modal/token/data-type-picker-modal.token.ts @@ -0,0 +1,18 @@ +import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; + +export interface UmbDataTypePickerModalData { + selection?: Array; + multiple?: boolean; +} + +export interface UmbDataTypePickerModalResult { + selection: Array; +} + +export const UMB_DATA_TYPE_PICKER_MODAL = new UmbModalToken( + 'Umb.Modal.DataTypePicker', + { + type: 'sidebar', + size: 'small', + } +); diff --git a/src/Umbraco.Web.UI.Client/libs/modal/token/index.ts b/src/Umbraco.Web.UI.Client/libs/modal/token/index.ts index 7fd02d418b..284cdc1e6a 100644 --- a/src/Umbraco.Web.UI.Client/libs/modal/token/index.ts +++ b/src/Umbraco.Web.UI.Client/libs/modal/token/index.ts @@ -26,3 +26,4 @@ export * from './template-picker-modal.token'; export * from './user-group-picker-modal.token'; export * from './user-picker-modal.token'; export * from './folder-modal.token'; +export * from './data-type-picker-modal.token'; diff --git a/src/Umbraco.Web.UI.Client/libs/picker-input/index.ts b/src/Umbraco.Web.UI.Client/libs/picker-input/index.ts new file mode 100644 index 0000000000..74b8745179 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/libs/picker-input/index.ts @@ -0,0 +1 @@ +export * from './picker-input.context'; diff --git a/src/Umbraco.Web.UI.Client/libs/picker-input/picker-input.context.ts b/src/Umbraco.Web.UI.Client/libs/picker-input/picker-input.context.ts new file mode 100644 index 0000000000..3c8bae91c2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/libs/picker-input/picker-input.context.ts @@ -0,0 +1,136 @@ +import { UmbItemRepository } from '@umbraco-cms/backoffice/repository'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; +import { UmbArrayState, UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; +import { createExtensionClass, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extensions-api'; +import { + UMB_CONFIRM_MODAL, + UMB_MODAL_CONTEXT_TOKEN, + UmbModalContext, + UmbModalToken, +} from '@umbraco-cms/backoffice/modal'; +import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; +import { ItemResponseModelBaseModel } from '@umbraco-cms/backoffice/backend-api'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/events'; + +export class UmbPickerInputContext { + host: UmbControllerHostElement; + modalAlias: string | UmbModalToken; + repository?: UmbItemRepository; + #getUnique: (entry: ItemType) => string | undefined; + + public modalContext?: UmbModalContext; + + #selection = new UmbArrayState([]); + selection = this.#selection.asObservable(); + + #selectedItems = new UmbArrayState([]); + selectedItems = this.#selectedItems.asObservable(); + + #selectedItemsObserver?: UmbObserverController; + + max = Infinity; + min = 0; + + /* TODO: find a better way to have a getUniqueMethod. If we want to support trees/items of different types, + then it need to be bound to the type and can't be a generic method we pass in. */ + constructor( + host: UmbControllerHostElement, + repositoryAlias: string, + modalAlias: string | UmbModalToken, + getUniqueMethod?: (entry: ItemType) => string | undefined + ) { + this.host = host; + this.modalAlias = modalAlias; + this.#getUnique = getUniqueMethod || ((entry) => entry.id || ''); + + // TODO: unsure a method can't be called before everything is initialized + new UmbObserverController( + this.host, + + // TODO: this code is reused in multiple places, so it should be extracted to a function + umbExtensionsRegistry.getByTypeAndAlias('repository', repositoryAlias), + async (repositoryManifest) => { + if (!repositoryManifest) return; + + try { + const result = await createExtensionClass>(repositoryManifest, [this.host]); + this.repository = result; + } catch (error) { + throw new Error('Could not create repository with alias: ' + repositoryAlias + ''); + } + } + ); + + new UmbContextConsumerController(this.host, UMB_MODAL_CONTEXT_TOKEN, (instance) => { + this.modalContext = instance; + }); + } + + getSelection() { + return this.#selection.value; + } + + setSelection(selection: string[]) { + this.#selection.next(selection); + } + + // TODO: revisit this method. How do we best pass picker data? + // If modalAlias is a ModalToken, then via TS, we should get the correct type for pickerData. Otherwise fallback to unknown. + openPicker(pickerData?: any) { + if (!this.modalContext) throw new Error('Modal context is not initialized'); + + const modalHandler = this.modalContext.open(this.modalAlias, { + multiple: this.max === 1 ? false : true, + selection: [...this.getSelection()], + ...pickerData, + }); + + modalHandler?.onSubmit().then(({ selection }: any) => { + this.setSelection(selection); + this.host.dispatchEvent(new UmbChangeEvent()); + // TODO: we only want to request items that are not already in the selectedItems array + this.#requestItems(); + }); + } + + async requestRemoveItem(unique: string) { + if (!this.repository) throw new Error('Repository is not initialized'); + + // TODO: id won't always be available on the model, so we need to get the unique property from somewhere. Maybe the repository? + const item = this.#selectedItems.value.find((item) => this.#getUnique(item) === unique); + if (!item) throw new Error('Could not find item with unique: ' + unique); + + const modalHandler = this.modalContext?.open(UMB_CONFIRM_MODAL, { + color: 'danger', + headline: `Remove ${item.name}?`, + content: 'Are you sure you want to remove this item', + confirmLabel: 'Remove', + }); + + await modalHandler?.onSubmit(); + this.#removeItem(unique); + } + + async #requestItems() { + if (!this.repository) throw new Error('Repository is not initialized'); + if (this.#selectedItemsObserver) this.#selectedItemsObserver.destroy(); + + const { asObservable } = await this.repository.requestItems(this.getSelection()); + + if (asObservable) { + this.#selectedItemsObserver = new UmbObserverController(this.host, asObservable(), (data) => + this.#selectedItems.next(data) + ); + } + } + + #removeItem(unique: string) { + const newSelection = this.getSelection().filter((value) => value !== unique); + this.setSelection(newSelection); + + // remove items items from selectedItems array + // TODO: id won't always be available on the model, so we need to get the unique property from somewhere. Maybe the repository? + const newSelectedItems = this.#selectedItems.value.filter((item) => this.#getUnique(item) !== unique); + this.#selectedItems.next(newSelectedItems); + } +} diff --git a/src/Umbraco.Web.UI.Client/libs/repository/data-source/data-source-response.interface.ts b/src/Umbraco.Web.UI.Client/libs/repository/data-source/data-source-response.interface.ts index a3ee6e90f0..2360da75a8 100644 --- a/src/Umbraco.Web.UI.Client/libs/repository/data-source/data-source-response.interface.ts +++ b/src/Umbraco.Web.UI.Client/libs/repository/data-source/data-source-response.interface.ts @@ -1,6 +1,9 @@ import type { ProblemDetailsModel } from '@umbraco-cms/backoffice/backend-api'; -export interface DataSourceResponse { +export interface DataSourceResponse extends UmbDataSourceErrorResponse { data?: T; +} + +export interface UmbDataSourceErrorResponse { error?: ProblemDetailsModel; } diff --git a/src/Umbraco.Web.UI.Client/libs/repository/data-source/index.ts b/src/Umbraco.Web.UI.Client/libs/repository/data-source/index.ts index 905a6849fe..3851c96686 100644 --- a/src/Umbraco.Web.UI.Client/libs/repository/data-source/index.ts +++ b/src/Umbraco.Web.UI.Client/libs/repository/data-source/index.ts @@ -2,3 +2,5 @@ export * from './data-source-response.interface'; export * from './data-source.interface'; export * from './folder-data-source.interface'; export * from './tree-data-source.interface'; +export * from './item-data-source.interface'; +export * from './move-data-source.interface'; diff --git a/src/Umbraco.Web.UI.Client/libs/repository/data-source/item-data-source.interface.ts b/src/Umbraco.Web.UI.Client/libs/repository/data-source/item-data-source.interface.ts new file mode 100644 index 0000000000..88fb741ee3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/libs/repository/data-source/item-data-source.interface.ts @@ -0,0 +1,5 @@ +import type { DataSourceResponse } from '@umbraco-cms/backoffice/repository'; + +export interface UmbItemDataSource { + getItems(unique: Array): Promise>>; +} diff --git a/src/Umbraco.Web.UI.Client/libs/repository/data-source/move-data-source.interface.ts b/src/Umbraco.Web.UI.Client/libs/repository/data-source/move-data-source.interface.ts new file mode 100644 index 0000000000..c0639e31ce --- /dev/null +++ b/src/Umbraco.Web.UI.Client/libs/repository/data-source/move-data-source.interface.ts @@ -0,0 +1,5 @@ +import type { UmbDataSourceErrorResponse } from '@umbraco-cms/backoffice/repository'; + +export interface UmbMoveDataSource { + move(unique: string, targetUnique: string): Promise; +} diff --git a/src/Umbraco.Web.UI.Client/libs/repository/data-source/tree-data-source.interface.ts b/src/Umbraco.Web.UI.Client/libs/repository/data-source/tree-data-source.interface.ts index ae57ef57af..52f623e13b 100644 --- a/src/Umbraco.Web.UI.Client/libs/repository/data-source/tree-data-source.interface.ts +++ b/src/Umbraco.Web.UI.Client/libs/repository/data-source/tree-data-source.interface.ts @@ -3,5 +3,7 @@ import type { DataSourceResponse } from '@umbraco-cms/backoffice/repository'; export interface UmbTreeDataSource { getRootItems(): Promise>; getChildrenOf(parentUnique: string): Promise>; + + // TODO: remove this when all repositories are migrated to the new items interface getItems(unique: Array): Promise>>; } diff --git a/src/Umbraco.Web.UI.Client/libs/repository/index.ts b/src/Umbraco.Web.UI.Client/libs/repository/index.ts index 9dcba782ba..0c73ef0870 100644 --- a/src/Umbraco.Web.UI.Client/libs/repository/index.ts +++ b/src/Umbraco.Web.UI.Client/libs/repository/index.ts @@ -2,3 +2,5 @@ export * from './data-source'; export * from './detail-repository.interface'; export * from './tree-repository.interface'; export * from './folder-repository.interface'; +export * from './item-repository.interface'; +export * from './move-repository.interface'; diff --git a/src/Umbraco.Web.UI.Client/libs/repository/item-repository.interface.ts b/src/Umbraco.Web.UI.Client/libs/repository/item-repository.interface.ts new file mode 100644 index 0000000000..3f92cb70e8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/libs/repository/item-repository.interface.ts @@ -0,0 +1,11 @@ +import type { Observable } from 'rxjs'; +import { ItemResponseModelBaseModel, ProblemDetailsModel } from '@umbraco-cms/backoffice/backend-api'; + +export interface UmbItemRepository { + requestItems: (uniques: string[]) => Promise<{ + data?: Array | undefined; + error?: ProblemDetailsModel | undefined; + asObservable?: () => Observable>; + }>; + items: (uniques: string[]) => Promise>>; +} diff --git a/src/Umbraco.Web.UI.Client/libs/repository/move-repository.interface.ts b/src/Umbraco.Web.UI.Client/libs/repository/move-repository.interface.ts new file mode 100644 index 0000000000..f8e5889300 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/libs/repository/move-repository.interface.ts @@ -0,0 +1,5 @@ +import { UmbRepositoryErrorResponse } from './detail-repository.interface'; + +export interface UmbMoveRepository { + move(unique: string, targetUnique: string): Promise; +} diff --git a/src/Umbraco.Web.UI.Client/libs/repository/tree-repository.interface.ts b/src/Umbraco.Web.UI.Client/libs/repository/tree-repository.interface.ts index 4ef19c3920..17ddc1a2d4 100644 --- a/src/Umbraco.Web.UI.Client/libs/repository/tree-repository.interface.ts +++ b/src/Umbraco.Web.UI.Client/libs/repository/tree-repository.interface.ts @@ -17,7 +17,9 @@ export interface UmbTreeRepository Observable; }>; - requestTreeItems: (uniques: string[]) => Promise<{ + + // TODO: remove this when all repositories are migrated to the new interface items interface + requestItemsLegacy?: (uniques: string[]) => Promise<{ data: Array | undefined; error: ProblemDetailsModel | undefined; asObservable?: () => Observable; @@ -25,5 +27,7 @@ export interface UmbTreeRepository Promise>; treeItemsOf: (parentUnique: string | null) => Promise>; - treeItems: (uniques: string[]) => Promise>; + + // TODO: remove this when all repositories are migrated to the new items interface + itemsLegacy?: (uniques: string[]) => Promise>; } diff --git a/src/Umbraco.Web.UI.Client/libs/router/index.ts b/src/Umbraco.Web.UI.Client/libs/router/index.ts index 3cfb67ff4a..7bfd8cff7a 100644 --- a/src/Umbraco.Web.UI.Client/libs/router/index.ts +++ b/src/Umbraco.Web.UI.Client/libs/router/index.ts @@ -1,4 +1,4 @@ -export type * from 'router-slot/model'; +export * from 'router-slot/model'; export * from 'router-slot/util'; export * from './route-location.interface'; export * from './route.context'; diff --git a/src/Umbraco.Web.UI.Client/libs/store/entity-tree-store.ts b/src/Umbraco.Web.UI.Client/libs/store/entity-tree-store.ts index dfa6815a04..fb032ac309 100644 --- a/src/Umbraco.Web.UI.Client/libs/store/entity-tree-store.ts +++ b/src/Umbraco.Web.UI.Client/libs/store/entity-tree-store.ts @@ -1,49 +1,27 @@ import { EntityTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; -import { UmbArrayState, partialUpdateFrozenArray } from '@umbraco-cms/backoffice/observable-api'; +import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; import { UmbStoreBase, UmbTreeStore } from '@umbraco-cms/backoffice/store'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; /** * @export * @class UmbEntityTreeStore * @extends {UmbStoreBase} - * @description - General Tree Data Store + * @description - Entity Tree Store */ -export class UmbEntityTreeStore extends UmbStoreBase implements UmbTreeStore { - #data = new UmbArrayState([], (x) => x.id); - - /** - * Appends items to the store - * @param {Array} items - * @memberof UmbEntityTreeStore - */ - appendItems(items: Array) { - this.#data.append(items); - } - - /** - * Updates an item in the store - * @param {string} id - * @param {Partial} data - * @memberof UmbEntityTreeStore - */ - updateItem(id: string, data: Partial) { - this.#data.next(partialUpdateFrozenArray(this.#data.getValue(), data, (entry) => entry.id === id)); - } - - /** - * Removes an item from the store - * @param {string} id - * @memberof UmbEntityTreeStore - */ - removeItem(id: string) { - this.#data.removeOne(id); +export class UmbEntityTreeStore + extends UmbStoreBase + implements UmbTreeStore +{ + constructor(host: UmbControllerHostElement, storeAlias: string) { + super(host, storeAlias, new UmbArrayState([], (x) => x.id)); } /** * An observable to observe the root items * @memberof UmbEntityTreeStore */ - rootItems = this.#data.getObservablePart((items) => items.filter((item) => item.parentId === null)); + rootItems = this._data.getObservablePart((items) => items.filter((item) => item.parentId === null)); /** * Returns an observable to observe the children of a given parent @@ -52,7 +30,7 @@ export class UmbEntityTreeStore extends UmbStoreBase implements UmbTreeStore items.filter((item) => item.parentId === parentId)); + return this._data.getObservablePart((items) => items.filter((item) => item.parentId === parentId)); } /** @@ -62,6 +40,6 @@ export class UmbEntityTreeStore extends UmbStoreBase implements UmbTreeStore) { - return this.#data.getObservablePart((items) => items.filter((item) => ids.includes(item.id ?? ''))); + return this._data.getObservablePart((items) => items.filter((item) => ids.includes(item.id ?? ''))); } } diff --git a/src/Umbraco.Web.UI.Client/libs/store/file-system-tree.store.ts b/src/Umbraco.Web.UI.Client/libs/store/file-system-tree.store.ts index 3bb024511a..68c9352ce5 100644 --- a/src/Umbraco.Web.UI.Client/libs/store/file-system-tree.store.ts +++ b/src/Umbraco.Web.UI.Client/libs/store/file-system-tree.store.ts @@ -1,49 +1,27 @@ import { FileSystemTreeItemPresentationModel } from '@umbraco-cms/backoffice/backend-api'; -import { UmbArrayState, partialUpdateFrozenArray } from '@umbraco-cms/backoffice/observable-api'; +import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; import { UmbStoreBase, UmbTreeStore } from '@umbraco-cms/backoffice/store'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; /** * @export * @class UmbFileSystemTreeStore * @extends {UmbStoreBase} - * @description - General Tree Data Store + * @description - File System Tree Store */ -export class UmbFileSystemTreeStore extends UmbStoreBase implements UmbTreeStore { - #data = new UmbArrayState([], (x) => x.path); - - /** - * Appends items to the store - * @param {Array} items - * @memberof UmbFileSystemTreeStore - */ - appendItems(items: Array) { - this.#data.append(items); - } - - /** - * Updates an item in the store - * @param {string} path - * @param {Partial} data - * @memberof UmbFileSystemTreeStore - */ - updateItem(path: string, data: Partial) { - this.#data.appendOne(data); - } - - /** - * Removes an item from the store - * @param {string} path - * @memberof UmbFileSystemTreeStore - */ - removeItem(path: string) { - this.#data.removeOne(path); +export class UmbFileSystemTreeStore + extends UmbStoreBase + implements UmbTreeStore +{ + constructor(host: UmbControllerHostElement, storeAlias: string) { + super(host, storeAlias, new UmbArrayState([], (x) => x.path)); } /** * An observable to observe the root items * @memberof UmbFileSystemTreeStore */ - rootItems = this.#data.getObservablePart((items) => items.filter((item) => item.path?.includes('/') === false)); + rootItems = this._data.getObservablePart((items) => items.filter((item) => item.path?.includes('/') === false)); /** * Returns an observable to observe the children of a given parent @@ -52,7 +30,7 @@ export class UmbFileSystemTreeStore extends UmbStoreBase implements UmbTreeStore * @memberof UmbFileSystemTreeStore */ childrenOf(parentPath: string | null) { - return this.#data.getObservablePart((items) => items.filter((item) => item.path?.startsWith(parentPath + '/'))); + return this._data.getObservablePart((items) => items.filter((item) => item.path?.startsWith(parentPath + '/'))); } /** @@ -62,6 +40,6 @@ export class UmbFileSystemTreeStore extends UmbStoreBase implements UmbTreeStore * @memberof UmbFileSystemTreeStore */ items(paths: Array) { - return this.#data.getObservablePart((items) => items.filter((item) => paths.includes(item.path ?? ''))); + return this._data.getObservablePart((items) => items.filter((item) => paths.includes(item.path ?? ''))); } } diff --git a/src/Umbraco.Web.UI.Client/libs/store/index.ts b/src/Umbraco.Web.UI.Client/libs/store/index.ts index f9152d3ebd..a72d304eca 100644 --- a/src/Umbraco.Web.UI.Client/libs/store/index.ts +++ b/src/Umbraco.Web.UI.Client/libs/store/index.ts @@ -3,3 +3,4 @@ export * from './store-base'; export * from './entity-tree-store'; export * from './file-system-tree.store'; export * from './tree-store.interface'; +export * from './item-store.interface'; diff --git a/src/Umbraco.Web.UI.Client/libs/store/item-store.interface.ts b/src/Umbraco.Web.UI.Client/libs/store/item-store.interface.ts new file mode 100644 index 0000000000..a68e83b5c1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/libs/store/item-store.interface.ts @@ -0,0 +1,7 @@ +import type { Observable } from 'rxjs'; +import { ItemResponseModelBaseModel } from '../backend-api'; +import { UmbStore } from './store.interface'; + +export interface UmbItemStore extends UmbStore { + items: (uniques: Array) => Observable>; +} diff --git a/src/Umbraco.Web.UI.Client/libs/store/store-base.ts b/src/Umbraco.Web.UI.Client/libs/store/store-base.ts index 4021307cd6..dcd9244391 100644 --- a/src/Umbraco.Web.UI.Client/libs/store/store-base.ts +++ b/src/Umbraco.Web.UI.Client/libs/store/store-base.ts @@ -1,9 +1,48 @@ +import { UmbStore } from './store.interface'; import { UmbContextProviderController } from '@umbraco-cms/backoffice/context-api'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; +import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; // TODO: Make a Store interface? -export class UmbStoreBase { - constructor(protected _host: UmbControllerHostElement, public readonly storeAlias: string) { +export class UmbStoreBase implements UmbStore { + protected _host: UmbControllerHostElement; + protected _data: UmbArrayState; + + public readonly storeAlias: string; + + constructor(_host: UmbControllerHostElement, storeAlias: string, data: UmbArrayState) { + this._host = _host; + this.storeAlias = storeAlias; + this._data = data; + new UmbContextProviderController(_host, storeAlias, this); } + + /** + * Appends items to the store + * @param {Array} items + * @memberof UmbEntityTreeStore + */ + appendItems(items: Array) { + this._data.append(items); + } + + /** + * Updates an item in the store + * @param {string} id + * @param {Partial} data + * @memberof UmbEntityTreeStore + */ + updateItem(unique: string, data: Partial) { + this._data.updateOne(unique, data); + } + + /** + * Removes an item from the store + * @param {string} id + * @memberof UmbEntityTreeStore + */ + removeItem(unique: string) { + this._data.removeOne(unique); + } } diff --git a/src/Umbraco.Web.UI.Client/libs/store/store.interface.ts b/src/Umbraco.Web.UI.Client/libs/store/store.interface.ts new file mode 100644 index 0000000000..0f094a1d4f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/libs/store/store.interface.ts @@ -0,0 +1,5 @@ +export interface UmbStore { + appendItems: (items: Array) => void; + updateItem: (unique: string, item: Partial) => void; + removeItem: (unique: string) => void; +} diff --git a/src/Umbraco.Web.UI.Client/libs/store/store.ts b/src/Umbraco.Web.UI.Client/libs/store/store.ts index 4961162767..b2c48aedde 100644 --- a/src/Umbraco.Web.UI.Client/libs/store/store.ts +++ b/src/Umbraco.Web.UI.Client/libs/store/store.ts @@ -1,3 +1,4 @@ +// TODO: delete when the last usages are gone import type { Observable } from 'rxjs'; export interface UmbDataStoreIdentifiers { diff --git a/src/Umbraco.Web.UI.Client/libs/store/tree-store.interface.ts b/src/Umbraco.Web.UI.Client/libs/store/tree-store.interface.ts index 80a366ada5..cbe7b563d8 100644 --- a/src/Umbraco.Web.UI.Client/libs/store/tree-store.interface.ts +++ b/src/Umbraco.Web.UI.Client/libs/store/tree-store.interface.ts @@ -1,12 +1,10 @@ import type { Observable } from 'rxjs'; import { TreeItemPresentationModel } from '../backend-api'; +import { UmbStore } from './store.interface'; -export interface UmbTreeStore { - appendItems: (items: Array) => void; - updateItem: (unique: string, item: Partial) => void; - removeItem: (unique: string) => void; - +export interface UmbTreeStore extends UmbStore { rootItems: Observable>; childrenOf: (parentUnique: string | null) => Observable>; + // TODO: remove this one when all repositories are using an item store items: (uniques: Array) => Observable>; } diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 8b9729f5f3..7a28616adb 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -69,6 +69,7 @@ "storybook": "^7.0.2", "tiny-glob": "^0.2.9", "typescript": "^5.0.3", + "typescript-json-schema": "^0.55.0", "vite": "^4.2.1", "vite-plugin-static-copy": "^0.13.0", "vite-tsconfig-paths": "^4.0.5", @@ -2000,6 +2001,28 @@ "node": ">=0.1.90" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -4733,6 +4756,30 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, "node_modules/@types/accepts": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", @@ -7049,6 +7096,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/address": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", @@ -7260,6 +7316,12 @@ "node": ">=10" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -8592,6 +8654,12 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "node_modules/cross-fetch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", @@ -13530,6 +13598,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "node_modules/make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", @@ -14666,6 +14740,12 @@ "tslib": "^2.0.3" } }, + "node_modules/path-equal": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/path-equal/-/path-equal-1.2.5.tgz", + "integrity": "sha512-i73IctDr3F2W+bsOWDyyVm/lqsXO47aY9nsFZUjTT/aljSbkxHxxCoyZ9UUrM8jK0JVod+An+rl48RCsvWM+9g==", + "dev": true + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -16179,6 +16259,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-stable-stringify": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.2.tgz", + "integrity": "sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -17234,6 +17323,58 @@ "node": ">=6.10" } }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/ts-simple-type": { "version": "2.0.0-next.0", "resolved": "https://registry.npmjs.org/ts-simple-type/-/ts-simple-type-2.0.0-next.0.tgz", @@ -17389,6 +17530,64 @@ "node": ">=12.20" } }, + "node_modules/typescript-json-schema": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.55.0.tgz", + "integrity": "sha512-BXaivYecUdiXWWNiUqXgY6A9cMWerwmhtO+lQE7tDZGs7Mf38sORDeQZugfYOZOHPZ9ulsD+w0LWjFDOQoXcwg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/node": "^16.9.2", + "glob": "^7.1.7", + "path-equal": "^1.1.2", + "safe-stable-stringify": "^2.2.0", + "ts-node": "^10.9.1", + "typescript": "~4.8.2", + "yargs": "^17.1.1" + }, + "bin": { + "typescript-json-schema": "bin/typescript-json-schema" + } + }, + "node_modules/typescript-json-schema/node_modules/@types/node": { + "version": "16.18.16", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.16.tgz", + "integrity": "sha512-ZOzvDRWp8dCVBmgnkIqYCArgdFOO9YzocZp8Ra25N/RStKiWvMOXHMz+GjSeVNe5TstaTmTWPucGJkDw0XXJWA==", + "dev": true + }, + "node_modules/typescript-json-schema/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typescript-json-schema/node_modules/typescript": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/typical": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", @@ -17708,6 +17907,12 @@ "integrity": "sha512-dsNgbLaTrd6l3MMxTtouOCFw4CBFc/3a+GgYA2YyrJvyQ1u6q4pcu3ktLoUZ/VN/Aw9WsauazbgsgdfVWgAKQg==", "dev": true }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "node_modules/v8-to-istanbul": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", @@ -18351,6 +18556,15 @@ "node": ">= 4.0.0" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -19712,6 +19926,27 @@ "dev": true, "optional": true }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } + } + }, "@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -21594,6 +21829,30 @@ "magic-string": "^0.27.0" } }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, "@types/accepts": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", @@ -23639,6 +23898,12 @@ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, "address": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", @@ -23804,6 +24069,12 @@ "readable-stream": "^3.6.0" } }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -24802,6 +25073,12 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "cross-fetch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", @@ -28474,6 +28751,12 @@ "semver": "^6.0.0" } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", @@ -29334,6 +29617,12 @@ "tslib": "^2.0.3" } }, + "path-equal": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/path-equal/-/path-equal-1.2.5.tgz", + "integrity": "sha512-i73IctDr3F2W+bsOWDyyVm/lqsXO47aY9nsFZUjTT/aljSbkxHxxCoyZ9UUrM8jK0JVod+An+rl48RCsvWM+9g==", + "dev": true + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -30419,6 +30708,12 @@ "is-regex": "^1.1.4" } }, + "safe-stable-stringify": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.2.tgz", + "integrity": "sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA==", + "dev": true + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -31276,6 +31571,35 @@ "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", "dev": true }, + "ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + } + } + }, "ts-simple-type": { "version": "2.0.0-next.0", "resolved": "https://registry.npmjs.org/ts-simple-type/-/ts-simple-type-2.0.0-next.0.tgz", @@ -31387,6 +31711,50 @@ "integrity": "sha512-xv8mOEDnigb/tN9PSMTwSEqAnUvkoXMQlicOb0IUVDBSQCgBSaAAROUZYy2IcUy5qU6XajK5jjjO7TMWqBTKZA==", "dev": true }, + "typescript-json-schema": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.55.0.tgz", + "integrity": "sha512-BXaivYecUdiXWWNiUqXgY6A9cMWerwmhtO+lQE7tDZGs7Mf38sORDeQZugfYOZOHPZ9ulsD+w0LWjFDOQoXcwg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@types/node": "^16.9.2", + "glob": "^7.1.7", + "path-equal": "^1.1.2", + "safe-stable-stringify": "^2.2.0", + "ts-node": "^10.9.1", + "typescript": "~4.8.2", + "yargs": "^17.1.1" + }, + "dependencies": { + "@types/node": { + "version": "16.18.16", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.16.tgz", + "integrity": "sha512-ZOzvDRWp8dCVBmgnkIqYCArgdFOO9YzocZp8Ra25N/RStKiWvMOXHMz+GjSeVNe5TstaTmTWPucGJkDw0XXJWA==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "typescript": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "dev": true + } + } + }, "typical": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", @@ -31616,6 +31984,12 @@ "integrity": "sha512-dsNgbLaTrd6l3MMxTtouOCFw4CBFc/3a+GgYA2YyrJvyQ1u6q4pcu3ktLoUZ/VN/Aw9WsauazbgsgdfVWgAKQg==", "dev": true }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "v8-to-istanbul": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", @@ -32081,6 +32455,12 @@ "integrity": "sha512-RXRJzMiK6U2ye0BlGGZnmpwJDPgakn6aNQ0A7gHRbD4I0uvK4TW6UqkK1V0pp9jskjJBAXd3dRrbzWkqJ+6cxA==", "dev": true }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 561d6a8af6..f2aef92bcc 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -28,7 +28,7 @@ "scripts": { "dev": "vite", "build": "tsc && vite build --mode staging", - "build:libs": "npm run wc-analyze && npm run wc-analyze:vscode && rollup -c rollup-libs.config.js && node utils/move-libs.js", + "build:libs": "npm run wc-analyze && npm run wc-analyze:vscode && npm run generate:jsonschema && rollup -c rollup-libs.config.js && node utils/move-libs.js", "build:for:static": "tsc && vite build", "build:for:cms": "tsc && npm run build:libs && vite build -c vite.cms.config.ts", "build:for:cms:watch": "tsc && npm run build:libs && vite build -c vite.cms.config.ts --watch", @@ -45,12 +45,13 @@ "format:fix": "npm run format -- --write", "generate:api": "openapi --input https://raw.githubusercontent.com/umbraco/Umbraco-CMS/v13/dev/src/Umbraco.Cms.Api.Management/OpenApi.json --output libs/backend-api/src --postfix Resource --useOptions", "generate:api-dev": "openapi --input http://localhost:11000/umbraco/swagger/v1/swagger.json --output libs/backend-api/src --postfix Resource --useOptions", + "generate:jsonschema": "typescript-json-schema --required --include \"./libs/extensions-registry/*.ts\" --out dist/libs/umbraco-package-schema.json tsconfig.json UmbracoPackage", "storybook": "npm run wc-analyze && storybook dev -p 6006", "storybook:build": "npm run wc-analyze && storybook build", "build-storybook": "npm run wc-analyze && storybook build", "generate:icons": "node ./devops/icons/index.js", - "wc-analyze": "wca **/*.element.ts --outFile custom-elements.json", - "wc-analyze:vscode": "wca **/*.element.ts --format vscode --outFile vscode-html-custom-data.json", + "wc-analyze": "wca **/*.element.ts --outFile dist/libs/custom-elements.json", + "wc-analyze:vscode": "wca **/*.element.ts --format vscode --outFile dist/libs/vscode-html-custom-data.json", "new-extension": "plop --plopfile ./devops/plop/plop.js", "compile": "tsc", "check": "npm run lint && npm run compile && npm run build-storybook" @@ -120,6 +121,7 @@ "storybook": "^7.0.2", "tiny-glob": "^0.2.9", "typescript": "^5.0.3", + "typescript-json-schema": "^0.55.0", "vite": "^4.2.1", "vite-plugin-static-copy": "^0.13.0", "vite-tsconfig-paths": "^4.0.5", diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts index c4590ec1ee..116be1ac07 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts @@ -71,7 +71,8 @@ export class UmbBackofficeElement extends UmbLitElement { this.provideContext(UMB_CURRENT_USER_HISTORY_STORE_CONTEXT_TOKEN, new UmbCurrentUserHistoryStore()); // Register All Stores - this.observe(umbExtensionsRegistry.extensionsOfTypes(['store', 'treeStore']), (stores) => { + // TODO: can we use kinds here so we don't have to hardcode the types? + this.observe(umbExtensionsRegistry.extensionsOfTypes(['store', 'treeStore', 'itemStore']), (stores) => { stores.forEach((store) => createExtensionClass(store, [this])); }); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/document-blueprint.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/document-blueprint.detail.store.ts index 054c05d1ec..d7efe4701c 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/document-blueprint.detail.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/document-blueprint.detail.store.ts @@ -11,11 +11,14 @@ import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; * @description - Data Store for Document Blueprints */ export class UmbDocumentBlueprintStore extends UmbStoreBase { - // TODO: use the right type: - #data = new UmbArrayState([], (x) => x.id); - constructor(host: UmbControllerHostElement) { - super(host, UMB_DOCUMENT_BLUEPRINT_STORE_CONTEXT_TOKEN.toString()); + super( + host, + UMB_DOCUMENT_BLUEPRINT_STORE_CONTEXT_TOKEN.toString(), + // TODO: use the right type: + + new UmbArrayState([], (x) => x.id) + ); } /** @@ -29,10 +32,10 @@ export class UmbDocumentBlueprintStore extends UmbStoreBase { fetch(`/umbraco/management/api/v1/document-blueprint/details/${id}`) .then((res) => res.json()) .then((data) => { - this.#data.append(data); + this._data.append(data); }); - return this.#data.getObservablePart((documents) => documents.find((document) => document.id === id)); + return this._data.getObservablePart((documents) => documents.find((document) => document.id === id)); } getScaffold(entityType: string, parentId: string | null) { @@ -68,7 +71,7 @@ export class UmbDocumentBlueprintStore extends UmbStoreBase { }) .then((res) => res.json()) .then((data: Array) => { - this.#data.append(data); + this._data.append(data); }); } @@ -89,7 +92,7 @@ export class UmbDocumentBlueprintStore extends UmbStoreBase { }, }); - this.#data.remove(ids); + this._data.remove(ids); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/document-type.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/document-type.repository.ts index dd865dc083..14656bb826 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/document-type.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/document-type.repository.ts @@ -81,7 +81,7 @@ export class UmbDocumentTypeRepository implements UmbTreeRepository, U return { data, error, asObservable: () => this.#treeStore!.childrenOf(parentId) }; } - async requestTreeItems(ids: Array) { + async requestItemsLegacy(ids: Array) { await this.#init; if (!ids) { @@ -104,7 +104,7 @@ export class UmbDocumentTypeRepository implements UmbTreeRepository, U return this.#treeStore!.childrenOf(parentId); } - async treeItems(ids: Array) { + async itemsLegacy(ids: Array) { await this.#init; return this.#treeStore!.items(ids); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/document-type.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/document-type.store.ts index 3ad9e8e4e2..e04813169c 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/document-type.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/document-type.store.ts @@ -11,15 +11,17 @@ import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; * @description - Data Store for Document Types */ export class UmbDocumentTypeStore extends UmbStoreBase { - #data = new UmbArrayState([], (x) => x.id); - /** * Creates an instance of UmbDocumentTypeStore. * @param {UmbControllerHostElement} host * @memberof UmbDocumentTypeStore */ constructor(host: UmbControllerHostElement) { - super(host, UMB_DOCUMENT_TYPE_STORE_CONTEXT_TOKEN.toString()); + super( + host, + UMB_DOCUMENT_TYPE_STORE_CONTEXT_TOKEN.toString(), + new UmbArrayState([], (x) => x.id) + ); } /** @@ -28,7 +30,7 @@ export class UmbDocumentTypeStore extends UmbStoreBase { * @memberof UmbDocumentTypeStore */ append(document: DocumentTypeResponseModel) { - this.#data.append([document]); + this._data.append([document]); } /** @@ -37,7 +39,7 @@ export class UmbDocumentTypeStore extends UmbStoreBase { * @memberof UmbDocumentTypeStore */ byId(id: DocumentTypeResponseModel['id']) { - return this.#data.getObservablePart((x) => x.find((y) => y.id === id)); + return this._data.getObservablePart((x) => x.find((y) => y.id === id)); } /** @@ -46,7 +48,7 @@ export class UmbDocumentTypeStore extends UmbStoreBase { * @memberof UmbDocumentTypeStore */ remove(uniques: Array) { - this.#data.remove(uniques); + this._data.remove(uniques); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/document.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/document.repository.ts index b6ad80faaa..cde51f788c 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/document.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/document.repository.ts @@ -82,7 +82,7 @@ export class UmbDocumentRepository implements UmbTreeRepository, UmbDe return { data, error, asObservable: () => this.#treeStore!.childrenOf(parentId) }; } - async requestTreeItems(ids: Array) { + async requestItemsLegacy(ids: Array) { await this.#init; if (!ids) { @@ -105,7 +105,7 @@ export class UmbDocumentRepository implements UmbTreeRepository, UmbDe return this.#treeStore!.childrenOf(parentId); } - async treeItems(ids: Array) { + async itemsLegacy(ids: Array) { await this.#init; return this.#treeStore!.items(ids); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/document.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/document.store.ts index 5af98a68b8..b1f023bd78 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/document.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/document.store.ts @@ -11,15 +11,13 @@ import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; * @description - Data Store for Template Details */ export class UmbDocumentStore extends UmbStoreBase { - #data = new UmbArrayState([], (x) => x.id); - /** * Creates an instance of UmbDocumentDetailStore. * @param {UmbControllerHostElement} host * @memberof UmbDocumentDetailStore */ constructor(host: UmbControllerHostElement) { - super(host, UMB_DOCUMENT_STORE_CONTEXT_TOKEN.toString()); + super(host, UMB_DOCUMENT_STORE_CONTEXT_TOKEN.toString(), new UmbArrayState([], (x) => x.id)); } /** @@ -28,7 +26,7 @@ export class UmbDocumentStore extends UmbStoreBase { * @memberof UmbDocumentDetailStore */ append(document: DocumentResponseModel) { - this.#data.append([document]); + this._data.append([document]); } /** @@ -37,7 +35,7 @@ export class UmbDocumentStore extends UmbStoreBase { * @memberof UmbDocumentStore */ byKey(id: DocumentResponseModel['id']) { - return this.#data.getObservablePart((x) => x.find((y) => y.id === id)); + return this._data.getObservablePart((x) => x.find((y) => y.id === id)); } /** @@ -46,7 +44,7 @@ export class UmbDocumentStore extends UmbStoreBase { * @memberof UmbDocumentDetailStore */ remove(uniques: Array) { - this.#data.remove(uniques); + this._data.remove(uniques); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/media-type.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/media-type.detail.store.ts index 844b1f5a1e..e258ca33cf 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/media-type.detail.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/media-type.detail.store.ts @@ -11,18 +11,16 @@ import type { MediaTypeDetails } from '@umbraco-cms/backoffice/models'; * @description - Details Data Store for Media Types */ export class UmbMediaTypeStore extends UmbStoreBase { - #data = new UmbArrayState([], (x) => x.id); - constructor(host: UmbControllerHostElement) { - super(host, UMB_MEDIA_TYPE_STORE_CONTEXT_TOKEN.toString()); + super(host, UMB_MEDIA_TYPE_STORE_CONTEXT_TOKEN.toString(), new UmbArrayState([], (x) => x.id)); } append(mediaType: MediaTypeDetails) { - this.#data.append([mediaType]); + this._data.append([mediaType]); } remove(uniques: string[]) { - this.#data.remove(uniques); + this._data.remove(uniques); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/media-type.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/media-type.repository.ts index 56e3769a84..32fdedecbd 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/media-type.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/repository/media-type.repository.ts @@ -73,7 +73,7 @@ export class UmbMediaTypeRepository implements UmbTreeRepository { return { data, error, asObservable: () => this.#treeStore!.childrenOf(parentId) }; } - async requestTreeItems(ids: Array) { + async requestItemsLegacy(ids: Array) { await this.#init; if (!ids) { @@ -96,7 +96,7 @@ export class UmbMediaTypeRepository implements UmbTreeRepository { return this.#treeStore!.childrenOf(parentId); } - async treeItems(ids: Array) { + async itemsLegacy(ids: Array) { await this.#init; return this.#treeStore!.items(ids); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/entity-bulk-actions/trash/trash.action.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/entity-bulk-actions/trash/trash.action.ts index 4a40576216..ef1200f2e9 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/entity-bulk-actions/trash/trash.action.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/entity-bulk-actions/trash/trash.action.ts @@ -21,7 +21,7 @@ export class UmbMediaTrashEntityBulkAction extends UmbEntityBulkActionBase this.#treeStore!.childrenOf(parentId) }; } - async requestTreeItems(ids: Array) { + async requestItemsLegacy(ids: Array) { await this.#init; if (!ids) { @@ -119,7 +119,7 @@ export class UmbMediaRepository return this.#treeStore!.childrenOf(parentId); } - async treeItems(ids: Array) { + async itemsLegacy(ids: Array) { await this.#init; return this.#treeStore!.items(ids); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/media.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/media.store.ts index c6eb6e22f7..ba59e42ce4 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/media.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/media.store.ts @@ -11,15 +11,13 @@ import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; * @description - Data Store for Template Details */ export class UmbMediaStore extends UmbStoreBase { - #data = new UmbArrayState([], (x) => x.id); - /** * Creates an instance of UmbMediaStore. * @param {UmbControllerHostElement} host * @memberof UmbMediaStore */ constructor(host: UmbControllerHostElement) { - super(host, UMB_MEDIA_STORE_CONTEXT_TOKEN.toString()); + super(host, UMB_MEDIA_STORE_CONTEXT_TOKEN.toString(), new UmbArrayState([], (x) => x.id)); } /** @@ -28,7 +26,7 @@ export class UmbMediaStore extends UmbStoreBase { * @memberof UmbMediaStore */ append(media: MediaDetails) { - this.#data.append([media]); + this._data.append([media]); } /** @@ -37,7 +35,7 @@ export class UmbMediaStore extends UmbStoreBase { * @memberof UmbMediaStore */ remove(uniques: string[]) { - this.#data.remove(uniques); + this._data.remove(uniques); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.repository.ts index 22f2dceff6..a405678c1a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.repository.ts @@ -59,7 +59,7 @@ export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRep return { data: undefined, error }; } - async requestTreeItems(ids: Array) { + async requestItemsLegacy(ids: Array) { await this.#init; if (!ids) { @@ -82,7 +82,7 @@ export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRep return this.#treeStore!.childrenOf(parentId); } - async treeItems(ids: Array) { + async itemsLegacy(ids: Array) { await this.#init; return this.#treeStore!.items(ids); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.store.ts index 44eff1edff..e379594fa5 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/member-group.store.ts @@ -14,15 +14,19 @@ export class UmbMemberGroupStore extends UmbStoreBase { #data = new UmbArrayState([], (x) => x.id); constructor(host: UmbControllerHostElement) { - super(host, UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN.toString()); + super( + host, + UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN.toString(), + new UmbArrayState([], (x) => x.id) + ); } append(memberGroup: MemberGroupDetails) { - this.#data.append([memberGroup]); + this._data.append([memberGroup]); } remove(uniques: string[]) { - this.#data.remove(uniques); + this._data.remove(uniques); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.repository.ts index fe51a1c02c..960be5ef86 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.repository.ts @@ -77,7 +77,7 @@ export class UmbMemberTypeRepository implements UmbTreeRepository, return { data, error, asObservable: () => this.#treeStore!.childrenOf(parentId) }; } - async requestTreeItems(ids: Array) { + async requestItemsLegacy(ids: Array) { await this.#init; if (!ids) { @@ -100,7 +100,7 @@ export class UmbMemberTypeRepository implements UmbTreeRepository, return this.#treeStore!.childrenOf(parentId); } - async treeItems(ids: Array) { + async itemsLegacy(ids: Array) { await this.#init; return this.#treeStore!.items(ids); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.store.ts index c379a95620..5dca6dd718 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.store.ts @@ -11,18 +11,16 @@ import type { MemberTypeDetails } from '@umbraco-cms/backoffice/models'; * @description - Data Store for Member Types */ export class UmbMemberTypeStore extends UmbStoreBase { - #data = new UmbArrayState([], (x) => x.id); - constructor(host: UmbControllerHostElement) { - super(host, UMB_MEMBER_TYPE_STORE_CONTEXT_TOKEN.toString()); + super(host, UMB_MEMBER_TYPE_STORE_CONTEXT_TOKEN.toString(), new UmbArrayState([], (x) => x.id)); } append(MemberType: MemberTypeDetails) { - this.#data.append([MemberType]); + this._data.append([MemberType]); } remove(uniques: string[]) { - this.#data.remove(uniques); + this._data.remove(uniques); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/member.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/member.detail.store.ts index a5b8248990..95520e4d2b 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/member.detail.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/member.detail.store.ts @@ -13,11 +13,8 @@ import { UmbEntityDetailStore, UmbStoreBase } from '@umbraco-cms/backoffice/stor * @description - Data Store for Members */ export class UmbMemberStore extends UmbStoreBase implements UmbEntityDetailStore { - #data = new UmbArrayState([], (x) => x.id); - public groups = this.#data.asObservable(); - constructor(private host: UmbControllerHostElement) { - super(host, UMB_MEMBER_STORE_CONTEXT_TOKEN.toString()); + super(host, UMB_MEMBER_STORE_CONTEXT_TOKEN.toString(), new UmbArrayState([], (x) => x.id)); } getScaffold(entityType: string, parentId: string | null) { @@ -40,10 +37,10 @@ export class UmbMemberStore extends UmbStoreBase implements UmbEntityDetailStore // temp until Resource is updated const member = umbMemberData.getById(id); if (member) { - this.#data.appendOne(member); + this._data.appendOne(member); } - return createObservablePart(this.#data, (members) => members.find((member) => member.id === id) as MemberDetails); + return createObservablePart(this._data, (members) => members.find((member) => member.id === id) as MemberDetails); } async save(member: Array): Promise { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/member.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/member.repository.ts index 39fbb84c35..e5020375a1 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/member.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/member.repository.ts @@ -58,7 +58,7 @@ export class UmbMemberRepository implements UmbTreeRepository { return { data: undefined, error }; } - async requestTreeItems(ids: Array) { + async requestItemsLegacy(ids: Array) { await this.#init; if (!ids) { @@ -81,7 +81,7 @@ export class UmbMemberRepository implements UmbTreeRepository { return this.#treeStore!.childrenOf(parentId); } - async treeItems(ids: Array) { + async itemsLegacy(ids: Array) { await this.#init; return this.#treeStore!.items(ids); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/member.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/member.store.ts index 958cb319ab..49a7339a76 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/member.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/members/repository/member.store.ts @@ -11,18 +11,16 @@ import type { MemberDetails } from '@umbraco-cms/backoffice/models'; * @description - Data Store for Members */ export class UmbMemberStore extends UmbStoreBase { - #data = new UmbArrayState([], (x) => x.id); - constructor(host: UmbControllerHostElement) { - super(host, UMB_MEMBER_STORE_CONTEXT_TOKEN.toString()); + super(host, UMB_MEMBER_STORE_CONTEXT_TOKEN.toString(), new UmbArrayState([], (x) => x.id)); } append(member: MemberDetails) { - this.#data.append([member]); + this._data.append([member]); } remove(uniques: string[]) { - this.#data.remove(uniques); + this._data.remove(uniques); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-builder/workspace/workspace-package-builder.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-builder/workspace/workspace-package-builder.element.ts index 9dce5a6dca..ccb98189a6 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-builder/workspace/workspace-package-builder.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-builder/workspace/workspace-package-builder.element.ts @@ -259,7 +259,7 @@ export class UmbWorkspacePackageBuilderElement extends UmbLitElement { #renderDataTypeSection() { return html`
- +
`; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/packages/repository/package.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/packages/repository/package.store.ts index 0d0a8440a0..13a9ce6aa9 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/packages/repository/package.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/packages/repository/package.store.ts @@ -42,7 +42,9 @@ export class UmbPackageStore extends UmbStoreBase { * @memberof PackageStore */ constructor(host: UmbControllerHostElement) { - super(host, UMB_PACKAGE_STORE_TOKEN.toString()); + // TODO: revisit this store. Is it ok to have multiple data sets? + // temp hack to satisfy the base class + super(host, UMB_PACKAGE_STORE_TOKEN.toString(), new UmbArrayState([], (x) => x.name)); } /** diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/components/data-type-input/data-type-input.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/components/data-type-input/data-type-input.context.ts new file mode 100644 index 0000000000..06b3e5762e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/components/data-type-input/data-type-input.context.ts @@ -0,0 +1,10 @@ +import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; +import { UMB_DATA_TYPE_PICKER_MODAL } from '@umbraco-cms/backoffice/modal'; +import { DataTypeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; + +export class UmbDataTypePickerContext extends UmbPickerInputContext { + constructor(host: UmbControllerHostElement) { + super(host, 'Umb.Repository.DataType', UMB_DATA_TYPE_PICKER_MODAL); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/components/data-type-input/data-type-input.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/components/data-type-input/data-type-input.element.ts new file mode 100644 index 0000000000..c8c28070e2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/components/data-type-input/data-type-input.element.ts @@ -0,0 +1,138 @@ +import { css, html } from 'lit'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { customElement, property, state } from 'lit/decorators.js'; +import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins'; +import { UmbDataTypePickerContext } from './data-type-input.context'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import type { DataTypeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; + +@customElement('umb-data-type-input') +export class UmbDataTypeInputElement extends FormControlMixin(UmbLitElement) { + /** + * This is a minimum amount of selected items in this input. + * @type {number} + * @attr + * @default 0 + */ + @property({ type: Number }) + public get min(): number { + return this.#pickerContext.min; + } + public set min(value: number) { + this.#pickerContext.min = value; + } + + /** + * Min validation message. + * @type {boolean} + * @attr + * @default + */ + @property({ type: String, attribute: 'min-message' }) + minMessage = 'This field need more items'; + + /** + * This is a maximum amount of selected items in this input. + * @type {number} + * @attr + * @default Infinity + */ + @property({ type: Number }) + public get max(): number { + return this.#pickerContext.max; + } + public set max(value: number) { + this.#pickerContext.max = value; + } + + /** + * Max validation message. + * @type {boolean} + * @attr + * @default + */ + @property({ type: String, attribute: 'min-message' }) + maxMessage = 'This field exceeds the allowed amount of items'; + + public get selectedIds(): Array { + return this.#pickerContext.getSelection(); + } + public set selectedIds(ids: Array) { + this.#pickerContext.setSelection(ids); + } + + @property() + public set value(idsString: string) { + // Its with full purpose we don't call super.value, as thats being handled by the observation of the context selection. + this.selectedIds = idsString.split(/[ ,]+/); + } + + @state() + private _items?: Array; + + #pickerContext = new UmbDataTypePickerContext(this); + + constructor() { + super(); + + this.addValidator( + 'rangeUnderflow', + () => this.minMessage, + () => !!this.min && this.#pickerContext.getSelection().length < this.min + ); + + this.addValidator( + 'rangeOverflow', + () => this.maxMessage, + () => !!this.max && this.#pickerContext.getSelection().length > this.max + ); + + this.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(','))); + this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems)); + } + + protected getFormElement() { + return undefined; + } + + render() { + return html` + ${this._items?.map((item) => this._renderItem(item))} + this.#pickerContext.openPicker()} label="open" + >Add + `; + } + + private _renderItem(item: DataTypeItemResponseModel) { + if (!item.id) return; + return html` + + + this.#pickerContext.requestRemoveItem(item.id!)} + label="Remove Data Type ${item.name}" + >Remove + + + `; + } + + static styles = [ + UUITextStyles, + css` + #add-button { + width: 100%; + } + `, + ]; +} + +export default UmbDataTypeInputElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-data-type-input': UmbDataTypeInputElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/components/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/components/index.ts new file mode 100644 index 0000000000..c076c5f9a3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/components/index.ts @@ -0,0 +1 @@ +import './data-type-input/data-type-input.element'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/entity-actions/manifests.ts index 54e8288b16..eb57a2b500 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/entity-actions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/entity-actions/manifests.ts @@ -1,5 +1,7 @@ +import { DATA_TYPE_ENTITY_TYPE } from '..'; import { DATA_TYPE_REPOSITORY_ALIAS } from '../repository/manifests'; import { manifests as createManifests } from './create/manifests'; +import { manifests as moveManifests } from './move/manifests'; import { UmbDeleteEntityAction, UmbDeleteFolderEntityAction, @@ -20,7 +22,7 @@ const entityActions: Array = [ api: UmbDeleteEntityAction, }, conditions: { - entityType: 'data-type', + entityType: DATA_TYPE_ENTITY_TYPE, }, }, { @@ -35,7 +37,7 @@ const entityActions: Array = [ api: UmbDeleteFolderEntityAction, }, conditions: { - entityType: 'data-type', + entityType: DATA_TYPE_ENTITY_TYPE, }, }, { @@ -50,9 +52,9 @@ const entityActions: Array = [ api: UmbFolderUpdateEntityAction, }, conditions: { - entityType: 'data-type', + entityType: DATA_TYPE_ENTITY_TYPE, }, }, ]; -export const manifests = [...entityActions, ...createManifests]; +export const manifests = [...entityActions, ...createManifests, ...moveManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/entity-actions/move/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/entity-actions/move/manifests.ts new file mode 100644 index 0000000000..5e7ff4e7c6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/entity-actions/move/manifests.ts @@ -0,0 +1,24 @@ +import { DATA_TYPE_ENTITY_TYPE } from '../..'; +import { DATA_TYPE_REPOSITORY_ALIAS } from '../../repository/manifests'; +import { UmbMoveDataTypeEntityAction } from './move.action'; +import { ManifestTypes } from '@umbraco-cms/backoffice/extensions-registry'; + +const entityActions: Array = [ + { + type: 'entityAction', + alias: 'Umb.EntityAction.DataType.Move', + name: 'Move Data Type Entity Action', + weight: 900, + meta: { + icon: 'umb:enter', + label: 'Move to...', + repositoryAlias: DATA_TYPE_REPOSITORY_ALIAS, + api: UmbMoveDataTypeEntityAction, + }, + conditions: { + entityType: DATA_TYPE_ENTITY_TYPE, + }, + }, +]; + +export const manifests = [...entityActions]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/entity-actions/move/move.action.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/entity-actions/move/move.action.ts new file mode 100644 index 0000000000..9d24d65844 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/entity-actions/move/move.action.ts @@ -0,0 +1,27 @@ +import { UmbDataTypeRepository } from '../../repository/data-type.repository'; +import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; +import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN, UMB_DATA_TYPE_PICKER_MODAL } from '@umbraco-cms/backoffice/modal'; +import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; + +// TODO: investigate what we need to make a generic move action +export class UmbMoveDataTypeEntityAction extends UmbEntityActionBase { + #modalContext?: UmbModalContext; + + constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) { + super(host, repositoryAlias, unique); + + new UmbContextConsumerController(this.host, UMB_MODAL_CONTEXT_TOKEN, (instance) => { + this.#modalContext = instance; + }); + } + + async execute() { + if (!this.#modalContext) throw new Error('Modal context is not available'); + if (!this.repository) throw new Error('Repository is not available'); + + const modalHandler = this.#modalContext?.open(UMB_DATA_TYPE_PICKER_MODAL); + const { selection } = await modalHandler.onSubmit(); + await this.repository.move(this.unique, selection[0]); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/index.ts new file mode 100644 index 0000000000..44cb2b0223 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/index.ts @@ -0,0 +1,3 @@ +import './components'; + +export const DATA_TYPE_ENTITY_TYPE = 'data-type'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/manifests.ts index fac58d2628..28e0f18938 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/manifests.ts @@ -3,6 +3,7 @@ import { manifests as repositoryManifests } from './repository/manifests'; import { manifests as menuItemManifests } from './menu-item/manifests'; import { manifests as treeManifests } from './tree/manifests'; import { manifests as workspaceManifests } from './workspace/manifests'; +import { manifests as modalManifests } from './modal/manifests'; export const manifests = [ ...entityActions, @@ -10,4 +11,5 @@ export const manifests = [ ...menuItemManifests, ...treeManifests, ...workspaceManifests, + ...modalManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/modal/data-type-picker/data-type-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/modal/data-type-picker/data-type-picker-modal.element.ts new file mode 100644 index 0000000000..ea1cae5dfb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/modal/data-type-picker/data-type-picker-modal.element.ts @@ -0,0 +1,75 @@ +import { css, html } from 'lit'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { customElement, property, state } from 'lit/decorators.js'; +import type { UmbTreeElement } from '../../../../shared/components/tree/tree.element'; +import { + UmbDataTypePickerModalData, + UmbDataTypePickerModalResult, + UmbModalHandler, +} from '@umbraco-cms/backoffice/modal'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; + +// TODO: make use of UmbPickerLayoutBase +@customElement('umb-data-type-picker-modal') +export class UmbDataTypePickerModalElement extends UmbLitElement { + static styles = [UUITextStyles, css``]; + + @property({ attribute: false }) + modalHandler?: UmbModalHandler; + + @property({ type: Object, attribute: false }) + data?: UmbDataTypePickerModalData; + + @state() + _selection: Array = []; + + @state() + _multiple = false; + + connectedCallback() { + super.connectedCallback(); + this._selection = this.data?.selection ?? []; + this._multiple = this.data?.multiple ?? false; + } + + #onSelectionChange(e: CustomEvent) { + e.stopPropagation(); + const element = e.target as UmbTreeElement; + this._selection = element.selection; + } + + #submit() { + this.modalHandler?.submit({ selection: this._selection }); + } + + #close() { + this.modalHandler?.reject(); + } + + render() { + return html` + + + + +
+ + +
+
+ `; + } +} + +export default UmbDataTypePickerModalElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-data-type-picker-modal': UmbDataTypePickerModalElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/modal/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/modal/manifests.ts new file mode 100644 index 0000000000..838aba346b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/modal/manifests.ts @@ -0,0 +1,12 @@ +import type { ManifestModal } from '@umbraco-cms/backoffice/extensions-registry'; + +const modals: Array = [ + { + type: 'modal', + alias: 'Umb.Modal.DataTypePicker', + name: 'Data Type Picker Modal', + loader: () => import('./data-type-picker/data-type-picker-modal.element'), + }, +]; + +export const manifests = [...modals]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/data-type-item.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/data-type-item.store.ts new file mode 100644 index 0000000000..8fe3a5205a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/data-type-item.store.ts @@ -0,0 +1,36 @@ +import { DataTypeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; +import { UmbItemStore, UmbStoreBase } from '@umbraco-cms/backoffice/store'; +import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; + +/** + * @export + * @class UmbDataTypeItemStore + * @extends {UmbStoreBase} + * @description - Data Store for Data Type items + */ + +export class UmbDataTypeItemStore + extends UmbStoreBase + implements UmbItemStore +{ + /** + * Creates an instance of UmbDataTypeItemStore. + * @param {UmbControllerHostElement} host + * @memberof UmbDataTypeItemStore + */ + constructor(host: UmbControllerHostElement) { + super( + host, + UMB_DATA_TYPE_ITEM_STORE_CONTEXT_TOKEN.toString(), + new UmbArrayState([], (x) => x.id) + ); + } + + items(ids: Array) { + return this._data.getObservablePart((items) => items.filter((item) => ids.includes(item.id ?? ''))); + } +} + +export const UMB_DATA_TYPE_ITEM_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbDataTypeItemStore'); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/data-type.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/data-type.repository.ts index 21a4d02041..52a24e2a58 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/data-type.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/data-type.repository.ts @@ -1,44 +1,52 @@ import { UmbDataTypeTreeServerDataSource } from './sources/data-type.tree.server.data'; +import { UmbDataTypeMoveServerDataSource } from './sources/data-type-move.server.data'; import { UmbDataTypeStore, UMB_DATA_TYPE_STORE_CONTEXT_TOKEN } from './data-type.store'; import { UmbDataTypeServerDataSource } from './sources/data-type.server.data'; import { UmbDataTypeTreeStore, UMB_DATA_TYPE_TREE_STORE_CONTEXT_TOKEN } from './data-type.tree.store'; import { UmbDataTypeFolderServerDataSource } from './sources/data-type-folder.server.data'; +import { UmbDataTypeItemServerDataSource } from './sources/data-type-item.server.data'; +import { UMB_DATA_TYPE_ITEM_STORE_CONTEXT_TOKEN, UmbDataTypeItemStore } from './data-type-item.store'; import type { - UmbTreeDataSource, UmbTreeRepository, UmbDetailRepository, - UmbFolderDataSource, - UmbDataSource, + UmbItemRepository, + UmbFolderRepository, + UmbMoveRepository, } from '@umbraco-cms/backoffice/repository'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; import { CreateDataTypeRequestModel, CreateFolderRequestModel, + DataTypeItemResponseModel, DataTypeResponseModel, FolderModelBaseModel, FolderTreeItemResponseModel, UpdateDataTypeRequestModel, } from '@umbraco-cms/backoffice/backend-api'; import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/notification'; -import { UmbFolderRepository } from '@umbraco-cms/backoffice/repository'; export class UmbDataTypeRepository implements + UmbItemRepository, UmbTreeRepository, UmbDetailRepository, - UmbFolderRepository + UmbFolderRepository, + UmbMoveRepository { - #init!: Promise; + #init: Promise; #host: UmbControllerHostElement; - #treeSource: UmbTreeDataSource; - #detailSource: UmbDataSource; - #folderSource: UmbFolderDataSource; + #treeSource: UmbDataTypeTreeServerDataSource; + #detailSource: UmbDataTypeServerDataSource; + #folderSource: UmbDataTypeFolderServerDataSource; + #itemSource: UmbDataTypeItemServerDataSource; + #moveSource: UmbDataTypeMoveServerDataSource; #detailStore?: UmbDataTypeStore; #treeStore?: UmbDataTypeTreeStore; + #itemStore?: UmbDataTypeItemStore; #notificationContext?: UmbNotificationContext; @@ -49,22 +57,29 @@ export class UmbDataTypeRepository this.#treeSource = new UmbDataTypeTreeServerDataSource(this.#host); this.#detailSource = new UmbDataTypeServerDataSource(this.#host); this.#folderSource = new UmbDataTypeFolderServerDataSource(this.#host); + this.#itemSource = new UmbDataTypeItemServerDataSource(this.#host); + this.#moveSource = new UmbDataTypeMoveServerDataSource(this.#host); this.#init = Promise.all([ - new UmbContextConsumerController(this.#host, UMB_DATA_TYPE_TREE_STORE_CONTEXT_TOKEN, (instance) => { - this.#treeStore = instance; - }), - new UmbContextConsumerController(this.#host, UMB_DATA_TYPE_STORE_CONTEXT_TOKEN, (instance) => { this.#detailStore = instance; - }), + }).asPromise(), + + new UmbContextConsumerController(this.#host, UMB_DATA_TYPE_TREE_STORE_CONTEXT_TOKEN, (instance) => { + this.#treeStore = instance; + }).asPromise(), + + new UmbContextConsumerController(this.#host, UMB_DATA_TYPE_ITEM_STORE_CONTEXT_TOKEN, (instance) => { + this.#itemStore = instance; + }).asPromise(), new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_CONTEXT_TOKEN, (instance) => { this.#notificationContext = instance; - }), + }).asPromise(), ]); } + // TREE: async requestRootTreeItems() { await this.#init; @@ -90,15 +105,6 @@ export class UmbDataTypeRepository return { data, error, asObservable: () => this.#treeStore!.childrenOf(parentId) }; } - async requestTreeItems(ids: Array) { - if (!ids) throw new Error('Keys are missing'); - await this.#init; - - const { data, error } = await this.#treeSource.getItems(ids); - - return { data, error, asObservable: () => this.#treeStore!.items(ids) }; - } - async rootTreeItems() { await this.#init; return this.#treeStore!.rootItems; @@ -110,13 +116,26 @@ export class UmbDataTypeRepository return this.#treeStore!.childrenOf(parentId); } - async treeItems(ids: Array) { + // ITEMS: + async requestItems(ids: Array) { + if (!ids) throw new Error('Keys are missing'); await this.#init; - return this.#treeStore!.items(ids); + + const { data, error } = await this.#itemSource.getItems(ids); + + if (data) { + this.#itemStore?.appendItems(data); + } + + return { data, error, asObservable: () => this.#itemStore!.items(ids) }; + } + + async items(ids: Array) { + await this.#init; + return this.#itemStore!.items(ids); } // DETAILS: - async createScaffold(parentId: string | null) { if (parentId === undefined) throw new Error('Parent id is missing'); await this.#init; @@ -146,7 +165,6 @@ export class UmbDataTypeRepository async create(dataType: CreateDataTypeRequestModel) { if (!dataType) throw new Error('Data Type is missing'); if (!dataType.id) throw new Error('Data Type id is missing'); - await this.#init; const { error } = await this.#detailSource.insert(dataType); @@ -167,7 +185,6 @@ export class UmbDataTypeRepository async save(id: string, updatedDataType: UpdateDataTypeRequestModel) { if (!id) throw new Error('Data Type id is missing'); if (!updatedDataType) throw new Error('Data Type is missing'); - await this.#init; const { error } = await this.#detailSource.update(id, updatedDataType); @@ -187,8 +204,6 @@ export class UmbDataTypeRepository return { error }; } - // General: - async delete(id: string) { if (!id) throw new Error('Data Type id is missing'); await this.#init; @@ -210,9 +225,10 @@ export class UmbDataTypeRepository return { error }; } - // folder + // Folder: async createFolderScaffold(parentId: string | null) { if (parentId === undefined) throw new Error('Parent id is missing'); + await this.#init; return this.#folderSource.createScaffold(parentId); } @@ -234,6 +250,7 @@ export class UmbDataTypeRepository async deleteFolder(id: string) { if (!id) throw new Error('Key is missing'); + await this.#init; const { error } = await this.#folderSource.delete(id); @@ -247,6 +264,7 @@ export class UmbDataTypeRepository async updateFolder(id: string, folder: FolderModelBaseModel) { if (!id) throw new Error('Key is missing'); if (!folder) throw new Error('Folder data is missing'); + await this.#init; const { error } = await this.#folderSource.update(id, folder); @@ -259,6 +277,7 @@ export class UmbDataTypeRepository async requestFolder(id: string) { if (!id) throw new Error('Key is missing'); + await this.#init; const { data, error } = await this.#folderSource.get(id); @@ -268,6 +287,23 @@ export class UmbDataTypeRepository return { data, error }; } + + // Actions + async move(id: string, targetId: string) { + await this.#init; + const { error } = await this.#moveSource.move(id, targetId); + + if (!error) { + // TODO: Be aware about this responsibility. + this.#treeStore?.updateItem(id, { parentId: targetId }); + this.#treeStore?.updateItem(targetId, { hasChildren: true }); + + const notification = { data: { message: `Data type moved` } }; + this.#notificationContext?.peek('positive', notification); + } + + return { error }; + } } export const createTreeItem = (item: CreateDataTypeRequestModel): FolderTreeItemResponseModel => { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/data-type.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/data-type.store.ts index b3aea6ee17..c2d25be707 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/data-type.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/data-type.store.ts @@ -13,15 +13,17 @@ export const UMB_DATA_TYPE_STORE_CONTEXT_TOKEN = new UmbContextToken([], (x) => x.id); - /** * Creates an instance of UmbDataTypeStore. * @param {UmbControllerHostElement} host * @memberof UmbDataTypeStore */ constructor(host: UmbControllerHostElement) { - super(host, UMB_DATA_TYPE_STORE_CONTEXT_TOKEN.toString()); + super( + host, + UMB_DATA_TYPE_STORE_CONTEXT_TOKEN.toString(), + new UmbArrayState([], (x) => x.id) + ); } /** @@ -30,7 +32,7 @@ export class UmbDataTypeStore extends UmbStoreBase { * @memberof UmbDataTypeStore */ append(dataType: DataTypeResponseModel) { - this.#data.append([dataType]); + this._data.append([dataType]); } /** @@ -39,7 +41,7 @@ export class UmbDataTypeStore extends UmbStoreBase { * @memberof UmbDataTypeStore */ byId(id: DataTypeResponseModel['id']) { - return this.#data.getObservablePart((x) => x.find((y) => y.id === id)); + return this._data.getObservablePart((x) => x.find((y) => y.id === id)); } /** @@ -48,6 +50,6 @@ export class UmbDataTypeStore extends UmbStoreBase { * @memberof UmbDataTypeStore */ remove(uniques: Array) { - this.#data.remove(uniques); + this._data.remove(uniques); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/manifests.ts index 0217359bf7..60ce0e41e6 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/manifests.ts @@ -1,7 +1,13 @@ import { UmbDataTypeRepository } from '../repository/data-type.repository'; +import { UmbDataTypeItemStore } from './data-type-item.store'; import { UmbDataTypeStore } from './data-type.store'; import { UmbDataTypeTreeStore } from './data-type.tree.store'; -import type { ManifestStore, ManifestTreeStore, ManifestRepository } from '@umbraco-cms/backoffice/extensions-registry'; +import type { + ManifestStore, + ManifestTreeStore, + ManifestRepository, + ManifestItemStore, +} from '@umbraco-cms/backoffice/extensions-registry'; export const DATA_TYPE_REPOSITORY_ALIAS = 'Umb.Repository.DataType'; @@ -14,6 +20,7 @@ const repository: ManifestRepository = { export const DATA_TYPE_STORE_ALIAS = 'Umb.Store.DataType'; export const DATA_TYPE_TREE_STORE_ALIAS = 'Umb.Store.DataTypeTree'; +export const DATA_TYPE_ITEM_STORE_ALIAS = 'Umb.Store.DataTypeItem'; const store: ManifestStore = { type: 'store', @@ -29,4 +36,11 @@ const treeStore: ManifestTreeStore = { class: UmbDataTypeTreeStore, }; -export const manifests = [repository, store, treeStore]; +const itemStore: ManifestItemStore = { + type: 'itemStore', + alias: DATA_TYPE_ITEM_STORE_ALIAS, + name: 'Data Type Item Store', + class: UmbDataTypeItemStore, +}; + +export const manifests = [repository, store, treeStore, itemStore]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/sources/data-type-item.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/sources/data-type-item.server.data.ts new file mode 100644 index 0000000000..ae7943c5ab --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/sources/data-type-item.server.data.ts @@ -0,0 +1,39 @@ +import type { UmbItemDataSource } from '@umbraco-cms/backoffice/repository'; +import { DataTypeItemResponseModel, DataTypeResource } from '@umbraco-cms/backoffice/backend-api'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; + +/** + * A data source for Data Type items that fetches data from the server + * @export + * @class UmbDataTypeItemServerDataSource + * @implements {DocumentTreeDataSource} + */ +export class UmbDataTypeItemServerDataSource implements UmbItemDataSource { + #host: UmbControllerHostElement; + + /** + * Creates an instance of UmbDataTypeItemServerDataSource. + * @param {UmbControllerHostElement} host + * @memberof UmbDataTypeItemServerDataSource + */ + constructor(host: UmbControllerHostElement) { + this.#host = host; + } + + /** + * Fetches the items for the given ids from the server + * @param {Array} ids + * @return {*} + * @memberof UmbDataTypeItemServerDataSource + */ + async getItems(ids: Array) { + if (!ids) throw new Error('Ids are missing'); + return tryExecuteAndNotify( + this.#host, + DataTypeResource.getDataTypeItem({ + id: ids, + }) + ); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/sources/data-type-move.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/sources/data-type-move.server.data.ts new file mode 100644 index 0000000000..c6759d7676 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/sources/data-type-move.server.data.ts @@ -0,0 +1,43 @@ +import { DataTypeResource } from '@umbraco-cms/backoffice/backend-api'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; +import { UmbMoveDataSource } from '@umbraco-cms/backoffice/repository'; + +/** + * A data source for Data Type items that fetches data from the server + * @export + * @class UmbDataTypeMoveServerDataSource + */ +export class UmbDataTypeMoveServerDataSource implements UmbMoveDataSource { + #host: UmbControllerHostElement; + + /** + * Creates an instance of UmbDataTypeMoveServerDataSource. + * @param {UmbControllerHostElement} host + * @memberof UmbDataTypeMoveServerDataSource + */ + constructor(host: UmbControllerHostElement) { + this.#host = host; + } + + /** + * Move an item for the given id to the target id + * @param {Array} id + * @return {*} + * @memberof UmbDataTypeMoveServerDataSource + */ + async move(id: string, targetId: string) { + if (!id) throw new Error('Id is missing'); + if (!targetId) throw new Error('Target Id is missing'); + + return tryExecuteAndNotify( + this.#host, + DataTypeResource.postDataTypeByIdMove({ + id, + requestBody: { + targetId, + }, + }) + ); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/sources/data-type.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/sources/data-type.server.data.ts index 0d70c24d85..c8108c42ea 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/sources/data-type.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/sources/data-type.server.data.ts @@ -67,7 +67,7 @@ export class UmbDataTypeServerDataSource * @return {*} * @memberof UmbDataTypeServerDataSource */ - async insert(dataType: CreateDataTypeRequestModel & { id: string }) { + async insert(dataType: CreateDataTypeRequestModel) { if (!dataType) throw new Error('Data Type is missing'); if (!dataType.id) throw new Error('Data Type id is missing'); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/index.ts index a578c12d5c..c86644f739 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/index.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/index.ts @@ -11,6 +11,8 @@ import { manifests as logviewerManifests } from './logviewer/manifests'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extensions-api'; import { ManifestTypes } from '@umbraco-cms/backoffice/extensions-registry'; +import './data-types/components'; + export const manifests = [ ...settingsSectionManifests, ...settingsMenuManifests, diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language-item.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language-item.store.ts new file mode 100644 index 0000000000..e475a0d96b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language-item.store.ts @@ -0,0 +1,31 @@ +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; +import { UmbStoreBase } from '@umbraco-cms/backoffice/store'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; +import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; +import { LanguageResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import type { UmbItemStore } from '@umbraco-cms/backoffice/store'; + +export const UMB_LANGUAGE_ITEM_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbLanguageItemStore'); + +/** + * @export + * @class UmbLanguageItemStore + * @extends {UmbStoreBase} + * @description - Store for Languages items + */ +export class UmbLanguageItemStore + extends UmbStoreBase + implements UmbItemStore +{ + constructor(host: UmbControllerHostElement) { + super( + host, + UMB_LANGUAGE_ITEM_STORE_CONTEXT_TOKEN.toString(), + new UmbArrayState([], (x) => x.isoCode) + ); + } + + items(isoCodes: Array) { + return this._data.getObservablePart((items) => items.filter((item) => isoCodes.includes(item.isoCode ?? ''))); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts index d37dada762..243a7f3791 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.repository.ts @@ -1,17 +1,26 @@ import { UmbLanguageServerDataSource } from './sources/language.server.data'; import { UmbLanguageStore, UMB_LANGUAGE_STORE_CONTEXT_TOKEN } from './language.store'; +import { UmbLanguageItemServerDataSource } from './sources/language-item.server.data'; +import { UMB_LANGUAGE_ITEM_STORE_CONTEXT_TOKEN, UmbLanguageItemStore } from './language-item.store'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/notification'; -import { LanguageResponseModel, ProblemDetailsModel } from '@umbraco-cms/backoffice/backend-api'; +import { + LanguageItemResponseModel, + LanguageResponseModel, + ProblemDetailsModel, +} from '@umbraco-cms/backoffice/backend-api'; +import { UmbItemRepository } from '@umbraco-cms/backoffice/repository'; -export class UmbLanguageRepository { - #init!: Promise; +export class UmbLanguageRepository implements UmbItemRepository { + #init: Promise; #host: UmbControllerHostElement; #dataSource: UmbLanguageServerDataSource; + #itemDataSource: UmbLanguageItemServerDataSource; #languageStore?: UmbLanguageStore; + #languageItemStore?: UmbLanguageItemStore; #notificationContext?: UmbNotificationContext; @@ -19,15 +28,20 @@ export class UmbLanguageRepository { this.#host = host; this.#dataSource = new UmbLanguageServerDataSource(this.#host); + this.#itemDataSource = new UmbLanguageItemServerDataSource(this.#host); this.#init = Promise.all([ new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_CONTEXT_TOKEN, (instance) => { this.#notificationContext = instance; - }), + }).asPromise(), new UmbContextConsumerController(this.#host, UMB_LANGUAGE_STORE_CONTEXT_TOKEN, (instance) => { this.#languageStore = instance; }).asPromise(), + + new UmbContextConsumerController(this.#host, UMB_LANGUAGE_ITEM_STORE_CONTEXT_TOKEN, (instance) => { + this.#languageItemStore = instance; + }).asPromise(), ]); } @@ -59,19 +73,19 @@ export class UmbLanguageRepository { } async requestItems(isoCodes: Array) { - // HACK: filter client side until we have a proper server side endpoint - // TODO: we will get a different size model here, how do we handle that in the store? - const { data, error } = await this.requestLanguages(); - - let items = undefined; + await this.#init; + const { data, error } = await this.#itemDataSource.getItems(isoCodes); if (data) { - // TODO: how do we best handle this? They might have a smaller data set than the details - items = data.items = data.items.filter((x) => isoCodes.includes(x.isoCode!)); - data.items.forEach((x) => this.#languageStore?.append(x)); + this.#languageItemStore?.appendItems(data); } - return { data: items, error, asObservable: () => this.#languageStore!.items(isoCodes) }; + return { data, error, asObservable: () => this.#languageItemStore!.items(isoCodes) }; + } + + async items(isoCodes: Array) { + await this.#init; + return this.#languageItemStore!.items(isoCodes); } /** diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.store.ts index 4844685cd7..34e3f4878c 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/language.store.ts @@ -13,23 +13,26 @@ export const UMB_LANGUAGE_STORE_CONTEXT_TOKEN = new UmbContextToken([], (x) => x.isoCode); - data = this.#data.asObservable(); + public readonly data = this._data.asObservable(); constructor(host: UmbControllerHostElement) { - super(host, UMB_LANGUAGE_STORE_CONTEXT_TOKEN.toString()); + super( + host, + UMB_LANGUAGE_STORE_CONTEXT_TOKEN.toString(), + new UmbArrayState([], (x) => x.isoCode) + ); } append(language: LanguageResponseModel) { - this.#data.append([language]); + this._data.append([language]); } remove(uniques: string[]) { - this.#data.remove(uniques); + this._data.remove(uniques); } // TODO: how do we best handle this? They might have a smaller data set than the details items(isoCodes: Array) { - return this.#data.getObservablePart((items) => items.filter((item) => isoCodes.includes(item.isoCode ?? ''))); + return this._data.getObservablePart((items) => items.filter((item) => isoCodes.includes(item.isoCode ?? ''))); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/manifests.ts index 75dcb127c4..e0811d94c1 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/manifests.ts @@ -1,5 +1,6 @@ import { UmbLanguageRepository } from '../repository/language.repository'; import { UmbLanguageStore } from './language.store'; +import { UmbLanguageItemStore } from './language-item.store'; import type { ManifestStore, ManifestRepository } from '@umbraco-cms/backoffice/extensions-registry'; export const LANGUAGE_REPOSITORY_ALIAS = 'Umb.Repository.Language'; @@ -12,6 +13,7 @@ const repository: ManifestRepository = { }; export const LANGUAGE_STORE_ALIAS = 'Umb.Store.Language'; +export const LANGUAGE_ITEM_STORE_ALIAS = 'Umb.Store.LanguageItem'; const store: ManifestStore = { type: 'store', @@ -20,4 +22,11 @@ const store: ManifestStore = { class: UmbLanguageStore, }; -export const manifests = [repository, store]; +const itemStore = { + type: 'itemStore', + alias: LANGUAGE_ITEM_STORE_ALIAS, + name: 'Language Item Store', + class: UmbLanguageItemStore, +}; + +export const manifests = [repository, store, itemStore]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/sources/language-item.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/sources/language-item.server.data.ts new file mode 100644 index 0000000000..807565c841 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/sources/language-item.server.data.ts @@ -0,0 +1,35 @@ +import { LanguageResource, LanguageItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; +import { UmbItemDataSource } from '@umbraco-cms/backoffice/repository'; + +/** + * A data source for Languages that fetches Language items from the server + * @export + * @class UmbLanguageItemServerDataSource + * @implements {UmbItemDataSource} + */ +export class UmbLanguageItemServerDataSource implements UmbItemDataSource { + #host: UmbControllerHostElement; + + constructor(host: UmbControllerHostElement) { + this.#host = host; + } + + /** + * Fetches Language items the given iso codes from the server + * @param {string[]} isoCodes + * @return {*} + * @memberof UmbLanguageItemServerDataSource + */ + async getItems(isoCodes: string[]) { + if (!isoCodes) throw new Error('Iso Codes are missing'); + + return tryExecuteAndNotify( + this.#host, + LanguageResource.getLanguageItem({ + isoCode: isoCodes, + }) + ); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/relation-types/repository/relation-type.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/relation-types/repository/relation-type.repository.ts index db6bfe9bcf..6f92a1ce92 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/relation-types/repository/relation-type.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/relation-types/repository/relation-type.repository.ts @@ -74,7 +74,7 @@ export class UmbRelationTypeRepository return { data: undefined, error }; } - async requestTreeItems(ids: Array) { + async requestItemsLegacy(ids: Array) { if (!ids) throw new Error('Ids are missing'); await this.#init; @@ -93,7 +93,7 @@ export class UmbRelationTypeRepository return this.#treeStore!.childrenOf(parentId); } - async treeItems(ids: Array) { + async itemsLegacy(ids: Array) { await this.#init; return this.#treeStore!.items(ids); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/relation-types/repository/relation-type.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/relation-types/repository/relation-type.store.ts index 71dac38ee3..708e9ba0e4 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/relation-types/repository/relation-type.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/relation-types/repository/relation-type.store.ts @@ -13,15 +13,17 @@ export const UMB_RELATION_TYPE_STORE_CONTEXT_TOKEN = new UmbContextToken([], (x) => x.id); - /** * Creates an instance of UmbRelationTypeStore. * @param {UmbControllerHostElement} host * @memberof UmbRelationTypeStore */ constructor(host: UmbControllerHostElement) { - super(host, UMB_RELATION_TYPE_STORE_CONTEXT_TOKEN.toString()); + super( + host, + UMB_RELATION_TYPE_STORE_CONTEXT_TOKEN.toString(), + new UmbArrayState([], (x) => x.id) + ); } /** @@ -30,7 +32,7 @@ export class UmbRelationTypeStore extends UmbStoreBase { * @memberof UmbRelationTypeStore */ append(RelationType: RelationTypeResponseModel) { - this.#data.append([RelationType]); + this._data.append([RelationType]); } /** @@ -39,7 +41,7 @@ export class UmbRelationTypeStore extends UmbStoreBase { * @memberof UmbRelationTypeStore */ byKey(id: RelationTypeResponseModel['id']) { - return this.#data.getObservablePart((x) => x.find((y) => y.id === id)); + return this._data.getObservablePart((x) => x.find((y) => y.id === id)); } /** @@ -48,6 +50,6 @@ export class UmbRelationTypeStore extends UmbStoreBase { * @memberof UmbRelationTypeStore */ remove(uniques: Array) { - this.#data.remove(uniques); + this._data.remove(uniques); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-language-picker/input-language-picker.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-language-picker/input-language-picker.context.ts new file mode 100644 index 0000000000..80568ccff4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-language-picker/input-language-picker.context.ts @@ -0,0 +1,10 @@ +import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; +import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; +import { UMB_LANGUAGE_PICKER_MODAL } from '@umbraco-cms/backoffice/modal'; +import type { LanguageItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; + +export class UmbLanguagePickerContext extends UmbPickerInputContext { + constructor(host: UmbControllerHostElement) { + super(host, 'Umb.Repository.Language', UMB_LANGUAGE_PICKER_MODAL, (item) => item.isoCode); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-language-picker/input-language-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-language-picker/input-language-picker.element.ts index df04084af4..2be4babdbd 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-language-picker/input-language-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-language-picker/input-language-picker.element.ts @@ -3,36 +3,25 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, property, state } from 'lit/decorators.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins'; -import { UmbLanguageRepository } from '../../../settings/languages/repository/language.repository'; -import { - UmbModalContext, - UMB_MODAL_CONTEXT_TOKEN, - UMB_CONFIRM_MODAL, - UMB_LANGUAGE_PICKER_MODAL, -} from '@umbraco-cms/backoffice/modal'; -import { UmbChangeEvent } from '@umbraco-cms/backoffice/events'; +import { UmbLanguagePickerContext } from './input-language-picker.context'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import type { LanguageResponseModel } from '@umbraco-cms/backoffice/backend-api'; -import type { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; @customElement('umb-input-language-picker') export class UmbInputLanguagePickerElement extends FormControlMixin(UmbLitElement) { - static styles = [ - UUITextStyles, - css` - #add-button { - width: 100%; - } - `, - ]; /** * This is a minimum amount of selected items in this input. * @type {number} * @attr - * @default undefined + * @default 0 */ @property({ type: Number }) - min?: number; + public get min(): number { + return this.#pickerContext.min; + } + public set min(value: number) { + this.#pickerContext.min = value; + } /** * Min validation message. @@ -47,10 +36,15 @@ export class UmbInputLanguagePickerElement extends FormControlMixin(UmbLitElemen * This is a maximum amount of selected items in this input. * @type {number} * @attr - * @default undefined + * @default Infinity */ @property({ type: Number }) - max?: number; + public get max(): number { + return this.#pickerContext.max; + } + public set max(value: number) { + this.#pickerContext.max = value; + } /** * Max validation message. @@ -64,29 +58,23 @@ export class UmbInputLanguagePickerElement extends FormControlMixin(UmbLitElemen @property({ type: Object, attribute: false }) public filter: (language: LanguageResponseModel) => boolean = () => true; - private _selectedIsoCodes: Array = []; public get selectedIsoCodes(): Array { - return this._selectedIsoCodes; + return this.#pickerContext.getSelection(); } - public set selectedIsoCodes(isoCodes: Array) { - this._selectedIsoCodes = isoCodes; - super.value = isoCodes.join(','); - this._observePickedItems(); + public set selectedIsoCodes(ids: Array) { + this.#pickerContext.setSelection(ids); } @property() public set value(isoCodesString: string) { - if (isoCodesString !== this._value) { - this.selectedIsoCodes = isoCodesString.split(/[ ,]+/); - } + // Its with full purpose we don't call super.value, as thats being handled by the observation of the context selection. + this.selectedIsoCodes = isoCodesString.split(/[ ,]+/); } @state() - private _items?: Array; + private _items: Array = []; - private _modalContext?: UmbModalContext; - private _repository = new UmbLanguageRepository(this); - private _pickedItemsObserver?: UmbObserverController; + #pickerContext = new UmbLanguagePickerContext(this); constructor() { super(); @@ -94,90 +82,65 @@ export class UmbInputLanguagePickerElement extends FormControlMixin(UmbLitElemen this.addValidator( 'rangeUnderflow', () => this.minMessage, - () => !!this.min && this._selectedIsoCodes.length < this.min + () => !!this.min && this.#pickerContext.getSelection().length < this.min ); this.addValidator( 'rangeOverflow', () => this.maxMessage, - () => !!this.max && this._selectedIsoCodes.length > this.max + () => !!this.max && this.#pickerContext.getSelection().length > this.max ); - this.consumeContext(UMB_MODAL_CONTEXT_TOKEN, (instance) => { - this._modalContext = instance; - }); + this.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(','))); + this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems)); } protected getFormElement() { return undefined; } - private async _observePickedItems() { - this._pickedItemsObserver?.destroy(); - if (!this._repository) return; - - const { asObservable } = await this._repository.requestItems(this._selectedIsoCodes); - - this._pickedItemsObserver = this.observe(asObservable(), (items) => { - this._items = items; - }); - } - private _openPicker() { - const modalHandler = this._modalContext?.open(UMB_LANGUAGE_PICKER_MODAL, { - multiple: this.max === 1 ? false : true, - selection: [...this._selectedIsoCodes], + this.#pickerContext.openPicker({ filter: this.filter, }); - - modalHandler?.onSubmit().then(({ selection }) => { - this._setSelection(selection); - }); - } - - private _removeItem(item: LanguageResponseModel) { - const modalHandler = this._modalContext?.open(UMB_CONFIRM_MODAL, { - color: 'danger', - headline: `Remove ${item.name}?`, - content: 'Are you sure you want to remove this item', - confirmLabel: 'Remove', - }); - - modalHandler?.onSubmit().then(() => { - const newSelection = this._selectedIsoCodes.filter((value) => value !== item.isoCode); - this._setSelection(newSelection); - }); - } - - private _setSelection(newSelection: Array) { - this.selectedIsoCodes = newSelection; - this.dispatchEvent(new UmbChangeEvent()); } render() { return html` - ${this._items?.map((item) => this._renderItem(item))} + ${this._items.map((item) => this._renderItem(item))} Add `; } private _renderItem(item: LanguageResponseModel) { + if (!item.isoCode) return; return html` - this._removeItem(item)} label="Remove ${item.name}">Remove + this.#pickerContext.requestRemoveItem(item.isoCode!)} label="Remove ${item.name}" + >Remove `; } + + static styles = [ + UUITextStyles, + css` + #add-button { + width: 100%; + } + `, + ]; } export default UmbInputLanguagePickerElement; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-media-picker/input-media-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-media-picker/input-media-picker.element.ts index f324ca44be..cf3afb8554 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-media-picker/input-media-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-media-picker/input-media-picker.element.ts @@ -129,7 +129,7 @@ export class UmbInputMediaPickerElement extends FormControlMixin(UmbLitElement) this._pickedItemsObserver?.destroy(); // TODO: consider changing this to the list data endpoint when it is available - const { asObservable } = await this._repository.requestTreeItems(this._selectedIds); + const { asObservable } = await this._repository.requestItemsLegacy(this._selectedIds); if (!asObservable) return; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-template/input-template.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-template/input-template.element.ts index e7d9dbd425..4fd0164550 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-template/input-template.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-template/input-template.element.ts @@ -87,7 +87,7 @@ export class UmbInputTemplateElement extends FormControlMixin(UmbLitElement) { async #observePickedTemplates() { this.observe( - await this._templateRepository.treeItems(this._selectedIds), + await this._templateRepository.itemsLegacy(this._selectedIds), (data) => { this._pickedTemplates = data; }, diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-upload-field/input-upload-field.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-upload-field/input-upload-field.element.ts index a5237a876d..f4727f6792 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-upload-field/input-upload-field.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-upload-field/input-upload-field.element.ts @@ -64,10 +64,10 @@ export class UmbInputUploadFieldElement extends FormControlMixin(UmbLitElement) multiple = false; @state() - _currentFiles: Blob[] = []; + _currentFiles: File[] = []; @state() - _currentFilesTemp?: Blob[]; + _currentFilesTemp?: File[]; @state() extensions?: string[]; @@ -114,12 +114,12 @@ export class UmbInputUploadFieldElement extends FormControlMixin(UmbLitElement) this.#setFiles(validated); } - #validateExtensions(): Blob[] { + #validateExtensions(): File[] { // TODO: Should property editor be able to handle allowed extensions like image/* ? - const filesValidated: Blob[] = []; + const filesValidated: File[] = []; this._currentFilesTemp?.forEach((temp) => { - const type = temp.type.slice(temp.type.lastIndexOf('/') + 1, temp.length); + const type = temp.type.slice(temp.type.lastIndexOf('/') + 1); if (this.fileExtensions?.find((x) => x === type)) filesValidated.push(temp); else this._notificationContext?.peek('danger', { @@ -129,7 +129,7 @@ export class UmbInputUploadFieldElement extends FormControlMixin(UmbLitElement) return filesValidated; } - #setFiles(files: Blob[]) { + #setFiles(files: File[]) { this._currentFiles = [...this._currentFiles, ...files]; //TODO: set keys when possible, not names diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/tree.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/tree.context.ts index cab623e4f8..cc720084d3 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/tree.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/tree.context.ts @@ -1,7 +1,7 @@ import type { Observable } from 'rxjs'; import { UmbTreeRepository } from '@umbraco-cms/backoffice/repository'; import type { ManifestTree } from '@umbraco-cms/backoffice/extensions-registry'; -import { UmbDeepState, UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; +import { UmbBooleanState, UmbArrayState, UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; import { createExtensionClass, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extensions-api'; @@ -10,6 +10,7 @@ export interface UmbTreeContext { readonly selectable: Observable; readonly selection: Observable>; setSelectable(value: boolean): void; + setMultiple(value: boolean): void; setSelection(value: Array): void; select(id: string): void; } @@ -18,10 +19,13 @@ export class UmbTreeContextBase implements UmbTreeContext { host: UmbControllerHostElement; public tree: ManifestTree; - #selectable = new UmbDeepState(false); + #selectable = new UmbBooleanState(false); public readonly selectable = this.#selectable.asObservable(); - #selection = new UmbDeepState(>[]); + #multiple = new UmbBooleanState(false); + public readonly multiple = this.#multiple.asObservable(); + + #selection = new UmbArrayState(>[]); public readonly selection = this.#selection.asObservable(); repository?: UmbTreeRepository; @@ -69,22 +73,36 @@ export class UmbTreeContextBase implements UmbTreeContext { this.#selectable.next(value); } + public getSelectable() { + return this.#selectable.getValue(); + } + + public setMultiple(value: boolean) { + this.#multiple.next(value); + } + + public getMultiple() { + return this.#multiple.getValue(); + } + public setSelection(value: Array) { if (!value) return; this.#selection.next(value); } - public select(id: string) { - const oldSelection = this.#selection.getValue(); - if (oldSelection.indexOf(id) !== -1) return; + public getSelection() { + return this.#selection.getValue(); + } - const selection = [...oldSelection, id]; - this.#selection.next(selection); + public select(id: string) { + if (!this.getSelectable()) return; + const newSelection = this.getMultiple() ? [...this.getSelection(), id] : [id]; + this.#selection.next(newSelection); } public deselect(id: string) { - const selection = this.#selection.getValue(); - this.#selection.next(selection.filter((x) => x !== id)); + const newSelection = this.getSelection().filter((x) => x !== id); + this.#selection.next(newSelection); } public async requestRootItems() { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/tree.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/tree.element.ts index 3466abc60b..e50d1e7930 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/tree.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/tree.element.ts @@ -51,6 +51,18 @@ export class UmbTreeElement extends UmbLitElement { this._treeContext?.setSelection(newVal); } + private _multiple = false; + @property({ type: Boolean, reflect: true }) + get multiple() { + return this._multiple; + } + set multiple(newVal) { + const oldVal = this._multiple; + this._multiple = newVal; + this.requestUpdate('multiple', oldVal); + this._treeContext?.setMultiple(newVal); + } + @state() private _tree?: ManifestTree; @@ -86,6 +98,7 @@ export class UmbTreeElement extends UmbLitElement { this._treeContext = new UmbTreeContextBase(this, this._tree); this._treeContext.setSelectable(this.selectable); this._treeContext.setSelection(this.selection); + this._treeContext.setMultiple(this.multiple); this.#observeSelection(); this.#observeTreeRoot(); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-layout/workspace-layout.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-layout/workspace-layout.element.ts index 99bf3fe996..fe1ca13fe1 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-layout/workspace-layout.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-layout/workspace-layout.element.ts @@ -4,7 +4,7 @@ import { customElement, property, state } from 'lit/decorators.js'; import { map } from 'rxjs'; import { repeat } from 'lit/directives/repeat.js'; -import type { IRoute } from '@umbraco-cms/backoffice/router'; +import type { IRoute, PageComponent } from '@umbraco-cms/backoffice/router'; import type { UmbRouterSlotInitEvent, UmbRouterSlotChangeEvent } from '@umbraco-cms/internal/router'; import { createExtensionElement, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extensions-api'; import type { @@ -120,6 +120,11 @@ export class UmbWorkspaceLayoutElement extends UmbLitElement { ); } + // TODO: Move into a helper function: + private componentHasManifest(component: PageComponent): component is HTMLElement & { manifest: unknown } { + return component ? 'manifest' in component : false; + } + private _createRoutes() { this._routes = []; @@ -135,8 +140,8 @@ export class UmbWorkspaceLayoutElement extends UmbLitElement { } return createExtensionElement(view); }, - setup: (component) => { - if (component && 'manifest' in component) { + setup: (component, info) => { + if (this.componentHasManifest(component)) { component.manifest = view; } }, diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/modals/property-settings/property-settings-modal.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/modals/property-settings/property-settings-modal.element.ts index c57165fe42..3e345aad1e 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/modals/property-settings/property-settings-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/modals/property-settings/property-settings-modal.element.ts @@ -127,7 +127,7 @@ export class UmbPropertySettingsModalElement extends UmbModalBaseElement this.#treeStore!.childrenOf(path) }; } - async requestTreeItems(paths: Array) { + async requestItemsLegacy(paths: Array) { if (!paths) throw new Error('Paths are missing'); await this.#init; const { data, error } = await this.#treeDataSource.getItems(paths); @@ -94,7 +94,7 @@ export class UmbStylesheetRepository return this.#treeStore!.childrenOf(parentPath); } - async treeItems(paths: Array) { + async itemsLegacy(paths: Array) { if (!paths) throw new Error('Paths are missing'); await this.#init; return this.#treeStore!.items(paths); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/templating/templates/repository/template.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/templating/templates/repository/template.repository.ts index 2903994109..24a0663004 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/templating/templates/repository/template.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/templating/templates/repository/template.repository.ts @@ -82,7 +82,7 @@ export class UmbTemplateRepository return { data, error, asObservable: () => this.#treeStore!.childrenOf(parentId) }; } - async requestTreeItems(ids: Array) { + async requestItemsLegacy(ids: Array) { await this.#init; if (!ids) { @@ -105,7 +105,7 @@ export class UmbTemplateRepository return this.#treeStore!.childrenOf(parentId); } - async treeItems(ids: Array) { + async itemsLegacy(ids: Array) { await this.#init; return this.#treeStore!.items(ids); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/templating/templates/repository/template.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/templating/templates/repository/template.store.ts index 4ef41040e2..d6e9a4589a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/templating/templates/repository/template.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/templating/templates/repository/template.store.ts @@ -11,15 +11,13 @@ import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controlle * @description - Data Store for Templates */ export class UmbTemplateStore extends UmbStoreBase { - #data = new UmbArrayState([], (x) => x.id); - /** * Creates an instance of UmbTemplateStore. * @param {UmbControllerHostElement} host * @memberof UmbTemplateStore */ constructor(host: UmbControllerHostElement) { - super(host, UMB_TEMPLATE_STORE_CONTEXT_TOKEN.toString()); + super(host, UMB_TEMPLATE_STORE_CONTEXT_TOKEN.toString(), new UmbArrayState([], (x) => x.id)); } /** @@ -28,7 +26,7 @@ export class UmbTemplateStore extends UmbStoreBase { * @memberof UmbTemplateStore */ append(template: TemplateResponseModel) { - this.#data.append([template]); + this._data.append([template]); } /** @@ -37,7 +35,7 @@ export class UmbTemplateStore extends UmbStoreBase { * @memberof UmbTemplateStore */ remove(uniques: string[]) { - this.#data.remove(uniques); + this._data.remove(uniques); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.repository.ts index d805833fe8..8ee8ddda86 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.repository.ts @@ -86,7 +86,7 @@ export class UmbDictionaryRepository return { data, error, asObservable: () => this.#treeStore!.childrenOf(parentId) }; } - async requestTreeItems(ids: Array) { + async requestItemsLegacy(ids: Array) { await this.#init; if (!ids) { @@ -109,7 +109,7 @@ export class UmbDictionaryRepository return this.#treeStore!.childrenOf(parentId); } - async treeItems(ids: Array) { + async itemsLegacy(ids: Array) { await this.#init; return this.#treeStore!.items(ids); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.store.ts index 8090202a0d..94347f6a59 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.store.ts @@ -11,18 +11,20 @@ import { DictionaryItemResponseModel } from '@umbraco-cms/backoffice/backend-api * @description - Data Store for Dictionary */ export class UmbDictionaryStore extends UmbStoreBase { - #data = new UmbArrayState([], (x) => x.id); - constructor(host: UmbControllerHostElement) { - super(host, UMB_DICTIONARY_STORE_CONTEXT_TOKEN.toString()); + super( + host, + UMB_DICTIONARY_STORE_CONTEXT_TOKEN.toString(), + new UmbArrayState([], (x) => x.id) + ); } append(dictionary: DictionaryItemResponseModel) { - this.#data.append([dictionary]); + this._data.append([dictionary]); } remove(uniques: string[]) { - this.#data.remove(uniques); + this._data.remove(uniques); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/repository/user-group.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/repository/user-group.store.ts index 87961ea266..b3eb6f3cd6 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/repository/user-group.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/repository/user-group.store.ts @@ -20,7 +20,7 @@ export class UmbUserGroupStore extends UmbStoreBase implements UmbEntityDetailSt public groups = this.#groups.asObservable(); constructor(host: UmbControllerHostElement) { - super(host, UMB_USER_GROUP_STORE_CONTEXT_TOKEN.toString()); + super(host, UMB_USER_GROUP_STORE_CONTEXT_TOKEN.toString(), new UmbArrayState([], (x) => x.id)); } getScaffold(entityType: string, parentId: string | null) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/repository/user.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/repository/user.store.ts index c074c2f3cb..fa14ef77fb 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/repository/user.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/repository/user.store.ts @@ -15,14 +15,13 @@ export const UMB_USER_STORE_CONTEXT_TOKEN = new UmbContextToken('U * @description - Data Store for Users */ export class UmbUserStore extends UmbStoreBase implements UmbEntityDetailStore { - #users = new UmbArrayState([], (x) => x.id); - public users = this.#users.asObservable(); + public users = this._data.asObservable(); #totalUsers = new UmbNumberState(0); public readonly totalUsers = this.#totalUsers.asObservable(); constructor(host: UmbControllerHostElement) { - super(host, UMB_USER_STORE_CONTEXT_TOKEN.toString()); + super(host, UMB_USER_STORE_CONTEXT_TOKEN.toString(), new UmbArrayState([], (x) => x.id)); } getScaffold(entityType: string, parentId: string | null) { @@ -52,7 +51,7 @@ export class UmbUserStore extends UmbStoreBase implements UmbEntityDetailStore res.json()) .then((data) => { this.#totalUsers.next(data.total); - this.#users.next(data.items); + this._data.next(data.items); }); return this.users; @@ -70,10 +69,10 @@ export class UmbUserStore extends UmbStoreBase implements UmbEntityDetailStore res.json()) .then((data) => { - this.#users.appendOne(data); + this._data.appendOne(data); }); - return this.#users.getObservablePart((users: Array) => + return this._data.getObservablePart((users: Array) => users.find((user: UmbUserStoreItemType) => user.id === id) ); } @@ -89,10 +88,10 @@ export class UmbUserStore extends UmbStoreBase implements UmbEntityDetailStore res.json()) .then((data) => { - this.#users.append(data); + this._data.append(data); }); - return this.#users.getObservablePart((users: Array) => + return this._data.getObservablePart((users: Array) => users.filter((user: UmbUserStoreItemType) => ids.includes(user.id)) ); } @@ -105,10 +104,10 @@ export class UmbUserStore extends UmbStoreBase implements UmbEntityDetailStore res.json()) .then((data) => { - this.#users.append(data); + this._data.append(data); }); - return this.#users.getObservablePart((users: Array) => + return this._data.getObservablePart((users: Array) => users.filter((user: UmbUserStoreItemType) => user.name.toLocaleLowerCase().includes(name)) ); } @@ -124,13 +123,13 @@ export class UmbUserStore extends UmbStoreBase implements UmbEntityDetailStore enabledKeys.includes(user.id)); + const storedUsers = this._data.getValue().filter((user) => enabledKeys.includes(user.id)); storedUsers.forEach((user) => { user.status = 'enabled'; }); - this.#users.append(storedUsers); + this._data.append(storedUsers); } catch (error) { console.error('Enable Users failed', error); } @@ -147,17 +146,17 @@ export class UmbUserStore extends UmbStoreBase implements UmbEntityDetailStore enabledKeys.includes(user.id)); + const storedUsers = this._data.getValue().filter((user) => enabledKeys.includes(user.id)); storedUsers.forEach((user) => { if (userKeys.includes(user.id)) { user.userGroups.push(userGroup); } else { - user.userGroups = user.userGroups.filter((group) => group !== userGroup); + user.userGroups = user.userGroups.filter((group: any) => group !== userGroup); } }); - this.#users.append(storedUsers); + this._data.append(storedUsers); } catch (error) { console.error('Add user group failed', error); } @@ -174,13 +173,13 @@ export class UmbUserStore extends UmbStoreBase implements UmbEntityDetailStore enabledKeys.includes(user.id)); + const storedUsers = this._data.getValue().filter((user) => enabledKeys.includes(user.id)); storedUsers.forEach((user) => { - user.userGroups = user.userGroups.filter((group) => group !== userGroup); + user.userGroups = user.userGroups.filter((group: any) => group !== userGroup); }); - this.#users.append(storedUsers); + this._data.append(storedUsers); } catch (error) { console.error('Remove user group failed', error); } @@ -197,13 +196,13 @@ export class UmbUserStore extends UmbStoreBase implements UmbEntityDetailStore disabledKeys.includes(user.id)); + const storedUsers = this._data.getValue().filter((user) => disabledKeys.includes(user.id)); storedUsers.forEach((user) => { user.status = 'disabled'; }); - this.#users.append(storedUsers); + this._data.append(storedUsers); } catch (error) { console.error('Disable Users failed', error); } @@ -220,7 +219,7 @@ export class UmbUserStore extends UmbStoreBase implements UmbEntityDetailStore = [ +export const data: Array = [ { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Folder 1', id: 'dt-folder1', @@ -17,7 +18,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde isFolder: true, }, { - $type: 'data-type', + $type: '', type: 'data-type', id: '0cc0eba1-9960-42c9-bf9b-60e150b429ae', parentId: null, @@ -27,7 +28,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde values: [], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Text', id: 'dt-textBox', @@ -42,7 +43,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde ], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Text Area', id: 'dt-textArea', @@ -52,7 +53,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde values: [], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'My JS Property Editor', id: 'dt-custom', @@ -62,7 +63,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde values: [], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Color Picker', id: 'dt-colorPicker', @@ -118,7 +119,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde ], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Content Picker', id: 'dt-contentPicker', @@ -133,7 +134,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde ], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Eye Dropper', id: 'dt-eyeDropper', @@ -170,7 +171,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde ], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Multi URL Picker', id: 'dt-multiUrlPicker', @@ -201,7 +202,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde ], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Multi Node Tree Picker', id: 'dt-multiNodeTreePicker', @@ -211,7 +212,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde values: [], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Date Picker', id: 'dt-datePicker', @@ -230,9 +231,9 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde ], }, { - $type: 'data-type', - name: 'Date Picker With Time', + $type: '', type: 'data-type', + name: 'Date Picker With Time', id: 'dt-datePicker-time', parentId: null, propertyEditorAlias: 'Umbraco.DateTime', @@ -249,9 +250,9 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde ], }, { - $type: 'data-type', - name: 'Time', + $type: '', type: 'data-type', + name: 'Time', id: 'dt-time', parentId: null, propertyEditorAlias: 'Umbraco.DateTime', @@ -268,7 +269,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde ], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Email', id: 'dt-email', @@ -278,7 +279,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde values: [], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Multiple Text String', id: 'dt-multipleTextString', @@ -297,7 +298,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde ], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Dropdown', id: 'dt-dropdown', @@ -307,7 +308,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde values: [], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Slider', id: 'dt-slider', @@ -342,7 +343,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde ], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Toggle', id: 'dt-toggle', @@ -369,7 +370,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde ], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Tags', id: 'dt-tags', @@ -379,7 +380,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde values: [], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Markdown Editor', id: 'dt-markdownEditor', @@ -389,7 +390,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde values: [], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Radio Button List', id: 'dt-radioButtonList', @@ -408,7 +409,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde ], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Checkbox List', id: 'dt-checkboxList', @@ -427,7 +428,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde ], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Block List', id: 'dt-blockList', @@ -437,7 +438,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde values: [], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Media Picker', id: 'dt-mediaPicker', @@ -447,7 +448,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde values: [], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Image Cropper', id: 'dt-imageCropper', @@ -457,7 +458,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde values: [], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Upload Field', id: 'dt-uploadField', @@ -472,7 +473,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde ], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Block Grid', id: 'dt-blockGrid', @@ -482,7 +483,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde values: [], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Collection View', id: 'dt-collectionView', @@ -492,7 +493,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde values: [], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Icon Picker', id: 'dt-iconPicker', @@ -502,7 +503,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde values: [], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Number Range', id: 'dt-numberRange', @@ -512,7 +513,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde values: [], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Order Direction', id: 'dt-orderDirection', @@ -522,7 +523,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde values: [], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Overlay Size', id: 'dt-overlaySize', @@ -532,7 +533,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde values: [], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Rich Text Editor', id: 'dt-richTextEditor', @@ -542,7 +543,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde values: [], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Label', id: 'dt-label', @@ -552,7 +553,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde values: [], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Integer', id: 'dt-integer', @@ -562,7 +563,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde values: [], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Decimal', id: 'dt-decimal', @@ -572,7 +573,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde values: [], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'User Picker', id: 'dt-userPicker', @@ -582,7 +583,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde values: [], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Member Picker', id: 'dt-memberPicker', @@ -592,7 +593,7 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde values: [], }, { - $type: 'data-type', + $type: '', type: 'data-type', name: 'Member Group Picker', id: 'dt-memberGroupPicker', @@ -603,6 +604,13 @@ export const data: Array<(DataTypeResponseModel & { type: 'data-type' }) | Folde }, ]; +const createDataTypeItem = (item: DataTypeResponseModel | FolderTreeItemResponseModel): DataTypeItemResponseModel => { + return { + id: item.id, + name: item.name, + }; +}; + // Temp mocked database // TODO: all properties are optional in the server schema. I don't think this is correct. // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -622,9 +630,9 @@ class UmbDataTypeData extends UmbEntityData createFolderTreeItem(item)); } - getTreeItem(ids: Array): Array { + getItems(ids: Array): Array { const items = this.data.filter((item) => ids.includes(item.id ?? '')); - return items.map((item) => createFolderTreeItem(item)); + return items.map((item) => createDataTypeItem(item)); } createFolder(folder: CreateFolderRequestModel & { id: string | undefined }) { @@ -632,8 +640,7 @@ class UmbDataTypeData extends UmbEntityData = [ defaultTemplateId: null, id: 'all-property-editors-document-type-id', alias: 'blogPost', - name: 'All Property Editors Document Type Name', + name: 'All property editors document type', description: null, icon: 'umb:item-arrangement', allowedAsRoot: true, diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/data/entity.data.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/data/entity.data.ts index d3a7356430..19f132d492 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/data/entity.data.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/data/entity.data.ts @@ -44,6 +44,9 @@ export class UmbEntityData extends UmbData { } move(ids: Array, destinationKey: string) { + const destinationItem = this.getById(destinationKey); + if (!destinationItem) throw new Error(`Destination item with key ${destinationKey} not found`); + const items = this.getByIds(ids); const movedItems = items.map((item) => { return { @@ -53,7 +56,8 @@ export class UmbEntityData extends UmbData { }); movedItems.forEach((movedItem) => this.updateData(movedItem)); - return movedItems; + destinationItem.hasChildren = true; + this.updateData(destinationItem); } trash(ids: Array) { diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/data/languages.data.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/data/languages.data.ts index 142e8b426d..5d0be1a5f6 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/data/languages.data.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/data/languages.data.ts @@ -16,6 +16,10 @@ class UmbLanguagesData extends UmbData { return this.data.find((item) => item.isoCode === isoCode); } + getItems(isoCodes: Array) { + return this.data.filter((item) => isoCodes.indexOf(item.isoCode || '') !== -1); + } + insert(language: LanguageResponseModel) { const foundIndex = this.data.findIndex((item) => item.isoCode === language.isoCode); diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/data/utils.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/data/utils.ts index 5c847f47e8..527d5c6304 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/data/utils.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/data/utils.ts @@ -25,6 +25,7 @@ export const createEntityTreeItem = (item: any): EntityTreeItemResponseModel => export const createFolderTreeItem = (item: any): FolderTreeItemResponseModel => { return { ...createEntityTreeItem(item), + $type: 'FolderTreeItemResponseModel', isFolder: item.isFolder, }; }; diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/index.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/index.ts index fb46e23ffd..3cd1dc2d02 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/index.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/index.ts @@ -2,5 +2,6 @@ import { folderHandlers } from './folder.handlers'; import { treeHandlers } from './tree.handlers'; import { detailHandlers } from './detail.handlers'; import { itemHandlers } from './item.handlers'; +import { moveHandlers } from './move.handlers'; -export const handlers = [...treeHandlers, ...itemHandlers, ...folderHandlers, ...detailHandlers]; +export const handlers = [...treeHandlers, ...itemHandlers, ...folderHandlers, ...moveHandlers, ...detailHandlers]; diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/item.handlers.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/item.handlers.ts index 24d17c79e0..5601aae644 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/item.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/item.handlers.ts @@ -7,7 +7,7 @@ export const itemHandlers = [ rest.get(umbracoPath(`${slug}/item`), (req, res, ctx) => { const ids = req.url.searchParams.getAll('id'); if (!ids) return; - const items = umbDataTypeData.getTreeItem(ids); + const items = umbDataTypeData.getItems(ids); return res(ctx.status(200), ctx.json(items)); }), ]; diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/move.handlers.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/move.handlers.ts new file mode 100644 index 0000000000..daf980630f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/move.handlers.ts @@ -0,0 +1,18 @@ +import { rest } from 'msw'; +import { umbDataTypeData } from '../../data/data-type.data'; +import { slug } from './slug'; +import { umbracoPath } from '@umbraco-cms/backoffice/utils'; + +export const moveHandlers = [ + rest.post(umbracoPath(`${slug}/:id/move`), async (req, res, ctx) => { + const id = req.params.id as string; + if (!id) return; + + const data = await req.json(); + if (!data) return; + + umbDataTypeData.move([id], data.targetId); + + return res(ctx.status(200)); + }), +]; diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/language.handlers.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/language.handlers.ts index 41158abe5b..b6e4557351 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/language.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/language.handlers.ts @@ -5,6 +5,13 @@ import { umbracoPath } from '@umbraco-cms/backoffice/utils'; // TODO: add schema export const handlers = [ + rest.get(umbracoPath('/language/item'), (req, res, ctx) => { + const isoCodes = req.url.searchParams.getAll('isoCode'); + if (!isoCodes) return; + const items = umbLanguagesData.getItems(isoCodes); + return res(ctx.status(200), ctx.json(items)); + }), + rest.get(umbracoPath('/language'), (req, res, ctx) => { const skip = req.url.searchParams.get('skip'); const skipNumber = skip ? Number.parseInt(skip) : undefined; diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/media.handlers.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/media.handlers.ts index efb5a91845..dc28dbd021 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/media.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/media.handlers.ts @@ -26,8 +26,8 @@ export const handlers = [ rest.post('/umbraco/management/api/v1/media/move', async (req, res, ctx) => { const data = await req.json(); if (!data) return; - const moved = umbMediaData.move(data.ids, data.destination); - return res(ctx.status(200), ctx.json(moved)); + umbMediaData.move(data.ids, data.destination); + return res(ctx.status(200)); }), rest.post('/umbraco/management/api/v1/media/trash', async (req, res, ctx) => { diff --git a/src/Umbraco.Web.UI.Client/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json index 31cdde98e2..e1ba304d5b 100644 --- a/src/Umbraco.Web.UI.Client/tsconfig.json +++ b/src/Umbraco.Web.UI.Client/tsconfig.json @@ -40,6 +40,7 @@ "@umbraco-cms/backoffice/store": ["libs/store"], "@umbraco-cms/backoffice/utils": ["libs/utils"], "@umbraco-cms/backoffice/workspace": ["libs/workspace"], + "@umbraco-cms/backoffice/picker-input": ["libs/picker-input"], "@umbraco-cms/internal/lit-element": ["src/core/lit-element"], "@umbraco-cms/internal/modal": ["src/core/modal"], "@umbraco-cms/internal/router": ["src/core/router"], diff --git a/src/Umbraco.Web.UI.Client/utils/json-schema/test-package.json b/src/Umbraco.Web.UI.Client/utils/json-schema/test-package.json new file mode 100644 index 0000000000..34edf0f9e9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/utils/json-schema/test-package.json @@ -0,0 +1,22 @@ +{ + "$schema": "../../types/umbraco-package-schema.json", + "name": "My Package", + "version": "1.0.0", + "extensions": [ + { + "name": "My Dashboard", + "alias": "myDashboard", + + "weight": -10, + "elementName": "my-dashboard", + "js": "js/my-dashboard.js", + + "type": "dashboard", + "meta": { + "label": "My Dashboard", + "pathname": "my-dashboard", + "sections": ["Umb.Section.Content"] + } + } + ] +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/utils/move-libs.js b/src/Umbraco.Web.UI.Client/utils/move-libs.js index 59c3f7d044..76c29dade7 100644 --- a/src/Umbraco.Web.UI.Client/utils/move-libs.js +++ b/src/Umbraco.Web.UI.Client/utils/move-libs.js @@ -3,28 +3,27 @@ // Example: import { Foo } from '@umbraco-cms/backoffice/element' -> import { Foo } from './element' // This is needed because the d.ts files are not in the same folder as the source files // and the absolute paths are not valid when the d.ts files are copied to the dist folder -// This is only used when building the d.ts files +// This is only used when building the d.ts files. // -// Usage: node utils/transform-dts.js +// This script also copies the package.json and README.md files to the dist/libs folder +// and the umbraco-package-schema.json file to the Umbraco.Web.UI.New folder // -// Note: This script is not used in the build process, it is only used to transform the d.ts files -// when the d.ts files are copied to the dist folder - -// Note: Updated to help copy the two JSON files generated from webcomponant analyzer tool -// One is specific to VSCode HTMLCutomData for intellisense and the other is a more broad format used in storybook etc +// Usage: node utils/move-libs.js import { readdirSync, readFileSync, writeFileSync, cpSync, mkdirSync } from 'fs'; -const rootDir = './'; const srcDir = './libs'; const inputDir = './dist/libs'; const outputDir = '../Umbraco.Cms.StaticAssets/wwwroot/umbraco/backoffice/libs'; +const executableDir = '../Umbraco.Web.UI.New'; // Copy package files cpSync(`${srcDir}/package.json`, `${inputDir}/package.json`, { recursive: true }); +console.log(`Copied ${srcDir}/package.json to ${inputDir}/package.json`); cpSync(`${srcDir}/README.md`, `${inputDir}/README.md`, { recursive: true }); -cpSync(`${rootDir}/custom-elements.json`, `${inputDir}/custom-elements.json`, { recursive: true }); -cpSync(`${rootDir}/vscode-html-custom-data.json`, `${inputDir}/vscode-html-custom-data.json`, { recursive: true }); +console.log(`Copied ${srcDir}/README.md to ${inputDir}/README.md`); +cpSync(`${inputDir}/umbraco-package-schema.json`, `${executableDir}/umbraco-json-schema.json`, { recursive: true }); +console.log(`Copied ${inputDir}/umbraco-package-schema.json to ${executableDir}/umbraco-package-schema.json`); const libs = readdirSync(inputDir); @@ -38,6 +37,8 @@ try { // Transform all .d.ts files and copy all other files to the output folder libs.forEach(lib => { + if (lib.endsWith('.js') === false && lib.endsWith('.js.map') === false) return; + console.log(`Transforming ${lib}`); const dtsFile = `${inputDir}/${lib}`; diff --git a/src/Umbraco.Web.UI.Client/vite.cms.config.ts b/src/Umbraco.Web.UI.Client/vite.cms.config.ts index 2d0006a27b..a2ac17fd86 100644 --- a/src/Umbraco.Web.UI.Client/vite.cms.config.ts +++ b/src/Umbraco.Web.UI.Client/vite.cms.config.ts @@ -1,9 +1,9 @@ import { defineConfig } from 'vite'; +import { viteStaticCopy } from 'vite-plugin-static-copy'; -import config from './vite.config'; +import { plugins } from './vite.config'; export default defineConfig({ - ...config, build: { lib: { entry: 'src/app.ts', @@ -11,12 +11,13 @@ export default defineConfig({ fileName: 'main', }, rollupOptions: { - external: [/^@umbraco-cms\/backoffice\//] + external: [/^@umbraco-cms\/backoffice\//], }, outDir: '../Umbraco.Cms.StaticAssets/wwwroot/umbraco/backoffice', emptyOutDir: false, sourcemap: true, }, base: '/umbraco/backoffice/', - mode: 'production' + mode: 'production', + plugins: [...plugins], }); diff --git a/src/Umbraco.Web.UI.Client/vite.config.ts b/src/Umbraco.Web.UI.Client/vite.config.ts index a9a8182fc9..28f834cc03 100644 --- a/src/Umbraco.Web.UI.Client/vite.config.ts +++ b/src/Umbraco.Web.UI.Client/vite.config.ts @@ -1,25 +1,23 @@ -import { defineConfig } from 'vite'; +import { defineConfig, PluginOption } from 'vite'; import { viteStaticCopy } from 'vite-plugin-static-copy'; import viteTSConfigPaths from 'vite-tsconfig-paths'; +export const plugins: PluginOption[] = [ + viteStaticCopy({ + targets: [ + { + src: 'public-assets/icons/*.js', + dest: 'icons', + }, + ], + }), + viteTSConfigPaths(), +]; + // https://vitejs.dev/config/ export default defineConfig({ build: { sourcemap: true, }, - plugins: [ - viteStaticCopy({ - targets: [ - { - src: 'public-assets/icons/*.js', - dest: 'icons', - }, - { - src: 'public-assets/App_Plugins/*.js', - dest: 'App_Plugins', - }, - ], - }), - viteTSConfigPaths(), - ], + plugins, }); diff --git a/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs b/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs index 2aa8016715..8f411f5ca9 100644 --- a/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs +++ b/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs @@ -56,10 +56,11 @@ export default { '@umbraco-cms/backoffice/store': './libs/store/index.ts', '@umbraco-cms/backoffice/utils': './libs/utils/index.ts', '@umbraco-cms/backoffice/workspace': './libs/workspace/index.ts', + '@umbraco-cms/backoffice/picker-input': './libs/picker-input/index.ts', '@umbraco-cms/internal/lit-element': './src/core/lit-element/index.ts', '@umbraco-cms/internal/modal': './src/core/modal/index.ts', '@umbraco-cms/internal/router': './src/core/router/index.ts', - '@umbraco-cms/internal/test-utils': './utils/test-utils.ts' + '@umbraco-cms/internal/test-utils': './utils/test-utils.ts', }, }, },