diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 73eef9f83d..6bc051b292 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -34,7 +34,6 @@ "./document-blueprint": "./dist-cms/packages/documents/document-blueprints/index.js", "./document-type": "./dist-cms/packages/documents/document-types/index.js", "./document": "./dist-cms/packages/documents/documents/index.js", - "./dynamic-root": "./dist-cms/packages/dynamic-root/index.js", "./entity": "./dist-cms/packages/core/entity/index.js", "./entity-action": "./dist-cms/packages/core/entity-action/index.js", "./entity-bulk-action": "./dist-cms/packages/core/entity-bulk-action/index.js", @@ -70,6 +69,7 @@ "./resources": "./dist-cms/packages/core/resources/index.js", "./router": "./dist-cms/packages/core/router/index.js", "./section": "./dist-cms/packages/core/section/index.js", + "./settings": "./dist-cms/packages/settings/index.js", "./server-file-system": "./dist-cms/packages/core/server-file-system/index.js", "./sorter": "./dist-cms/packages/core/sorter/index.js", "./static-file": "./dist-cms/packages/static-file/index.js", diff --git a/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts b/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts index c85cf0ed4f..45186a691a 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts @@ -16,7 +16,6 @@ const CORE_PACKAGES = [ import('../../packages/data-type/umbraco-package.js'), import('../../packages/dictionary/umbraco-package.js'), import('../../packages/documents/umbraco-package.js'), - import('../../packages/dynamic-root/umbraco-package.js'), import('../../packages/health-check/umbraco-package.js'), import('../../packages/language/umbraco-package.js'), import('../../packages/log-viewer/umbraco-package.js'), diff --git a/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-main.element.ts b/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-main.element.ts index 0eddd8fbc8..d94257088e 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-main.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-main.element.ts @@ -1,7 +1,7 @@ import type { UmbBackofficeContext } from '../backoffice.context.js'; import { UMB_BACKOFFICE_CONTEXT } from '../backoffice.context.js'; import { css, html, customElement, state, nothing } from '@umbraco-cms/backoffice/external/lit'; -import { UmbSectionContext, UMB_SECTION_CONTEXT } from '@umbraco-cms/backoffice/section'; +import { UmbSectionContext, UMB_SECTION_CONTEXT, UMB_SECTION_PATH_PATTERN } from '@umbraco-cms/backoffice/section'; import type { UmbRoute, UmbRouterSlotChangeEvent } from '@umbraco-cms/backoffice/router'; import type { ManifestSection, UmbSectionElement } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbExtensionManifestInitializer } from '@umbraco-cms/backoffice/extension-api'; @@ -16,7 +16,6 @@ export class UmbBackofficeMainElement extends UmbLitElement { @state() private _sections: Array> = []; - private _routePrefix = 'section/'; private _backofficeContext?: UmbBackofficeContext; private _sectionContext?: UmbSectionContext; @@ -56,7 +55,7 @@ export class UmbBackofficeMainElement extends UmbLitElement { } else { return { alias: section.alias, - path: this._routePrefix + (section.manifest as ManifestSection).meta.pathname, + path: UMB_SECTION_PATH_PATTERN.generateLocal({ sectionName: section.manifest!.meta.pathname }), component: () => createExtensionElement(section.manifest!, 'umb-section-default'), setup: (component) => { (component as UmbSectionElement).manifest = section.manifest as ManifestSection; @@ -66,10 +65,18 @@ export class UmbBackofficeMainElement extends UmbLitElement { }); if (this._sections.length > 0) { + const fallbackSectionPath = UMB_SECTION_PATH_PATTERN.generateLocal({ + sectionName: this._sections[0].manifest!.meta.pathname, + }); this._routes.push({ alias: '__redirect', path: '/', - redirectTo: 'section/content', + redirectTo: fallbackSectionPath, + }); + this._routes.push({ + alias: '__redirect', + path: '/section/', + redirectTo: fallbackSectionPath, }); } @@ -78,7 +85,9 @@ export class UmbBackofficeMainElement extends UmbLitElement { private _onRouteChange = async (event: UmbRouterSlotChangeEvent) => { const currentPath = event.target.localActiveViewPath || ''; - const section = this._sections.find((s) => this._routePrefix + s.manifest?.meta.pathname === currentPath); + const section = this._sections.find( + (s) => UMB_SECTION_PATH_PATTERN.generateLocal({ sectionName: s.manifest!.meta.pathname }) === currentPath, + ); if (!section) return; await section.asPromise(); if (section.manifest) { diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.ts index e4cbaacc6e..e737275ae2 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.ts @@ -91,9 +91,10 @@ export class UmbContextConsumer('Umb.Modal.Workspace', { modal: { type: 'sidebar', @@ -13,4 +13,4 @@ export const UMB_BLOCK_GRID_AREA_TYPE_WORKSPACE_MODAL = new UmbModalToken< }, data: { entityType: 'block-grid-area-type', preset: {} }, // Recast the type, so the entityType data prop is not required: -}) as UmbModalToken, UmbWorkspaceValue>; +}) as UmbModalToken, UmbWorkspaceModalValue>; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-type-configuration/property-editor-ui-block-grid-type-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-type-configuration/property-editor-ui-block-grid-type-configuration.element.ts index 8129fb2739..b309c9ceae 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-type-configuration/property-editor-ui-block-grid-type-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-type-configuration/property-editor-ui-block-grid-type-configuration.element.ts @@ -19,7 +19,11 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UMB_BLOCK_GRID_TYPE, type UmbBlockGridTypeGroupType } from '@umbraco-cms/backoffice/block-grid'; import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; -import { UMB_PROPERTY_DATASET_CONTEXT, type UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property'; +import { + UMB_PROPERTY_CONTEXT, + UMB_PROPERTY_DATASET_CONTEXT, + type UmbPropertyDatasetContext, +} from '@umbraco-cms/backoffice/property'; import { UMB_WORKSPACE_MODAL, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal'; import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; @@ -70,6 +74,9 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement this.#mapValuesToBlockGroups(); } + @state() + public _alias?: string; + @property({ type: Object, attribute: false }) public config?: UmbPropertyEditorConfigCollection; @@ -86,8 +93,13 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement constructor() { super(); - this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, async (instance) => { - this.#datasetContext = instance; + + this.consumeContext(UMB_PROPERTY_CONTEXT, async (context) => { + this._alias = context.getAlias(); + }); + + this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, async (context) => { + this.#datasetContext = context; //this.#observeBlocks(); this.#observeBlockGroups(); }); @@ -203,6 +215,7 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement return html`
${this._notGroupedBlockTypes ? html` {} -export const UMB_BLOCK_GRID_WORKSPACE_MODAL = new UmbModalToken( +export const UMB_BLOCK_GRID_WORKSPACE_MODAL = new UmbModalToken( 'Umb.Modal.Workspace', { modal: { @@ -19,4 +19,4 @@ export const UMB_BLOCK_GRID_WORKSPACE_MODAL = new UmbModalToken, UmbWorkspaceValue>; +) as UmbModalToken, UmbWorkspaceModalValue>; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/workspace/block-list-workspace.modal-token.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/workspace/block-list-workspace.modal-token.ts index 091954dbe9..e86a26f97d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/workspace/block-list-workspace.modal-token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/workspace/block-list-workspace.modal-token.ts @@ -1,5 +1,5 @@ import type { UmbBlockWorkspaceData } from '@umbraco-cms/backoffice/block'; -import type { UmbWorkspaceData, UmbWorkspaceValue } from '@umbraco-cms/backoffice/modal'; +import type { UmbWorkspaceModalData, UmbWorkspaceModalValue } from '@umbraco-cms/backoffice/modal'; import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; export interface UmbBlockListWorkspaceData @@ -7,7 +7,7 @@ export interface UmbBlockListWorkspaceData index: number; }> {} -export const UMB_BLOCK_LIST_WORKSPACE_MODAL = new UmbModalToken( +export const UMB_BLOCK_LIST_WORKSPACE_MODAL = new UmbModalToken( 'Umb.Modal.Workspace', { modal: { @@ -17,4 +17,4 @@ export const UMB_BLOCK_LIST_WORKSPACE_MODAL = new UmbModalToken, UmbWorkspaceValue>; +) as UmbModalToken, UmbWorkspaceModalValue>; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/workspace/block-rte-workspace.modal-token.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/workspace/block-rte-workspace.modal-token.ts index cdeb480d58..da51f16a31 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/workspace/block-rte-workspace.modal-token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/workspace/block-rte-workspace.modal-token.ts @@ -1,5 +1,5 @@ import type { UmbBlockWorkspaceData } from '@umbraco-cms/backoffice/block'; -import type { UmbWorkspaceData, UmbWorkspaceValue } from '@umbraco-cms/backoffice/modal'; +import type { UmbWorkspaceModalData, UmbWorkspaceModalValue } from '@umbraco-cms/backoffice/modal'; import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; export interface UmbBlockRTEWorkspaceData @@ -7,7 +7,7 @@ export interface UmbBlockRTEWorkspaceData index: number; }> {} -export const UMB_BLOCK_RTE_WORKSPACE_MODAL = new UmbModalToken( +export const UMB_BLOCK_RTE_WORKSPACE_MODAL = new UmbModalToken( 'Umb.Modal.Workspace', { modal: { @@ -17,4 +17,4 @@ export const UMB_BLOCK_RTE_WORKSPACE_MODAL = new UmbModalToken, UmbWorkspaceValue>; +) as UmbModalToken, UmbWorkspaceModalValue>; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts index 64a4dd422b..ef77263298 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts @@ -1,6 +1,6 @@ import type { UmbBlockTypeCardElement } from '../block-type-card/index.js'; import type { UmbBlockTypeBaseModel, UmbBlockTypeWithGroupKey } from '../../types.js'; -import { UMB_MODAL_MANAGER_CONTEXT, umbConfirmModal } from '@umbraco-cms/backoffice/modal'; +import { UmbModalRouteRegistrationController, umbConfirmModal } from '@umbraco-cms/backoffice/modal'; import '../block-type-card/index.js'; import { css, html, customElement, property, state, repeat } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @@ -29,9 +29,11 @@ export class UmbInputBlockTypeElement< this.dispatchEvent(new CustomEvent('change', { detail: { item } })); }, onEnd: () => { + // TODO: Investigate if onEnd is called when a container move has been performed, if not then I would say it should be. [NL] this.dispatchEvent(new CustomEvent('change', { detail: { moveComplete: true } })); }, }); + #elementPickerModal; @property({ type: Array, attribute: false }) public set value(items) { @@ -42,9 +44,20 @@ export class UmbInputBlockTypeElement< return this._items; } + @property({ type: String }) + public set propertyAlias(value: string | undefined) { + this.#elementPickerModal.setUniquePathValue('propertyAlias', value); + } + public get propertyAlias(): string | undefined { + return undefined; + } + @property({ type: String }) workspacePath?: string; + @state() + private _pickerPath?: string; + @state() private _items: Array = []; @@ -60,31 +73,44 @@ export class UmbInputBlockTypeElement< this.#filter = value as Array; }); }); - } - async create() { - const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); + this.#elementPickerModal = new UmbModalRouteRegistrationController(this, UMB_DOCUMENT_TYPE_PICKER_MODAL) + .addUniquePaths(['propertyAlias']) + .onSetup(() => { + return { + data: { + hideTreeRoot: true, + multiple: false, + createAction: { + extendWithPathParams: { + parentUnique: null, + presetAlias: 'element', + }, + }, + pickableFilter: (docType) => + // Only pick elements: + docType.isElement && + // Prevent picking the an already used element type: + this.#filter && + this.#filter.find((x) => x.contentElementTypeKey === docType.unique) === undefined, + }, + value: { + selection: [], + }, + }; + }) + .onSubmit((value) => { + const selectedElementType = value.selection[0]; - // TODO: Make as mode for the Picker Modal, so the click to select immediately submits the modal(And in that mode we do not want to see a Submit button). - const modalContext = modalManager.open(this, UMB_DOCUMENT_TYPE_PICKER_MODAL, { - data: { - hideTreeRoot: true, - multiple: false, - pickableFilter: (docType) => - // Only pick elements: - docType.isElement && - // Prevent picking the an already used element type: - this.#filter && - this.#filter.find((x) => x.contentElementTypeKey === docType.unique) === undefined, - }, - }); - - const modalValue = await modalContext?.onSubmit(); - const selectedElementType = modalValue.selection[0]; - - if (selectedElementType) { - this.dispatchEvent(new CustomEvent('create', { detail: { contentElementTypeKey: selectedElementType } })); - } + if (selectedElementType) { + this.dispatchEvent(new CustomEvent('create', { detail: { contentElementTypeKey: selectedElementType } })); + } + }) + .observeRouteBuilder((routeBuilder) => { + const oldPath = this._pickerPath; + this._pickerPath = routeBuilder({}); + this.requestUpdate('_pickerPath', oldPath); + }); } deleteItem(contentElementTypeKey: string) { @@ -131,12 +157,14 @@ export class UmbInputBlockTypeElement< }; #renderButton() { - return html` - this.create()} label="open"> - - Add - - `; + return this._pickerPath + ? html` + + + Add + + ` + : null; } static styles = [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.modal-token.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.modal-token.ts index 0a2d21ccc1..5bf06ad44f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.modal-token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.modal-token.ts @@ -1,11 +1,11 @@ -import type { UmbWorkspaceData, UmbWorkspaceValue } from '@umbraco-cms/backoffice/modal'; +import type { UmbWorkspaceModalData, UmbWorkspaceModalValue } from '@umbraco-cms/backoffice/modal'; import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; -export interface UmbBlockWorkspaceData extends UmbWorkspaceData { +export interface UmbBlockWorkspaceData extends UmbWorkspaceModalData { originData: OriginDataType; } -export const UMB_BLOCK_WORKSPACE_MODAL = new UmbModalToken( +export const UMB_BLOCK_WORKSPACE_MODAL = new UmbModalToken( 'Umb.Modal.Workspace', { modal: { @@ -15,4 +15,4 @@ export const UMB_BLOCK_WORKSPACE_MODAL = new UmbModalToken, UmbWorkspaceValue>; +) as UmbModalToken, UmbWorkspaceModalValue>; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts index 332473da09..368ad64743 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts @@ -23,7 +23,6 @@ export * from './input-multi-url/index.js'; export * from './input-number-range/index.js'; export * from './input-radio-button-list/index.js'; export * from './input-slider/index.js'; -export * from './input-tree-picker-source/index.js'; export * from './input-toggle/index.js'; export * from './input-upload-field/index.js'; export * from './multiple-color-picker-input/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree-picker-source/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree-picker-source/index.ts deleted file mode 100644 index 7f9792d5a4..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree-picker-source/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './input-tree-picker-source.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/index.ts index 15bf3de62f..857ded731a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/index.ts @@ -1,2 +1,3 @@ export * from './composition-picker/composition-picker-modal.token.js'; export * from './property-type-settings/property-type-settings-modal.token.js'; +export * from './property-type-settings/property-type-settings-modal.context-token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/property-type-settings/property-type-settings-modal.context-token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/property-type-settings/property-type-settings-modal.context-token.ts new file mode 100644 index 0000000000..cbbc765325 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/property-type-settings/property-type-settings-modal.context-token.ts @@ -0,0 +1,12 @@ +import type { UmbPropertyTypeWorkspaceContext } from './property-type-settings-modal.context.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; +import type { UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; + +export const UMB_PROPERTY_TYPE_WORKSPACE_CONTEXT = new UmbContextToken< + UmbWorkspaceContext, + UmbPropertyTypeWorkspaceContext +>( + 'UmbWorkspaceContext', + undefined, + (context): context is UmbPropertyTypeWorkspaceContext => context.getEntityType() === 'property-type', +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/property-type-settings/property-type-settings-modal.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/property-type-settings/property-type-settings-modal.context.ts index 76c94f8483..708329c498 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/property-type-settings/property-type-settings-modal.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/property-type-settings/property-type-settings-modal.context.ts @@ -1,7 +1,12 @@ +import { UMB_PROPERTY_TYPE_WORKSPACE_CONTEXT } from './property-type-settings-modal.context-token.js'; +import type { + UmbPropertyTypeSettingsModalData, + UmbPropertyTypeSettingsModalValue, +} from './property-type-settings-modal.token.js'; import type { UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; +import { UMB_MODAL_CONTEXT, type UmbModalContext } from '@umbraco-cms/backoffice/modal'; export const UMB_PROPERTY_TYPE_WORKSPACE_ALIAS = 'Umb.Workspace.PropertyType'; @@ -12,8 +17,14 @@ export class UmbPropertyTypeWorkspaceContext extends UmbContextBase implements UmbWorkspaceContext { + #modal?: UmbModalContext; + constructor(host: UmbControllerHost) { super(host, UMB_PROPERTY_TYPE_WORKSPACE_CONTEXT); + + this.consumeContext(UMB_MODAL_CONTEXT, (context) => { + this.#modal = context as UmbModalContext; + }); } get workspaceAlias() { @@ -21,21 +32,16 @@ export class UmbPropertyTypeWorkspaceContext } getUnique() { - return undefined; + return this.#modal?.getValue()?.alias ?? ''; } getEntityType() { return 'property-type'; } + + getLabel() { + return this.#modal?.getValue()?.name ?? ''; + } } export default UmbPropertyTypeWorkspaceContext; - -export const UMB_PROPERTY_TYPE_WORKSPACE_CONTEXT = new UmbContextToken< - UmbWorkspaceContext, - UmbPropertyTypeWorkspaceContext ->( - 'UmbWorkspaceContext', - undefined, - (context): context is UmbPropertyTypeWorkspaceContext => context.getEntityType() === 'property-type', -); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/property-type-settings/property-type-settings-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/property-type-settings/property-type-settings-modal.element.ts index ff53f2f34e..463ef9497e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/property-type-settings/property-type-settings-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/property-type-settings/property-type-settings-modal.element.ts @@ -64,8 +64,7 @@ export class UmbPropertyTypeSettingsModalElement extends UmbModalBaseElement< super.connectedCallback(); this.consumeContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT, (instance) => { - if (!this.data?.contentTypeId) return; - if (instance.getUnique() !== this.data.contentTypeId) { + if (!this.data?.contentTypeId || instance.getUnique() !== this.data.contentTypeId) { // We can currently only edit properties that are part of a content type workspace, which has to be present outside of the modal. [NL] throw new Error( 'The content type workspace context does not match the content type id of the property type settings modal.', diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/structure/content-type-structure-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/structure/content-type-structure-manager.class.ts index 5e00bfd186..26d96f45a8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/structure/content-type-structure-manager.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/structure/content-type-structure-manager.class.ts @@ -120,18 +120,18 @@ export class UmbContentTypeStructureManager< */ public async create(parentUnique: string | null) { const contentType = this.getOwnerContentType(); - if (!contentType || !contentType.unique) return false; + if (!contentType || !contentType.unique) { + throw new Error('Could not find the Content Type to create'); + } const { data } = await this.#repository.create(contentType, parentUnique); - if (!data) return false; + if (!data) return Promise.reject(); // Update state with latest version: this.#contentTypes.updateOne(contentType.unique, data); // Start observe the new content type in the store, as we did not do that when it was a scaffold/local-version. this._observeContentType(data); - - return true; } private async _loadContentTypeCompositions(contentType: T) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/content-type-workspace-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/content-type-workspace-context.interface.ts index db4bbf3419..1405ff101c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/content-type-workspace-context.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/content-type-workspace-context.interface.ts @@ -8,6 +8,7 @@ export interface UmbContentTypeWorkspaceContext; + getName(): string | undefined; readonly alias: Observable; readonly description: Observable; readonly icon: Observable; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/index.ts index 1a9af36bce..352572934d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/index.ts @@ -1,2 +1,3 @@ export type * from './content-type-workspace-context.interface.js'; export * from './content-type-workspace.context-token.js'; +export * from './views/design/content-type-design-editor-property.context-token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.context-token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.context-token.ts new file mode 100644 index 0000000000..931d48e3d6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.context-token.ts @@ -0,0 +1,4 @@ +import type { UmbPropertyTypeContext } from './content-type-design-editor-property.context.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const UMB_PROPERTY_TYPE_CONTEXT = new UmbContextToken('UmbPropertyTypeContext'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.context.ts new file mode 100644 index 0000000000..87234ec3f7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.context.ts @@ -0,0 +1,34 @@ +import { UMB_PROPERTY_TYPE_CONTEXT } from './content-type-design-editor-property.context-token.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; +import { UmbStringState } from '@umbraco-cms/backoffice/observable-api'; + +export class UmbPropertyTypeContext extends UmbContextBase { + #alias = new UmbStringState(undefined); + public readonly alias = this.#alias.asObservable(); + #label = new UmbStringState(undefined); + public readonly label = this.#label.asObservable(); + + constructor(host: UmbControllerHost) { + super(host, UMB_PROPERTY_TYPE_CONTEXT); + } + + public setAlias(alias: string | undefined): void { + this.#alias.setValue(alias); + } + public getAlias(): string | undefined { + return this.#alias.getValue(); + } + public setLabel(label: string | undefined): void { + this.#label.setValue(label); + } + public getLabel(): string | undefined { + return this.#label.getValue(); + } + + public destroy(): void { + super.destroy(); + this.#alias.destroy(); + this.#label.destroy(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts index 389040c68b..4a2a857964 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts @@ -1,3 +1,4 @@ +import { UmbPropertyTypeContext } from './content-type-design-editor-property.context.js'; import { UmbDataTypeDetailRepository } from '@umbraco-cms/backoffice/data-type'; import type { UUIInputElement } from '@umbraco-cms/backoffice/external/uui'; import { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; @@ -25,6 +26,7 @@ export class UmbContentTypeDesignEditorPropertyElement extends UmbLitElement { #dataTypeDetailRepository = new UmbDataTypeDetailRepository(this); #settingsModal; #dataTypeUnique?: string; + #context = new UmbPropertyTypeContext(this); @property({ attribute: false }) public set propertyStructureHelper(value: UmbContentTypePropertyStructureHelper | undefined) { @@ -51,6 +53,8 @@ export class UmbContentTypeDesignEditorPropertyElement extends UmbLitElement { const oldValue = this._property; if (value === oldValue) return; this._property = value; + this.#context.setAlias(value?.alias); + this.#context.setLabel(value?.name); this.#checkInherited(); this.#settingsModal.setUniquePathValue('propertyId', value?.id); this.#setDataType(this._property?.dataType?.unique); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal-manager.context.ts index eb74e7deca..0764a232c8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal-manager.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal-manager.context.ts @@ -38,7 +38,7 @@ export class UmbModalManagerContext extends UmbContextBase, >( diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts index 79c999d1ed..c8751d8b3e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts @@ -6,6 +6,7 @@ import type { UUIModalSidebarSize } from '@umbraco-cms/backoffice/external/uui'; import { UmbId } from '@umbraco-cms/backoffice/id'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import { type UmbDeepPartialObject, umbDeepMerge } from '@umbraco-cms/backoffice/utils'; export interface UmbModalRejectReason { type: string; @@ -13,7 +14,9 @@ export interface UmbModalRejectReason { export type UmbModalContextClassArgs< ModalAliasType extends string | UmbModalToken, - ModalAliasTypeAsToken extends UmbModalToken = ModalAliasType extends UmbModalToken ? ModalAliasType : UmbModalToken, + ModalAliasTypeAsToken extends UmbModalToken = ModalAliasType extends UmbModalToken + ? ModalAliasType + : UmbModalToken, > = { router?: IRouterSlot | null; data?: ModalAliasTypeAsToken['DATA']; @@ -22,7 +25,10 @@ export type UmbModalContextClassArgs< }; // TODO: consider splitting this into two separate handlers -export class UmbModalContext extends UmbControllerBase { +export class UmbModalContext< + ModalPreset extends { [key: string]: any } = { [key: string]: any }, + ModalValue = any, +> extends UmbControllerBase { // #submitPromise: Promise; #submitResolver?: (value: ModalValue) => void; @@ -60,7 +66,13 @@ export class UmbModalContext, defaultData) as ModalPreset) + : // otherwise pick one of them: + (args.data as ModalPreset) ?? defaultData, + ); const initValue = args.value ?? (this.alias instanceof UmbModalToken ? (this.alias as UmbModalToken).getDefaultValue() : undefined); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/route-registration/modal-route-registration.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/route-registration/modal-route-registration.controller.ts index b4de970109..87a7b863d2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/route-registration/modal-route-registration.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/route-registration/modal-route-registration.controller.ts @@ -1,25 +1,42 @@ import type { UmbModalToken } from '../token/index.js'; import type { UmbModalConfig, UmbModalContext, UmbModalManagerContext, UmbModalRouteRegistration } from '../index.js'; +import type { UmbModalContextClassArgs } from '../context/modal.context.js'; import { type Params, type IRouterSlot, UMB_ROUTE_CONTEXT, encodeFolderName } from '@umbraco-cms/backoffice/router'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import { UmbId } from '@umbraco-cms/backoffice/id'; +import type { UmbDeepPartialObject } from '@umbraco-cms/backoffice/utils'; export type UmbModalRouteBuilder = (params: { [key: string]: string | number } | null) => string; export type UmbModalRouteSetupReturn = UmbModalTokenValue extends undefined - ? { - modal?: UmbModalConfig; - data: UmbModalTokenData; - value?: UmbModalTokenValue; - } - : { - modal?: UmbModalConfig; - data: UmbModalTokenData; - value: UmbModalTokenValue; - }; -export class UmbModalRouteRegistrationController + ? UmbModalTokenValue extends undefined + ? { + modal?: UmbDeepPartialObject; + data?: UmbDeepPartialObject; + value?: UmbModalTokenValue; + } + : { + modal?: UmbDeepPartialObject; + data?: UmbDeepPartialObject; + value: UmbModalTokenValue; + } + : UmbModalTokenValue extends undefined + ? { + modal?: UmbDeepPartialObject; + data: UmbDeepPartialObject; + value?: UmbModalTokenValue; + } + : { + modal?: UmbDeepPartialObject; + data: UmbDeepPartialObject; + value: UmbModalTokenValue; + }; +export class UmbModalRouteRegistrationController< + UmbModalTokenData extends { [key: string]: any } = { [key: string]: any }, + UmbModalTokenValue = any, + > extends UmbControllerBase implements UmbModalRouteRegistration { @@ -293,8 +310,8 @@ export class UmbModalRouteRegistrationController>; + args.modal!.key = this.#key; this.#modalContext = modalManagerContext.open(this, this.#modalAlias, args); this.#modalContext.onSubmit().then(this.#onSubmit, this.#onReject); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/route-registration/modal-route-registration.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/route-registration/modal-route-registration.interface.ts index e4113c2408..a1af2fbcf7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/route-registration/modal-route-registration.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/route-registration/modal-route-registration.interface.ts @@ -3,7 +3,10 @@ import type { UmbModalContext, UmbModalRouteBuilder } from '../index.js'; import type { UmbModalToken } from '../token/modal-token.js'; import type { IRouterSlot, Params } from '@umbraco-cms/backoffice/router'; -export interface UmbModalRouteRegistration { +export interface UmbModalRouteRegistration< + UmbModalTokenData extends { [key: string]: any } = { [key: string]: any }, + UmbModalTokenValue = any, +> { key: string; alias: UmbModalToken | string; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/modal-token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/modal-token.ts index 8d45fde4b9..61d66ce63e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/modal-token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/modal-token.ts @@ -1,12 +1,18 @@ import type { UmbModalConfig } from '../context/modal-manager.context.js'; -export interface UmbModalTokenDefaults { +export interface UmbModalTokenDefaults< + ModalDataType extends { [key: string]: any } = { [key: string]: any }, + ModalValueType = unknown, +> { modal?: UmbModalConfig; data?: ModalDataType; value?: ModalValueType; } -export class UmbModalToken { +export class UmbModalToken< + ModalDataType extends { [key: string]: any } = { [key: string]: any }, + ModalValueType = unknown, +> { /** * Get the data type of the token's data. * diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/workspace-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/workspace-modal.token.ts index 7806a971be..f5b7edf84c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/workspace-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/workspace-modal.token.ts @@ -1,19 +1,22 @@ import { UmbModalToken } from './modal-token.js'; -export interface UmbWorkspaceData { +export interface UmbWorkspaceModalData { entityType: string; preset: Partial; } // TODO: It would be good with a WorkspaceValueBaseType, to avoid the hardcoded type for unique here: -export type UmbWorkspaceValue = +export type UmbWorkspaceModalValue = | { unique: string; } | undefined; -export const UMB_WORKSPACE_MODAL = new UmbModalToken('Umb.Modal.Workspace', { - modal: { - type: 'sidebar', - size: 'large', +export const UMB_WORKSPACE_MODAL = new UmbModalToken( + 'Umb.Modal.Workspace', + { + modal: { + type: 'sidebar', + size: 'large', + }, }, -}); +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/router/generate-route-path-builder.function.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/router/generate-route-path-builder.function.test.ts index f6de7d9f86..59637c2db8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/router/generate-route-path-builder.function.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/router/generate-route-path-builder.function.test.ts @@ -1,41 +1,41 @@ import { expect } from '@open-wc/testing'; -import { createRoutePathBuilder } from './generate-route-path-builder.function.js'; +import { umbGenerateRoutePathBuilder } from './generate-route-path-builder.function.js'; describe('createRoutePathBuilder', () => { it('should return a function that builds a route path without parameters', () => { - const buildPath = createRoutePathBuilder('test/path'); + const buildPath = umbGenerateRoutePathBuilder('test/path'); expect(buildPath(null)).to.eq('/test/path/'); }); it('should return a function that builds a route path with parameters', () => { - const buildPath = createRoutePathBuilder(':param0/test/:param1/path/:param2'); + const buildPath = umbGenerateRoutePathBuilder(':param0/test/:param1/path/:param2'); expect(buildPath({ param0: 'value0', param1: 'value1', param2: 'value2' })).to.eq( '/value0/test/value1/path/value2/', ); }); it('should convert number parameters to strings', () => { - const buildPath = createRoutePathBuilder('test/:param1/path/:param2'); + const buildPath = umbGenerateRoutePathBuilder('test/:param1/path/:param2'); expect(buildPath({ param1: 123, param2: 456 })).to.eq('/test/123/path/456/'); }); it('should not consider route segments that resembles parameters as parameters', () => { - const buildPath = createRoutePathBuilder('test/uc:store/path'); + const buildPath = umbGenerateRoutePathBuilder('test/uc:store/path'); expect(buildPath({ someOtherParam: 'test' })).to.eq('/test/uc:store/path/'); }); it('should support multiple parameters with the same name', () => { - const buildPath = createRoutePathBuilder('test/:param1/path/:param1'); + const buildPath = umbGenerateRoutePathBuilder('test/:param1/path/:param1'); expect(buildPath({ param1: 'value1' })).to.eq('/test/value1/path/value1/'); }); it('should not consider parameters that are not in the params object', () => { - const buildPath = createRoutePathBuilder('test/:param1/path/:param2'); + const buildPath = umbGenerateRoutePathBuilder('test/:param1/path/:param2'); expect(buildPath({ param1: 'value1' })).to.eq('/test/value1/path/:param2/'); }); it('should support complex objects as parameters with a custom toString method', () => { - const buildPath = createRoutePathBuilder('test/:param1/path/:param2'); + const buildPath = umbGenerateRoutePathBuilder('test/:param1/path/:param2'); const obj = { toString() { return 'value1'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/router/generate-route-path-builder.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/router/generate-route-path-builder.function.ts index 115a6c3f91..24db7b9dc6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/router/generate-route-path-builder.function.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/router/generate-route-path-builder.function.ts @@ -1,21 +1,13 @@ -/* eslint-disable */ +import { type UrlParametersRecord, umbUrlPatternToString } from '../utils/path/url-pattern-to-string.function.js'; import { stripSlash } from '@umbraco-cms/backoffice/external/router-slot'; // This must only include the util to avoid side effects of registering the route element. -const PARAM_IDENTIFIER = /:([^\/]+)/g; - -export function createRoutePathBuilder(path: string) { - return (params: { [key: string]: string | number | { toString: () => string } } | null) => { - return ( - '/' + - stripSlash( - params - ? path.replace(PARAM_IDENTIFIER, (_substring: string, ...args: string[]) => { - // Replace the parameter with the value from the params object or the parameter name if it doesn't exist (args[0] is the parameter name without the colon) - return typeof params[args[0]] !== 'undefined' ? params[args[0]].toString() : `:${args[0]}`; - }) - : path, - ) + - '/' - ); +export function umbGenerateRoutePathBuilder(path: string) { + return (params: UrlParametersRecord | null) => { + return '/' + stripSlash(umbUrlPatternToString(path, params)) + '/'; }; } + +/** + * @deprecated Use `umbGenerateRoutePathBuilder` instead. + */ +export { umbGenerateRoutePathBuilder as umbCreateRoutePathBuilder }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/router/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/router/index.ts index c4b9ff29db..7aeb5ac6ec 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/router/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/router/index.ts @@ -1,8 +1,8 @@ export * from '@umbraco-cms/backoffice/external/router-slot'; export * from './encode-folder-name.function.js'; -export * from './generate-route-path-builder.function.js'; export * from './route.context.js'; export * from './route.interface.js'; export * from './router-slot-change.event.js'; export * from './router-slot-init.event.js'; export * from './router-slot.element.js'; +export * from './path-pattern.class.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/router/path-pattern.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/router/path-pattern.class.ts new file mode 100644 index 0000000000..57dbda10eb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/router/path-pattern.class.ts @@ -0,0 +1,63 @@ +import { umbUrlPatternToString } from '../utils/path/url-pattern-to-string.function.js'; + +export type UmbPathPatternParamsType = { [key: string]: any }; + +// Replaces property union type null with 'null' [NL] +type ReplaceNull = T extends null ? 'null' : T; +export type UmbPathPatternTypeAsEncodedParamsType = { + [P in keyof T]: T[P] extends (infer R)[] ? ReplaceNull[] : ReplaceNull; +}; + +export class UmbPathPattern< + LocalParamsType extends UmbPathPatternParamsType = UmbPathPatternParamsType, + BaseParamsType extends UmbPathPatternParamsType = LocalParamsType, +> { + #local: string; + #base: string; + + /** + * Get the params type of the path pattern + * + * @public + * @type {T} + * @memberOf UmbPathPattern + * @example `typeof MyPathPattern.PARAMS` + */ + readonly PARAMS!: LocalParamsType; + + /** + * Get absolute params type of the path pattern + * + * @public + * @type {T} + * @memberOf UmbPathPattern + * @example `typeof MyPathPattern.ABSOLUTE_PARAMS` + */ + readonly ABSOLUTE_PARAMS!: LocalParamsType & BaseParamsType; + + constructor(localPattern: string, basePath?: UmbPathPattern | string) { + this.#local = localPattern; + basePath = basePath?.toString() ?? ''; + this.#base = basePath.lastIndexOf('/') !== basePath.length - 1 ? basePath + '/' : basePath; + } + + generateLocal(params: LocalParamsType) { + return umbUrlPatternToString(this.#local, params); + } + /** + * generate an absolute path from the path pattern + * @param params + * @param baseParams + * @returns + */ + generateAbsolute(params: LocalParamsType & BaseParamsType) { + return ( + (this.#base.indexOf(':') !== -1 ? umbUrlPatternToString(this.#base, params) : this.#base) + + umbUrlPatternToString(this.#local, params) + ); + } + + toString() { + return this.#local; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/router/route.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/router/route.context.ts index 47576a4e0b..ce7842c4cd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/router/route.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/router/route.context.ts @@ -1,5 +1,5 @@ import type { UmbRoute } from './route.interface.js'; -import { createRoutePathBuilder } from './generate-route-path-builder.function.js'; +import { umbGenerateRoutePathBuilder } from './generate-route-path-builder.function.js'; import type { IRoutingInfo, IRouterSlot } from '@umbraco-cms/backoffice/external/router-slot'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; @@ -70,8 +70,8 @@ export class UmbRouteContext extends UmbContextBase { } #removeModalPath(info: IRoutingInfo) { - // Reset the URL to the routerBasePath + routerActiveLocalPath - const folderToRemove = info.match.match.input; + // Reset the URL to the routerBasePath + routerActiveLocalPath [NL] + const folderToRemove = info.match.fragments.consumed; if (folderToRemove && window.location.href.includes(folderToRemove)) { window.history.pushState({}, '', this.#routerBasePath + '/' + this.#routerActiveLocalPath); } @@ -147,7 +147,7 @@ export class UmbRouteContext extends UmbContextBase { : this.#routerActiveLocalPath + '/' : ''; const localPath = routeBasePath + routeActiveLocalPath + modalRegistration.generateModalPath(); - const urlBuilder = createRoutePathBuilder(localPath); + const urlBuilder = umbGenerateRoutePathBuilder(localPath); modalRegistration._internal_setRouteBuilder(urlBuilder); }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/index.ts index b46d9acc87..51e427cbb7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/index.ts @@ -1,4 +1,5 @@ export * from './components/index.js'; +export * from './paths.js'; export * from './section-default.element.js'; export * from './section-main/index.js'; export * from './section-picker-modal/section-picker-modal.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/paths.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/paths.ts new file mode 100644 index 0000000000..ef9da66864 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/paths.ts @@ -0,0 +1,3 @@ +import { UmbPathPattern } from '../router/path-pattern.class.js'; + +export const UMB_SECTION_PATH_PATTERN = new UmbPathPattern<{ sectionName: string }>('section/:sectionName'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-default.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-default.element.ts index d4af460978..b30d7ac991 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-default.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-default.element.ts @@ -13,6 +13,7 @@ import type { UmbRoute } from '@umbraco-cms/backoffice/router'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UmbExtensionElementInitializer } from '@umbraco-cms/backoffice/extension-api'; import { UmbExtensionsElementInitializer } from '@umbraco-cms/backoffice/extension-api'; +import { UMB_WORKSPACE_PATH_PATTERN } from '@umbraco-cms/backoffice/workspace'; /** * @export @@ -58,7 +59,7 @@ export class UmbSectionDefaultElement extends UmbLitElement implements UmbSectio #createRoutes() { this._routes = [ { - path: 'workspace/:entityType', + path: UMB_WORKSPACE_PATH_PATTERN.toString(), component: () => import('../workspace/workspace.element.js'), setup: (element, info) => { (element as UmbWorkspaceElement).entityType = info.match.params.entityType; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/index.ts deleted file mode 100644 index e8fa7cda1c..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './input-tree/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/input-tree/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/input-tree/index.ts deleted file mode 100644 index 490f084b27..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/input-tree/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './input-tree.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/input-tree/input-tree.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/input-tree/input-tree.stories.ts deleted file mode 100644 index 21f89e9529..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/input-tree/input-tree.stories.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/web-components'; -import './input-tree.element.js'; -import type { UmbInputTreeElement } from './input-tree.element.js'; - -const meta: Meta = { - title: 'Components/Inputs/Tree', - component: 'umb-input-tree', -}; - -export default meta; -type Story = StoryObj; - -export const Overview: Story = { - args: {}, -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/index.ts index f12bb43497..a6376a6b04 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/index.ts @@ -1,6 +1,5 @@ import { UmbRequestReloadTreeItemChildrenEvent } from './reload-tree-item-children/index.js'; -export * from './components/index.js'; export * from './tree-item/index.js'; export * from './default/index.js'; export * from './data/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker/tree-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker/tree-picker-modal.element.ts index c118899df7..0ba176209b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker/tree-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker/tree-picker-modal.element.ts @@ -1,7 +1,11 @@ import type { UmbTreeSelectionConfiguration } from '../types.js'; import type { UmbTreePickerModalData, UmbTreePickerModalValue } from './tree-picker-modal.token.js'; -import { html, customElement, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; -import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; +import { html, customElement, state, ifDefined, nothing } from '@umbraco-cms/backoffice/external/lit'; +import { + UMB_WORKSPACE_MODAL, + UmbModalBaseElement, + UmbModalRouteRegistrationController, +} from '@umbraco-cms/backoffice/modal'; import { UmbDeselectedEvent, UmbSelectedEvent, UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event'; import type { UmbTreeElement, UmbTreeItemModelBase } from '@umbraco-cms/backoffice/tree'; @@ -17,17 +21,57 @@ export class UmbTreePickerModalElement { this._selectionConfiguration.selection = value?.selection ?? []; }); } + // Same here [NL] this._selectionConfiguration.multiple = this.data?.multiple ?? false; + + // TODO: If data.enableCreate is true, we should add a button to create a new item. [NL] + // Does the tree know enough about this, for us to be able to create a new item? [NL] + // I think we need to be able to get entityType and a parentId?, or do we only allow creation in the root? and then create via entity actions? [NL] + // To remove the hardcoded URLs for workspaces of entity types, we could make an create event from the tree, which either this or the sidebar impl. will pick up and react to. [NL] + // Or maybe the tree item context base can handle this? [NL] + // Maybe its a general item context problem to be solved. [NL] + const createAction = this.data?.createAction; + if (createAction) { + this._createLabel = createAction.label; + new UmbModalRouteRegistrationController( + this, + (createAction.modalToken as typeof UMB_WORKSPACE_MODAL) ?? UMB_WORKSPACE_MODAL, + ) + .onSetup(() => { + return { data: createAction.modalData }; + }) + .onSubmit((value) => { + if (value) { + this.value = { selection: [value.unique] }; + this._submitModal(); + } else { + this._rejectModal(); + } + }) + .observeRouteBuilder((routeBuilder) => { + const oldPath = this._createPath; + this._createPath = + routeBuilder({}) + createAction.extendWithPathPattern.generateLocal(createAction.extendWithPathParams); + this.requestUpdate('_createPath', oldPath); + }); + } } #onSelectionChange(event: UmbSelectionChangeEvent) { @@ -66,6 +110,12 @@ export class UmbTreePickerModalElement
+ ${this._createPath + ? html` ` + : nothing} extends UmbPickerModalData { +export interface UmbTreePickerModalCreateActionData { + label: string; + modalData: UmbWorkspaceModalData; + modalToken?: UmbModalToken; + extendWithPathPattern: UmbPathPattern; + extendWithPathParams: PathPatternParamsType; +} + +export interface UmbTreePickerModalData< + TreeItemType, + PathPatternParamsType extends UmbPathPatternParamsType = UmbPathPatternParamsType, +> extends UmbPickerModalData { treeAlias?: string; + // Consider if it makes sense to move this into the UmbPickerModalData interface, but for now this is a TreePicker feature. [NL] + createAction?: UmbTreePickerModalCreateActionData; } export interface UmbTreePickerModalValue extends UmbPickerModalValue {} -export const UMB_TREE_PICKER_MODAL = new UmbModalToken( +export const UMB_TREE_PICKER_MODAL = new UmbModalToken, UmbTreePickerModalValue>( UMB_TREE_PICKER_MODAL_ALIAS, { modal: { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts index 903ec84c9c..59f441f2ba 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts @@ -3,12 +3,14 @@ export * from './direction/index.js'; export * from './download/blob-download.function.js'; export * from './get-processed-image-url.function.js'; export * from './math/math.js'; +export * from './object/deep-merge.function.js'; export * from './pagination-manager/pagination.manager.js'; export * from './path/ensure-path-ends-with-slash.function.js'; export * from './path/path-decode.function.js'; export * from './path/path-encode.function.js'; export * from './path/path-folder-name.function.js'; export * from './path/umbraco-path.function.js'; +export * from './path/url-pattern-to-string.function.js'; export * from './selection-manager/selection.manager.js'; export * from './string/from-camel-case.function.js'; export * from './string/generate-umbraco-alias.function.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/object/deep-merge.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/object/deep-merge.function.ts new file mode 100644 index 0000000000..94b67c9b94 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/object/deep-merge.function.ts @@ -0,0 +1,25 @@ +import type { UmbDeepPartialObject } from '../type/deep-partial-object.type.js'; + +/** + * Deep merge two objects. + * @param target + * @param ...sources + */ +export function umbDeepMerge< + T extends { [key: string]: any }, + PartialType extends UmbDeepPartialObject = UmbDeepPartialObject, +>(source: PartialType, fallback: T) { + const result = { ...fallback }; + + for (const key in source) { + if (Object.prototype.hasOwnProperty.call(source, key) && source[key] !== undefined) { + if (source[key]?.constructor === Object && fallback[key].constructor === Object) { + result[key] = umbDeepMerge(source[key] as any, fallback[key]); + } else { + result[key] = source[key] as any; + } + } + } + + return result; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/object/deep-merge.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/object/deep-merge.test.ts new file mode 100644 index 0000000000..c67f608c72 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/object/deep-merge.test.ts @@ -0,0 +1,61 @@ +import { expect } from '@open-wc/testing'; +import { umbDeepMerge } from './deep-merge.function.js'; + +describe('UmbDeepMerge', () => { + beforeEach(() => {}); + + describe('merge just objects', () => { + it('transfers defined properties', () => { + const defaults = { + prop1: { + name: 'prop1', + value: 'value1', + }, + prop2: { + name: 'prop2', + value: 'value2', + }, + }; + const source = { + prop2: { + name: 'prop2_updatedName', + }, + }; + const result = umbDeepMerge(source, defaults); + + expect(result.prop1.name).to.equal('prop1'); + expect(result.prop2.name).to.equal('prop2_updatedName'); + }); + }); + + describe('merge objects with arrays', () => { + // The arrays should not be merged, but take the value from the main object. [NL] + it('transfers defined properties', () => { + const defaults = { + prop1: { + name: 'prop1', + value: ['entry1', 'entry2', 'entry3'], + }, + prop2: { + name: 'prop2', + value: ['entry4', 'entry4', 'entry5'], + }, + }; + const source = { + prop1: { + name: 'prop1_updatedName', + }, + prop2: { + name: 'prop2_updatedName', + value: ['entry666'], + }, + }; + const result = umbDeepMerge(source, defaults); + + expect(result.prop1.name).to.equal('prop1_updatedName'); + expect(result.prop1.value.join(',')).to.equal(defaults.prop1.value.join(',')); + expect(result.prop2.name).to.equal('prop2_updatedName'); + expect(result.prop2.value.join(',')).to.equal(source.prop2.value.join(',')); + }); + }); +}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/path/url-pattern-to-string.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/path/url-pattern-to-string.function.ts new file mode 100644 index 0000000000..1cd087a67e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/path/url-pattern-to-string.function.ts @@ -0,0 +1,13 @@ +export type UrlParametersRecord = Record string } | null>; + +const PARAM_IDENTIFIER = /:([^/]+)/g; + +export function umbUrlPatternToString(pattern: string, params: UrlParametersRecord | null): string { + return params + ? pattern.replace(PARAM_IDENTIFIER, (_substring: string, ...args: string[]) => { + const segmentValue = params![args[0]]; // (segmentValue is the value to replace the parameter) + // Replace the path-segment with the value from the params object or 'null' if it doesn't exist + return segmentValue === undefined ? `:${args[0]}` : segmentValue === null ? 'null' : segmentValue.toString(); + }) + : pattern; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/type/deep-partial-object.type.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/type/deep-partial-object.type.ts new file mode 100644 index 0000000000..7961caf5d1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/type/deep-partial-object.type.ts @@ -0,0 +1,30 @@ +/*export type DeepPartial = T extends { [key: string]: any } + ? { + [P in keyof T]?: DeepPartial; + } + : T; +*/ + +// Notice this can be way more complex, but in this case I just wanted to cover pure objects, to match our deep merge function [NL] +// See https://stackoverflow.com/questions/61132262/typescript-deep-partial for more extensive solutions. +/** + * Deep partial object type, making objects and their properties optional, but only until a property of a different type is encountered. + * This means if an object holds a property with an array that holds objects, the array will be made optional, but the properties of the objects inside the array will not be changed. + * @type UmbDeepPartialObject + * @generic T - The object to make partial. + * @returns A type with all properties of objects made optional. + */ +// eslint-disable-next-line @typescript-eslint/ban-types +export type UmbDeepPartialObject = T extends Function + ? T + : // Thing extends Array + // ? DeepPartialArray : + T extends { [key: string]: any } + ? UmbDeepPartialObjectProperty + : T | undefined; + +//interface DeepPartialArray extends Array> {} + +type UmbDeepPartialObjectProperty = { + [Key in keyof Thing]?: UmbDeepPartialObject; +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/type/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/type/index.ts index 1aac725c2b..eb7e9afca7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/utils/type/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/type/index.ts @@ -1,2 +1,3 @@ +export * from './deep-partial-object.type.js'; export * from './diff.type.js'; export * from './partial-some.type.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/type/partial-some.type.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/type/partial-some.type.ts index d5fd502f1a..2676f23bef 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/utils/type/partial-some.type.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/type/partial-some.type.ts @@ -1 +1 @@ -export type PartialSome = Omit & Partial>; +export type UmbPartialSome = Omit & Partial>; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/data-path-property-value-filter.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/data-path-property-value-filter.function.ts index 5de89688b5..408cf91b06 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/data-path-property-value-filter.function.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/data-path-property-value-filter.function.ts @@ -1,4 +1,4 @@ -import type { PartialSome } from '@umbraco-cms/backoffice/utils'; +import type { UmbPartialSome } from '@umbraco-cms/backoffice/utils'; import type { UmbVariantPropertyValueModel } from '@umbraco-cms/backoffice/variant'; /** @@ -9,7 +9,7 @@ import type { UmbVariantPropertyValueModel } from '@umbraco-cms/backoffice/varia * @returns */ export function UmbDataPathPropertyValueFilter( - value: PartialSome, 'culture' | 'segment'>, + value: UmbPartialSome, 'culture' | 'segment'>, ): string { // write a array of strings for each property, where alias must be present and culture and segment are optional const filters: Array = [`@.alias = '${value.alias}'`]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/controllers/workspace-is-new-redirect.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/controllers/workspace-is-new-redirect.controller.ts index fa3434a304..daabfc3140 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/controllers/workspace-is-new-redirect.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/controllers/workspace-is-new-redirect.controller.ts @@ -1,8 +1,8 @@ import type { UmbSubmittableWorkspaceContextBase } from '../contexts/index.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; -import { createRoutePathBuilder, type UmbRouterSlotElement } from '@umbraco-cms/backoffice/router'; -import { ensurePathEndsWithSlash } from '@umbraco-cms/backoffice/utils'; +import type { UmbRouterSlotElement } from '@umbraco-cms/backoffice/router'; +import { ensurePathEndsWithSlash, umbUrlPatternToString } from '@umbraco-cms/backoffice/utils'; /** * Observe the workspace context to see if the entity is new or not. @@ -28,7 +28,7 @@ export class UmbWorkspaceIsNewRedirectController extends UmbControllerBase { if (router && unique) { const routerPath = router.absoluteRouterPath; if (routerPath) { - const newPath: string = createRoutePathBuilder(ensurePathEndsWithSlash(routerPath) + 'edit/:id')({ + const newPath: string = umbUrlPatternToString(ensurePathEndsWithSlash(routerPath) + 'edit/:id', { id: unique, }); this.destroy(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/index.ts index 81c6f4dbfc..0896610ff9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/index.ts @@ -4,6 +4,7 @@ export * from './controllers/index.js'; export * from './modals/index.js'; export * from './workspace-property-dataset/index.js'; export * from './workspace.element.js'; +export * from './paths.js'; export type * from './conditions/index.js'; export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/modals/workspace-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/modals/workspace-modal.element.ts index 5a9722e24d..9d702fc1d8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/modals/workspace-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/modals/workspace-modal.element.ts @@ -1,13 +1,13 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { CSSResultGroup } from '@umbraco-cms/backoffice/external/lit'; import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; -import type { UmbWorkspaceData } from '@umbraco-cms/backoffice/modal'; +import type { UmbWorkspaceModalData } from '@umbraco-cms/backoffice/modal'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @customElement('umb-workspace-modal') export class UmbWorkspaceModalElement extends UmbLitElement { @property({ attribute: false }) - data?: UmbWorkspaceData; + data?: UmbWorkspaceModalData; /** * TODO: Consider if this binding and events integration is the right for communicating back the modal handler. Or if we should go with some Context API. like a Modal Context API. diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/paths.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/paths.ts new file mode 100644 index 0000000000..da4f96e16a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/paths.ts @@ -0,0 +1,7 @@ +import { UmbPathPattern } from '@umbraco-cms/backoffice/router'; +import { UMB_SECTION_PATH_PATTERN } from '@umbraco-cms/backoffice/section'; + +export const UMB_WORKSPACE_PATH_PATTERN = new UmbPathPattern< + { entityType: string }, + typeof UMB_SECTION_PATH_PATTERN.ABSOLUTE_PARAMS +>('workspace/:entityType', UMB_SECTION_PATH_PATTERN); diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-data-type-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-data-type-picker-modal.element.ts index 23d0012b14..bead006cfe 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-data-type-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-data-type-picker-modal.element.ts @@ -3,7 +3,7 @@ import type { UmbDataTypePickerFlowDataTypePickerModalData, UmbDataTypePickerFlowDataTypePickerModalValue, } from './data-type-picker-flow-data-type-picker-modal.token.js'; -import { css, html, customElement, state, repeat, when } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement, state, repeat } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; import type { UmbDataTypeItemModel } from '@umbraco-cms/backoffice/data-type'; @@ -72,12 +72,8 @@ export class UmbDataTypePickerFlowDataTypePickerModalElement extends UmbModalBas } private _renderDataTypes() { - const shouldRender = this._dataTypes && this._dataTypes.length > 0; - - return when( - shouldRender, - () => - html`
    + return this._dataTypes && this._dataTypes.length > 0 + ? html`
      ${repeat( this._dataTypes!, (dataType) => dataType.unique, @@ -93,8 +89,8 @@ export class UmbDataTypePickerFlowDataTypePickerModalElement extends UmbModalBas ` : '', )} -
    `, - ); +
` + : ''; } private _renderCreate() { return html` diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-modal.element.ts index 962abe0c73..8f219a0ae4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-modal.element.ts @@ -15,6 +15,10 @@ import type { UmbDataTypeItemModel } from '@umbraco-cms/backoffice/data-type'; import type { UmbModalRouteBuilder } from '@umbraco-cms/backoffice/modal'; import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; import { umbFocus } from '@umbraco-cms/backoffice/lit-element'; +import { + UMB_CONTENT_TYPE_WORKSPACE_CONTEXT, + UMB_PROPERTY_TYPE_WORKSPACE_CONTEXT, +} from '@umbraco-cms/backoffice/content-type'; interface GroupedItems { [key: string]: Array; @@ -24,6 +28,8 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement< UmbDataTypePickerFlowModalData, UmbDataTypePickerFlowModalValue > { + #initPromise!: Promise; + public set data(value: UmbDataTypePickerFlowModalData) { super.data = value; this._submitLabel = this.data?.submitLabel ?? this._submitLabel; @@ -41,7 +47,7 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement< @state() private _dataTypePickerModalRouteBuilder?: UmbModalRouteBuilder; - private _createDataTypeModal: UmbModalRouteRegistrationController; + private _createDataTypeModal!: UmbModalRouteRegistrationController; #collectionRepository; #dataTypes: Array = []; @@ -52,6 +58,37 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement< super(); this.#collectionRepository = new UmbDataTypeCollectionRepository(this); + this.#init(); + } + + private _createDataType(propertyEditorUiAlias: string) { + // TODO: Could be nice with a more pretty way to prepend to the URL: + // Open create modal: + console.log('_createDataType', propertyEditorUiAlias); + this._createDataTypeModal.open( + { uiAlias: propertyEditorUiAlias }, + `create/parent/${UMB_DATA_TYPE_ENTITY_TYPE}/null`, + ); + } + + async #init() { + this.#initPromise = Promise.all([ + this.observe( + (await this.#collectionRepository.requestCollection({ skip: 0, take: 100 })).asObservable(), + (dataTypes) => { + this.#dataTypes = dataTypes; + this._performFiltering(); + }, + '_repositoryItemsObserver', + ).asPromise(), + this.observe(umbExtensionsRegistry.byType('propertyEditorUi'), (propertyEditorUIs) => { + // Only include Property Editor UIs which has Property Editor Schema Alias + this.#propertyEditorUIs = propertyEditorUIs.filter( + (propertyEditorUi) => !!propertyEditorUi.meta.propertyEditorSchemaAlias, + ); + this._performFiltering(); + }).asPromise(), + ]); new UmbModalRouteRegistrationController(this, UMB_DATA_TYPE_PICKER_FLOW_DATA_TYPE_PICKER_MODAL) .addAdditionalPath(':uiAlias') @@ -78,43 +115,32 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement< this._createDataTypeModal = new UmbModalRouteRegistrationController(this, UMB_DATATYPE_WORKSPACE_MODAL) .addAdditionalPath(':uiAlias') - .onSetup((params) => { - return { data: { entityType: UMB_DATA_TYPE_ENTITY_TYPE, preset: { editorUiAlias: params.uiAlias } } }; + .onSetup(async (params) => { + const contentContextConsumer = this.consumeContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT, () => { + this.removeUmbController(contentContextConsumer); + }).passContextAliasMatches(); + const propContextConsumer = this.consumeContext(UMB_PROPERTY_TYPE_WORKSPACE_CONTEXT, () => { + this.removeUmbController(propContextConsumer); + }).passContextAliasMatches(); + const [contentContext, propContext] = await Promise.all([ + contentContextConsumer.asPromise(), + propContextConsumer.asPromise(), + this.#initPromise, + ]); + const propertyEditorName = this.#propertyEditorUIs.find((ui) => ui.alias === params.uiAlias)?.name; + const dataTypeName = `${contentContext?.getName() ?? ''} - ${propContext.getLabel() ?? ''} - ${propertyEditorName}`; + + return { + data: { + entityType: UMB_DATA_TYPE_ENTITY_TYPE, + preset: { editorUiAlias: params.uiAlias, name: dataTypeName }, + }, + }; }) .onSubmit((value) => { this._select(value?.unique); this._submitModal(); }); - - this.#init(); - } - - private _createDataType(propertyEditorUiAlias: string) { - // TODO: Could be nice with a more pretty way to prepend to the URL: - // Open create modal: - this._createDataTypeModal.open( - { uiAlias: propertyEditorUiAlias }, - `create/parent/${UMB_DATA_TYPE_ENTITY_TYPE}/null`, - ); - } - - async #init() { - this.observe( - (await this.#collectionRepository.requestCollection({ skip: 0, take: 100 })).asObservable(), - (dataTypes) => { - this.#dataTypes = dataTypes; - this._performFiltering(); - }, - '_repositoryItemsObserver', - ); - - this.observe(umbExtensionsRegistry.byType('propertyEditorUi'), (propertyEditorUIs) => { - // Only include Property Editor UIs which has Property Editor Schema Alias - this.#propertyEditorUIs = propertyEditorUIs.filter( - (propertyEditorUi) => !!propertyEditorUi.meta.propertyEditorSchemaAlias, - ); - this._performFiltering(); - }); } private _handleDataTypeClick(dataType: UmbDataTypeItemModel) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.modal-token.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.modal-token.ts index d0c0d0652a..457a0f5ff4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.modal-token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.modal-token.ts @@ -1,10 +1,10 @@ import type { UmbDataTypeDetailModel } from '../types.js'; -import type { UmbWorkspaceData, UmbWorkspaceValue } from '@umbraco-cms/backoffice/modal'; +import type { UmbWorkspaceModalData, UmbWorkspaceModalValue } from '@umbraco-cms/backoffice/modal'; import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; export const UMB_DATATYPE_WORKSPACE_MODAL = new UmbModalToken< - UmbWorkspaceData, - UmbWorkspaceValue + UmbWorkspaceModalData, + UmbWorkspaceModalValue >('Umb.Modal.Workspace', { modal: { type: 'sidebar', @@ -12,4 +12,4 @@ export const UMB_DATATYPE_WORKSPACE_MODAL = new UmbModalToken< }, data: { entityType: 'data-type', preset: {} }, // Recast the type, so the entityType data prop is not required: -}) as UmbModalToken, 'entityType'>, UmbWorkspaceValue>; +}) as UmbModalToken, 'entityType'>, UmbWorkspaceModalValue>; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/index.ts index 9a775fd168..b3b43602fe 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/index.ts @@ -1,3 +1,2 @@ -import './input-document-type/input-document-type.element.js'; - -export * from './input-document-type/input-document-type.element.js'; +export { UmbDocumentTypePickerContext } from './input-document-type/input-document-type.context.js'; +export { UmbInputDocumentTypeElement } from './input-document-type/input-document-type.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.context.ts index 7eec6cddde..f89c8e3f70 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.context.ts @@ -1,9 +1,9 @@ +import { UMB_DOCUMENT_TYPE_PICKER_MODAL } from '../../modals/index.js'; import type { UmbDocumentTypeItemModel } from '../../repository/index.js'; import type { UmbDocumentTypeTreeItemModel } from '../../tree/types.js'; import { UMB_DOCUMENT_TYPE_ITEM_REPOSITORY_ALIAS } from '../../repository/index.js'; import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { UMB_DOCUMENT_TYPE_PICKER_MODAL } from '@umbraco-cms/backoffice/document-type'; export class UmbDocumentTypePickerContext extends UmbPickerInputContext< UmbDocumentTypeItemModel, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts index 65cb668f40..f163c3f449 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts @@ -1,11 +1,12 @@ import type { UmbDocumentTypeItemModel } from '../../repository/index.js'; +import { UMB_DOCUMENT_TYPE_WORKSPACE_MODAL } from '../../workspace/document-type-workspace.modal-token.js'; import type { UmbDocumentTypeTreeItemModel } from '../../tree/types.js'; import { UmbDocumentTypePickerContext } from './input-document-type.context.js'; import { css, html, customElement, property, state, repeat, nothing } from '@umbraco-cms/backoffice/external/lit'; import { splitStringToArray } from '@umbraco-cms/backoffice/utils'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbModalRouteRegistrationController, UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/modal'; +import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal'; import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; import { UUIFormControlMixin } from '@umbraco-cms/backoffice/external/uui'; @@ -107,7 +108,6 @@ export class UmbInputDocumentTypeElement extends UUIFormControlMixin(UmbLitEleme public get value(): string { return this.selection.join(','); } - @state() private _items?: Array; @@ -119,10 +119,10 @@ export class UmbInputDocumentTypeElement extends UUIFormControlMixin(UmbLitEleme constructor() { super(); - new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL) + new UmbModalRouteRegistrationController(this, UMB_DOCUMENT_TYPE_WORKSPACE_MODAL) .addAdditionalPath('document-type') .onSetup(() => { - return { data: { entityType: 'document-type', preset: {} } }; + return {}; }) .observeRouteBuilder((routeBuilder) => { this._editPath = routeBuilder({}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/entity-actions/create/create.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/entity-actions/create/create.action.ts index 23f8b40dc0..2d5edbd8d3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/entity-actions/create/create.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/entity-actions/create/create.action.ts @@ -2,7 +2,7 @@ import { UMB_DOCUMENT_TYPE_CREATE_OPTIONS_MODAL } from './modal/index.js'; import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; -export class UmbCreateDataTypeEntityAction extends UmbEntityActionBase { +export class UmbCreateDocumentTypeEntityAction extends UmbEntityActionBase { async execute() { const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); const modalContext = modalManager.open(this, UMB_DOCUMENT_TYPE_CREATE_OPTIONS_MODAL, { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/entity-actions/create/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/entity-actions/create/manifests.ts index 93e2053474..f62071f6c3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/entity-actions/create/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/entity-actions/create/manifests.ts @@ -3,7 +3,7 @@ import { UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE, UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE, } from '../../entity.js'; -import { UmbCreateDataTypeEntityAction } from './create.action.js'; +import { UmbCreateDocumentTypeEntityAction } from './create.action.js'; import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; const entityActions: Array = [ @@ -13,7 +13,7 @@ const entityActions: Array = [ alias: 'Umb.EntityAction.DocumentType.Create', name: 'Create Document Type Entity Action', weight: 1200, - api: UmbCreateDataTypeEntityAction, + api: UmbCreateDocumentTypeEntityAction, forEntityTypes: [ UMB_DOCUMENT_TYPE_ENTITY_TYPE, UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/entity-actions/create/modal/document-type-create-options-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/entity-actions/create/modal/document-type-create-options-modal.element.ts index 2d8e72684b..389f8ae656 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/entity-actions/create/modal/document-type-create-options-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/entity-actions/create/modal/document-type-create-options-modal.element.ts @@ -1,16 +1,29 @@ import { UMB_DOCUMENT_TYPE_FOLDER_REPOSITORY_ALIAS } from '../../../tree/index.js'; import type { UmbDocumentTypeCreateOptionsModalData } from './index.js'; +import { + UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN, + type UmbCreateDocumentTypeWorkspacePresetType, + type UmbDocumentTypeEntityTypeUnion, +} from '@umbraco-cms/backoffice/document-type'; import { html, customElement, map } from '@umbraco-cms/backoffice/external/lit'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; import { UmbCreateFolderEntityAction } from '@umbraco-cms/backoffice/tree'; +// Include the types from the DocumentTypeWorkspacePresetType + folder. +type OptionsPresetType = UmbCreateDocumentTypeWorkspacePresetType | 'folder' | null; + @customElement('umb-document-type-create-options-modal') export class UmbDataTypeCreateOptionsModalElement extends UmbModalBaseElement { #createFolderAction?: UmbCreateFolderEntityAction; - #items: Array<{ preset: string; label: string; description: string; icon: string }> = [ + #items: Array<{ + preset: OptionsPresetType; + label: string; + description: string; + icon: string; + }> = [ { - preset: 'null', + preset: null, label: this.localize.term('create_documentType'), description: this.localize.term('create_documentTypeDescription'), icon: 'icon-document', @@ -53,12 +66,13 @@ export class UmbDataTypeCreateOptionsModalElement extends UmbModalBaseElement; -export type UmbDocumentTypePickerModalValue = UmbTreePickerModalValue; +/*export interface UmbDocumentTypePickerModalData + extends UmbTreePickerModalData {} +*/ +export type UmbDocumentTypePickerModalData = UmbTreePickerModalData< + UmbDocumentTypeTreeItemModel, + typeof UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN.PARAMS +>; + +export interface UmbDocumentTypePickerModalValue extends UmbTreePickerModalValue {} export const UMB_DOCUMENT_TYPE_PICKER_MODAL = new UmbModalToken< UmbDocumentTypePickerModalData, @@ -19,5 +27,18 @@ export const UMB_DOCUMENT_TYPE_PICKER_MODAL = new UmbModalToken< }, data: { treeAlias: 'Umb.Tree.DocumentType', + createAction: { + label: '#content_createEmpty', + modalData: { + entityType: 'document-type', + preset: {}, + }, + extendWithPathPattern: UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN, + extendWithPathParams: { + parentEntityType: 'document-type-root', + parentUnique: null, + presetAlias: null, + }, + }, }, }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/paths.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/paths.ts new file mode 100644 index 0000000000..2fcd3c5725 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/paths.ts @@ -0,0 +1,29 @@ +import { UMB_DOCUMENT_TYPE_ENTITY_TYPE, type UmbDocumentTypeEntityTypeUnion } from './entity.js'; +import { UMB_SETTINGS_SECTION_PATHNAME } from '@umbraco-cms/backoffice/settings'; +import { UmbPathPattern } from '@umbraco-cms/backoffice/router'; +import { UMB_WORKSPACE_PATH_PATTERN } from '@umbraco-cms/backoffice/workspace'; + +export const UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PRESET_TEMPLATE = 'template'; +export const UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PRESET_ELEMENT = 'element'; + +export type UmbCreateDocumentTypeWorkspacePresetTemplateType = + typeof UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PRESET_TEMPLATE; +export type UmbCreateDocumentTypeWorkspacePresetElementType = // line break thanks! + typeof UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PRESET_ELEMENT; + +export type UmbCreateDocumentTypeWorkspacePresetType = + | UmbCreateDocumentTypeWorkspacePresetTemplateType + | UmbCreateDocumentTypeWorkspacePresetElementType; + +export const UMB_DOCUMENT_TYPE_WORKSPACE_PATH = UMB_WORKSPACE_PATH_PATTERN.generateAbsolute({ + sectionName: UMB_SETTINGS_SECTION_PATHNAME, + entityType: UMB_DOCUMENT_TYPE_ENTITY_TYPE, +}); + +export const UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN = new UmbPathPattern<{ + parentEntityType: UmbDocumentTypeEntityTypeUnion; + parentUnique?: string | null; + presetAlias?: UmbCreateDocumentTypeWorkspacePresetType | null; +}>('create/parent/:parentEntityType/:parentUnique/:presetAlias', UMB_DOCUMENT_TYPE_WORKSPACE_PATH); + +export const UMB_EDIT_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN = new UmbPathPattern<{ unique: string }>('edit/:unique'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace.context.ts index 08b2610991..a52c8fc1b2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace.context.ts @@ -1,6 +1,13 @@ import { UmbDocumentTypeDetailRepository } from '../repository/detail/document-type-detail.repository.js'; import { UMB_DOCUMENT_TYPE_ENTITY_TYPE } from '../entity.js'; import type { UmbDocumentTypeDetailModel } from '../types.js'; +import { + UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN, + UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PRESET_ELEMENT, + UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PRESET_TEMPLATE, + UMB_EDIT_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN, + type UmbCreateDocumentTypeWorkspacePresetType, +} from '../paths.js'; import { UmbDocumentTypeWorkspaceEditorElement } from './document-type-workspace-editor.element.js'; import { UmbContentTypeStructureManager } from '@umbraco-cms/backoffice/content-type'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; @@ -21,6 +28,7 @@ import type { import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models'; import type { UmbRoutableWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; +import type { UmbPathPatternTypeAsEncodedParamsType } from '@umbraco-cms/backoffice/router'; type EntityType = UmbDocumentTypeDetailModel; export class UmbDocumentTypeWorkspaceContext @@ -41,6 +49,9 @@ export class UmbDocumentTypeWorkspaceContext //readonly data; readonly unique; readonly name; + getName(): string | undefined { + return this.structure.getOwnerContentType()?.name; + } readonly alias; readonly description; readonly icon; @@ -89,12 +100,18 @@ export class UmbDocumentTypeWorkspaceContext this.routes.setRoutes([ { - path: 'create/:entityType/:parentUnique/:presetAlias', + path: UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN.toString(), component: UmbDocumentTypeWorkspaceEditorElement, setup: (_component, info) => { - const parentEntityType = info.match.params.entityType; - const parentUnique = info.match.params.parentUnique === 'null' ? null : info.match.params.parentUnique; - const presetAlias = info.match.params.presetAlias === 'null' ? null : info.match.params.presetAlias; + const params = info.match.params as unknown as UmbPathPatternTypeAsEncodedParamsType< + typeof UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN.PARAMS + >; + const parentEntityType = params.parentEntityType; + const parentUnique = params.parentUnique === 'null' ? null : params.parentUnique; + const presetAlias = params.presetAlias === 'null' ? null : params.presetAlias ?? null; + if (parentUnique === undefined) { + throw new Error('ParentUnique url parameter is required to create a document type'); + } this.create({ entityType: parentEntityType, unique: parentUnique }, presetAlias); new UmbWorkspaceIsNewRedirectController( @@ -105,12 +122,12 @@ export class UmbDocumentTypeWorkspaceContext }, }, { - path: 'edit/:id', + path: UMB_EDIT_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN.toString(), component: UmbDocumentTypeWorkspaceEditorElement, setup: (_component, info) => { this.removeUmbControllerByAlias('isNewRedirectController'); - const id = info.match.params.id; - this.load(id); + const unique = info.match.params.unique; + this.load(unique); }, }, ]); @@ -198,12 +215,12 @@ export class UmbDocumentTypeWorkspaceContext if (!data) return undefined; switch (presetAlias) { - case 'template': { + case UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PRESET_TEMPLATE satisfies UmbCreateDocumentTypeWorkspacePresetType: { this.setIcon('icon-notepad'); this.createTemplateMode = true; break; } - case 'element': { + case UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PRESET_ELEMENT satisfies UmbCreateDocumentTypeWorkspacePresetType: { this.setIcon('icon-plugin'); this.setIsElement(true); break; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace.modal-token.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace.modal-token.ts new file mode 100644 index 0000000000..87da2aba36 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace.modal-token.ts @@ -0,0 +1,15 @@ +import { UMB_DOCUMENT_TYPE_ENTITY_TYPE } from '../entity.js'; +import { UMB_WORKSPACE_MODAL, UmbModalToken } from '@umbraco-cms/backoffice/modal'; +import type { UmbDeepPartialObject } from '@umbraco-cms/backoffice/utils'; +import type { UmbWorkspaceModalData, UmbWorkspaceModalValue } from '@umbraco-cms/backoffice/modal'; + +export interface UmbDocumentTypeWorkspaceData extends UmbWorkspaceModalData {} + +export const UMB_DOCUMENT_TYPE_WORKSPACE_MODAL = new UmbModalToken< + UmbDocumentTypeWorkspaceData, + UmbWorkspaceModalValue +>('Umb.Modal.Workspace', { + modal: UMB_WORKSPACE_MODAL.getDefaultModal(), + data: { entityType: UMB_DOCUMENT_TYPE_ENTITY_TYPE, preset: {} }, + // Recast the type, so the entityType data prop is not required: +}) as UmbModalToken, UmbWorkspaceModalValue>; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/index.ts index b5c792e5ef..cf7ab5393f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/index.ts @@ -1 +1,2 @@ export * from './document-type-workspace.context-token.js'; +export * from './document-type-workspace.modal-token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/action/create-document-collection-action.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/action/create-document-collection-action.element.ts index a414f3f365..bd5c34ceeb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/action/create-document-collection-action.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/action/create-document-collection-action.element.ts @@ -2,7 +2,12 @@ import { html, customElement, property, state, map } from '@umbraco-cms/backoffi import { UmbDocumentTypeStructureRepository } from '@umbraco-cms/backoffice/document-type'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UMB_DEFAULT_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection'; -import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/document'; +import { + UMB_CREATE_DOCUMENT_WORKSPACE_PATH_PATTERN, + UMB_DOCUMENT_ENTITY_TYPE, + UMB_DOCUMENT_ROOT_ENTITY_TYPE, + UMB_DOCUMENT_WORKSPACE_CONTEXT, +} from '@umbraco-cms/backoffice/document'; import type { ManifestCollectionAction } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbAllowedDocumentTypeModel } from '@umbraco-cms/backoffice/document-type'; import { UMB_WORKSPACE_MODAL, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal'; @@ -83,9 +88,19 @@ export class UmbCreateDocumentCollectionActionElement extends UmbLitElement { #getCreateUrl(item: UmbAllowedDocumentTypeModel) { // TODO: [LK] I need help with this. I don't know what the infinity editor URL should be. + // TODO: Yes, revisit the path extension of the routable modal, cause this is not pretty...? [NL] return this._useInfiniteEditor - ? `${this._createDocumentPath}create/${this._documentUnique ?? 'null'}/${item.unique}` - : `section/content/workspace/document/create/${this._documentUnique ?? 'null'}/${item.unique}`; + ? this._createDocumentPath + + UMB_CREATE_DOCUMENT_WORKSPACE_PATH_PATTERN.generateLocal({ + parentEntityType: this._documentUnique ? UMB_DOCUMENT_ENTITY_TYPE : UMB_DOCUMENT_ROOT_ENTITY_TYPE, + parentUnique: this._documentUnique ?? 'null', + documentTypeUnique: item.unique, + }) + : UMB_CREATE_DOCUMENT_WORKSPACE_PATH_PATTERN.generateAbsolute({ + parentEntityType: this._documentUnique ? UMB_DOCUMENT_ENTITY_TYPE : UMB_DOCUMENT_ROOT_ENTITY_TYPE, + parentUnique: this._documentUnique ?? 'null', + documentTypeUnique: item.unique, + }); } render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/index.ts index 361ab007ce..9c097fa1bf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/index.ts @@ -1,2 +1,2 @@ -export * from './input-document/input-document.element.js'; -export * from './input-document-root-picker/input-document-root-picker.element.js'; +export { UmbDocumentPickerContext } from './input-document/input-document.context.js'; +export { UmbInputDocumentElement } from './input-document/input-document.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.context.ts index b05b320192..81476fe796 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.context.ts @@ -1,6 +1,6 @@ +import { UMB_DOCUMENT_PICKER_MODAL } from '../../modals/index.js'; import { UMB_DOCUMENT_ITEM_REPOSITORY_ALIAS } from '../../repository/index.js'; import type { UmbDocumentItemModel } from '../../repository/index.js'; -import { UMB_DOCUMENT_PICKER_MODAL } from '../../modals/index.js'; import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts index cd8205865b..48dd116fc8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts @@ -238,8 +238,6 @@ export class UmbInputDocumentElement extends UUIFormControlMixin(UmbLitElement, ]; } -export default UmbInputDocumentElement; - declare global { interface HTMLElementTagNameMap { 'umb-input-document': UmbInputDocumentElement; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/create/document-create-options-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/create/document-create-options-modal.element.ts index 18553f1c3d..a5a81d1c5d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/create/document-create-options-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/create/document-create-options-modal.element.ts @@ -14,6 +14,11 @@ import { UmbDocumentBlueprintItemRepository, type UmbDocumentBlueprintItemBaseModel, } from '@umbraco-cms/backoffice/document-blueprint'; +import { + UMB_CREATE_DOCUMENT_WORKSPACE_PATH_PATTERN, + UMB_CREATE_FROM_BLUEPRINT_DOCUMENT_WORKSPACE_PATH_PATTERN, + type UmbDocumentEntityTypeUnion, +} from '@umbraco-cms/backoffice/document'; @customElement('umb-document-create-options-modal') export class UmbDocumentCreateOptionsModalElement extends UmbModalBaseElement< @@ -68,17 +73,29 @@ export class UmbDocumentCreateOptionsModalElement extends UmbModalBaseElement< // close the modal when navigating to data type #onNavigate(documentTypeUnique: string, blueprintUnique?: string) { + if (!this.data) { + throw new Error('Data is not defined'); + } if (!blueprintUnique) { history.pushState( null, '', - `section/content/workspace/document/create/parent/${this.data?.parent.entityType}/${this.data?.parent.unique ?? 'null'}/${documentTypeUnique}`, + UMB_CREATE_DOCUMENT_WORKSPACE_PATH_PATTERN.generateAbsolute({ + parentEntityType: this.data.parent.entityType as UmbDocumentEntityTypeUnion, + parentUnique: this.data.parent.unique, + documentTypeUnique, + }), ); } else { history.pushState( null, '', - `section/content/workspace/document/create/parent/${this.data?.parent.entityType}/${this.data?.parent.unique ?? 'null'}/${documentTypeUnique}/blueprint/${blueprintUnique}`, + UMB_CREATE_FROM_BLUEPRINT_DOCUMENT_WORKSPACE_PATH_PATTERN.generateAbsolute({ + parentEntityType: this.data.parent.entityType as UmbDocumentEntityTypeUnion, + parentUnique: this.data.parent.unique, + documentTypeUnique, + blueprintUnique, + }), ); } this._submitModal(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity.ts index c85fa375c0..13602ef85d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity.ts @@ -3,3 +3,5 @@ export const UMB_DOCUMENT_ROOT_ENTITY_TYPE = 'document-root'; export type UmbDocumentEntityType = typeof UMB_DOCUMENT_ENTITY_TYPE; export type UmbDocumentRootEntityType = typeof UMB_DOCUMENT_ROOT_ENTITY_TYPE; + +export type UmbDocumentEntityTypeUnion = UmbDocumentEntityType | UmbDocumentRootEntityType; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/index.ts index e5ac096a12..565baaf5df 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/index.ts @@ -1,15 +1,16 @@ import './components/index.js'; -export * from './repository/index.js'; -export * from './reference/index.js'; -export * from './workspace/index.js'; -export * from './recycle-bin/index.js'; -export * from './user-permissions/index.js'; export * from './components/index.js'; -export * from './entity.js'; export * from './entity-actions/index.js'; -export * from './modals/index.js'; +export * from './entity.js'; export * from './global-contexts/index.js'; +export * from './modals/index.js'; +export * from './paths.js'; +export * from './recycle-bin/index.js'; +export * from './reference/index.js'; +export * from './repository/index.js'; +export * from './user-permissions/index.js'; +export * from './workspace/index.js'; export * from './tree/index.js'; export { UMB_CONTENT_MENU_ALIAS } from './menu/manifests.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/rollback/rollback-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/rollback/rollback-modal.element.ts index 4bff0c9cac..f38424987f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/rollback/rollback-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/rollback/rollback-modal.element.ts @@ -10,6 +10,7 @@ import '../shared/document-variant-language-picker.element.js'; import { UmbUserItemRepository } from '@umbraco-cms/backoffice/user'; import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property'; import type { UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; +import { UMB_EDIT_DOCUMENT_WORKSPACE_PATH_PATTERN } from '../../paths.js'; type DocumentVersion = { id: string; @@ -147,7 +148,7 @@ export class UmbRollbackModalElement extends UmbModalBaseElement( + 'create/parent/:parentEntityType/:parentUnique/:documentTypeUnique/blueprint/:blueprintUnique', + UMB_DOCUMENT_WORKSPACE_PATH, +); + +export const UMB_CREATE_DOCUMENT_WORKSPACE_PATH_PATTERN = new UmbPathPattern<{ + parentEntityType: UmbDocumentEntityTypeUnion; + parentUnique?: string | null; + documentTypeUnique: string; +}>('create/parent/:parentEntityType/:parentUnique/:documentTypeUnique', UMB_DOCUMENT_WORKSPACE_PATH); + +export const UMB_EDIT_DOCUMENT_WORKSPACE_PATH_PATTERN = new UmbPathPattern<{ unique: string }>('edit/:unique'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 0c4f621089..055ed42f86 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -18,6 +18,12 @@ import { import { UmbDocumentPublishingRepository } from '../repository/publishing/index.js'; import { UmbUnpublishDocumentEntityAction } from '../entity-actions/unpublish.action.js'; import { UmbDocumentValidationRepository } from '../repository/validation/document-validation.repository.js'; +import { + UMB_CREATE_DOCUMENT_WORKSPACE_PATH_PATTERN, + UMB_CREATE_FROM_BLUEPRINT_DOCUMENT_WORKSPACE_PATH_PATTERN, + UMB_EDIT_DOCUMENT_WORKSPACE_PATH_PATTERN, +} from '../paths.js'; +import { UMB_DOCUMENTS_SECTION_PATH } from '../../paths.js'; import { UMB_DOCUMENT_WORKSPACE_ALIAS } from './manifests.js'; import { UmbEntityContext } from '@umbraco-cms/backoffice/entity'; import { UMB_INVARIANT_CULTURE, UmbVariantId } from '@umbraco-cms/backoffice/variant'; @@ -159,10 +165,10 @@ export class UmbDocumentWorkspaceContext this.routes.setRoutes([ { - path: 'create/parent/:entityType/:parentUnique/:documentTypeUnique/blueprint/:blueprintUnique', + path: UMB_CREATE_FROM_BLUEPRINT_DOCUMENT_WORKSPACE_PATH_PATTERN.toString(), component: () => import('./document-workspace-editor.element.js'), setup: async (_component, info) => { - const parentEntityType = info.match.params.entityType; + const parentEntityType = info.match.params.parentEntityType; const parentUnique: string | null = info.match.params.parentUnique === 'null' ? null : info.match.params.parentUnique; const documentTypeUnique = info.match.params.documentTypeUnique; @@ -177,10 +183,10 @@ export class UmbDocumentWorkspaceContext }, }, { - path: 'create/parent/:entityType/:parentUnique/:documentTypeUnique', + path: UMB_CREATE_DOCUMENT_WORKSPACE_PATH_PATTERN.toString(), component: () => import('./document-workspace-editor.element.js'), setup: async (_component, info) => { - const parentEntityType = info.match.params.entityType; + const parentEntityType = info.match.params.parentEntityType; const parentUnique = info.match.params.parentUnique === 'null' ? null : info.match.params.parentUnique; const documentTypeUnique = info.match.params.documentTypeUnique; this.create({ entityType: parentEntityType, unique: parentUnique }, documentTypeUnique); @@ -193,7 +199,7 @@ export class UmbDocumentWorkspaceContext }, }, { - path: 'edit/:unique', + path: UMB_EDIT_DOCUMENT_WORKSPACE_PATH_PATTERN.toString(), component: () => import('./document-workspace-editor.element.js'), setup: (_component, info) => { const unique = info.match.params.unique; @@ -235,7 +241,7 @@ export class UmbDocumentWorkspaceContext #onStoreChange(entity: EntityType | undefined) { if (!entity) { //TODO: This solution is alright for now. But reconsider when we introduce signal-r - history.pushState(null, '', 'section/content'); + history.pushState(null, '', UMB_DOCUMENTS_SECTION_PATH); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/index.ts index e0f90f8c40..64a195374d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/index.ts @@ -1,5 +1,6 @@ +import './document-blueprints/index.js'; import './document-types/index.js'; import './documents/index.js'; -import './document-blueprints/index.js'; +import './paths.js'; export * from './manifests.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/paths.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/paths.ts new file mode 100644 index 0000000000..5af6fdb576 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/paths.ts @@ -0,0 +1,6 @@ +import { UMB_SECTION_PATH_PATTERN } from '@umbraco-cms/backoffice/section'; + +export const UMB_DOCUMENTS_SECTION_PATHNAME = 'content'; +export const UMB_DOCUMENTS_SECTION_PATH = UMB_SECTION_PATH_PATTERN.generateAbsolute({ + sectionName: UMB_DOCUMENTS_SECTION_PATHNAME, +}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/index.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/index.ts deleted file mode 100644 index 3fcf6dc3f7..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL_ALIAS, - UMB_DYNAMIC_ROOT_QUERY_STEP_PICKER_MODAL_ALIAS, -} from './manifests.js'; -import type { - ManifestDynamicRootOrigin, - ManifestDynamicRootQueryStep, -} from '@umbraco-cms/backoffice/extension-registry'; -import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; - -export interface UmbDynamicRootOriginModalData { - items: Array; -} - -export interface UmbDynamicRootQueryStepModalData { - items: Array; -} - -export const UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL = new UmbModalToken( - UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL_ALIAS, - { - modal: { - type: 'sidebar', - size: 'small', - }, - }, -); - -export const UMB_DYNAMIC_ROOT_QUERY_STEP_PICKER_MODAL = new UmbModalToken( - UMB_DYNAMIC_ROOT_QUERY_STEP_PICKER_MODAL_ALIAS, - { - modal: { - type: 'sidebar', - size: 'small', - }, - }, -); diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.repository.ts deleted file mode 100644 index 91eb4ec1dc..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.repository.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { UmbDynamicRootServerDataSource } from './dynamic-root.server.data.js'; -import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import type { DynamicRootRequestModel } from '@umbraco-cms/backoffice/external/backend-api'; -import type { UmbTreePickerDynamicRoot } from '@umbraco-cms/backoffice/components'; - -const GUID_EMPTY: string = '00000000-0000-0000-0000-000000000000'; - -export class UmbDynamicRootRepository extends UmbControllerBase { - #dataSource: UmbDynamicRootServerDataSource; - - constructor(host: UmbControllerHost) { - super(host); - - this.#dataSource = new UmbDynamicRootServerDataSource(host); - } - - async postDynamicRootQuery(query: UmbTreePickerDynamicRoot, entityId: string, parentId?: string) { - const model: DynamicRootRequestModel = { - context: { - id: entityId, - parent: { id: parentId ?? GUID_EMPTY }, - }, - query: { - origin: { - alias: query.originAlias, - id: query.originKey, - }, - steps: query.querySteps!.map((step) => { - return { - alias: step.alias!, - documentTypeIds: step.anyOfDocTypeKeys!, - }; - }), - }, - }; - - const result = await this.#dataSource.postDynamicRootQuery(model); - - return result?.roots; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/index.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/index.ts deleted file mode 100644 index f60228e806..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { UMB_DYNAMIC_ROOT_REPOSITORY_ALIAS } from './manifests.js'; -export { UmbDynamicRootRepository } from './dynamic-root.repository.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/manifests.ts deleted file mode 100644 index dff470e8a2..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/manifests.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; - -export const UMB_DYNAMIC_ROOT_REPOSITORY_ALIAS = 'Umb.Repository.DynamicRoot'; - -export const manifests: Array = []; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/umbraco-package.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/umbraco-package.ts deleted file mode 100644 index 46eff51dfd..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/umbraco-package.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const name = 'Umbraco.Core.DynamicRoot'; -export const extensions = [ - { - name: 'Dynamic Root Bundle', - alias: 'Umb.Bundle.DynamicRoot', - type: 'bundle', - js: () => import('./manifests.js'), - }, -]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts index 86e1fd5a81..6253c452d1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts @@ -40,6 +40,9 @@ export class UmbMediaTypeWorkspaceContext readonly data; readonly unique; readonly name; + getName(): string | undefined { + return this.structure.getOwnerContentType()?.name; + } readonly alias; readonly description; readonly icon; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/workspace/member-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/workspace/member-type-workspace.context.ts index 65ee4c3950..5ee915d1ce 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/workspace/member-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/workspace/member-type-workspace.context.ts @@ -37,6 +37,9 @@ export class UmbMemberTypeWorkspaceContext readonly data; readonly unique; readonly name; + getName(): string | undefined { + return this.structure.getOwnerContentType()?.name; + } readonly alias; readonly description; readonly icon; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/tree-picker/Umbraco.MultiNodeTreePicker.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/Umbraco.MultiNodeTreePicker.ts similarity index 89% rename from src/Umbraco.Web.UI.Client/src/packages/property-editors/tree-picker/Umbraco.MultiNodeTreePicker.ts rename to src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/Umbraco.MultiNodeTreePicker.ts index dfec95c952..f9532ea5eb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/tree-picker/Umbraco.MultiNodeTreePicker.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/Umbraco.MultiNodeTreePicker.ts @@ -5,7 +5,7 @@ export const manifest: ManifestPropertyEditorSchema = { name: 'Multi Node Tree Picker', alias: 'Umbraco.MultiNodeTreePicker', meta: { - defaultPropertyEditorUiAlias: 'Umb.PropertyEditorUi.TreePicker', + defaultPropertyEditorUiAlias: 'Umb.PropertyEditorUi.ContentPicker', settings: { properties: [ { @@ -32,7 +32,7 @@ export const manifest: ManifestPropertyEditorSchema = { alias: 'startNode', label: 'Node type', description: '', - propertyEditorUiAlias: 'Umb.PropertyEditorUi.TreePicker.SourcePicker', + propertyEditorUiAlias: 'Umb.PropertyEditorUi.ContentPicker.Source', }, ], defaultData: [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/components/index.ts new file mode 100644 index 0000000000..cb519d7b9d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/components/index.ts @@ -0,0 +1 @@ +export * from './input-content/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/components/input-content/index.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/components/input-content/index.ts new file mode 100644 index 0000000000..2f7008eaa4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/components/input-content/index.ts @@ -0,0 +1 @@ +export * from './input-content.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/input-tree/input-tree.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/components/input-content/input-content.element.ts similarity index 88% rename from src/Umbraco.Web.UI.Client/src/packages/core/tree/components/input-tree/input-tree.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/components/input-content/input-content.element.ts index adeb1fe651..966114331f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/input-tree/input-tree.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/components/input-content/input-content.element.ts @@ -1,3 +1,4 @@ +import type { UmbContentPickerSource } from '../../types.js'; import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; import { UUIFormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; @@ -6,24 +7,24 @@ import type { UmbInputDocumentElement } from '@umbraco-cms/backoffice/document'; import type { UmbInputMediaElement } from '@umbraco-cms/backoffice/media'; import type { UmbInputMemberElement } from '@umbraco-cms/backoffice/member'; import type { UmbReferenceByUniqueAndType } from '@umbraco-cms/backoffice/models'; -import type { UmbTreePickerSource } from '@umbraco-cms/backoffice/components'; -@customElement('umb-input-tree') -export class UmbInputTreeElement extends UUIFormControlMixin(UmbLitElement, '') { +const elementName = 'umb-input-content'; +@customElement(elementName) +export class UmbInputContentElement extends UUIFormControlMixin(UmbLitElement, '') { protected getFormElement() { return undefined; } - private _type: UmbTreePickerSource['type'] = 'content'; + private _type: UmbContentPickerSource['type'] = 'content'; @property() - public set type(newType: UmbTreePickerSource['type']) { + public set type(newType: UmbContentPickerSource['type']) { const oldType = this._type; if (newType?.toLowerCase() !== this._type) { - this._type = newType?.toLowerCase() as UmbTreePickerSource['type']; + this._type = newType?.toLowerCase() as UmbContentPickerSource['type']; this.requestUpdate('type', oldType); } } - public get type(): UmbTreePickerSource['type'] { + public get type(): UmbContentPickerSource['type'] { return this._type; } @@ -152,10 +153,10 @@ export class UmbInputTreeElement extends UUIFormControlMixin(UmbLitElement, '') ]; } -export default UmbInputTreeElement; +export default UmbInputContentElement; declare global { interface HTMLElementTagNameMap { - 'umb-input-tree': UmbInputTreeElement; + [elementName]: UmbInputContentElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/components/input-content/input-content.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/components/input-content/input-content.stories.ts new file mode 100644 index 0000000000..4a940912b2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/components/input-content/input-content.stories.ts @@ -0,0 +1,15 @@ +import type { Meta, StoryObj } from '@storybook/web-components'; +import './input-content.element.js'; +import type { UmbInputContentElement } from './input-content.element.js'; + +const meta: Meta = { + title: 'Components/Inputs/Content', + component: 'umb-input-content', +}; + +export default meta; +type Story = StoryObj; + +export const Overview: Story = { + args: {}, +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/input-tree/input-tree.test.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/components/input-content/input-content.test.ts similarity index 60% rename from src/Umbraco.Web.UI.Client/src/packages/core/tree/components/input-tree/input-tree.test.ts rename to src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/components/input-content/input-content.test.ts index afabcaa6d2..6781940b92 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/input-tree/input-tree.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/components/input-content/input-content.test.ts @@ -1,15 +1,15 @@ import { expect, fixture, html } from '@open-wc/testing'; -import { UmbInputTreeElement } from './input-tree.element.js'; +import { UmbInputContentElement } from './input-content.element.js'; import { type UmbTestRunnerWindow, defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; -describe('UmbInputTreeElement', () => { - let element: UmbInputTreeElement; +describe('UmbInputContentElement', () => { + let element: UmbInputContentElement; beforeEach(async () => { - element = await fixture(html` `); + element = await fixture(html` `); }); it('is defined with its own instance', () => { - expect(element).to.be.instanceOf(UmbInputTreeElement); + expect(element).to.be.instanceOf(UmbInputContentElement); }); if ((window as UmbTestRunnerWindow).__UMBRACO_TEST_RUN_A11Y_TEST) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-type/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-type/manifests.ts new file mode 100644 index 0000000000..ebe91862c5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-type/manifests.ts @@ -0,0 +1,13 @@ +import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifest: ManifestPropertyEditorUi = { + type: 'propertyEditorUi', + alias: 'Umb.PropertyEditorUi.ContentPicker.SourceType', + name: 'Content Picker Source Type Property Editor UI', + element: () => import('./property-editor-ui-content-picker-source-type.element.js'), + meta: { + label: 'Content Picker Source Type Picker', + icon: 'icon-page-add', + group: 'pickers', + }, +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/tree-picker/config/source-type-picker/property-editor-ui-tree-picker-source-type-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-type/property-editor-ui-content-picker-source-type.element.ts similarity index 86% rename from src/Umbraco.Web.UI.Client/src/packages/property-editors/tree-picker/config/source-type-picker/property-editor-ui-tree-picker-source-type-picker.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-type/property-editor-ui-content-picker-source-type.element.ts index 54f606d8b1..a72180bbb0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/tree-picker/config/source-type-picker/property-editor-ui-tree-picker-source-type-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-type/property-editor-ui-content-picker-source-type.element.ts @@ -1,7 +1,7 @@ +import type { UmbContentPickerSource } from '../../types.js'; import type { UmbInputMemberTypeElement } from '@umbraco-cms/backoffice/member-type'; import type { UmbInputDocumentTypeElement } from '@umbraco-cms/backoffice/document-type'; import type { UmbInputMediaTypeElement } from '@umbraco-cms/backoffice/media-type'; -import type { UmbTreePickerSource } from '@umbraco-cms/backoffice/components'; import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @@ -9,10 +9,10 @@ import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property'; import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; /** - * @element umb-property-editor-ui-tree-picker-source-type-picker + * @element umb-property-editor-ui-content-picker-source-type */ -@customElement('umb-property-editor-ui-tree-picker-source-type-picker') -export class UmbPropertyEditorUITreePickerSourceTypePickerElement +@customElement('umb-property-editor-ui-content-picker-source-type') +export class UmbPropertyEditorUIContentPickerSourceTypeElement extends UmbLitElement implements UmbPropertyEditorUiElement { @@ -51,7 +51,7 @@ export class UmbPropertyEditorUITreePickerSourceTypePickerElement (value) => { if (!value) return; - const startNode = value as UmbTreePickerSource; + const startNode = value as UmbContentPickerSource; if (startNode?.type) { // If we had a sourceType before, we can see this as a change and not the initial value, // so let's reset the value, so we don't carry over content-types to the new source type. @@ -127,10 +127,10 @@ export class UmbPropertyEditorUITreePickerSourceTypePickerElement } } -export default UmbPropertyEditorUITreePickerSourceTypePickerElement; +export default UmbPropertyEditorUIContentPickerSourceTypeElement; declare global { interface HTMLElementTagNameMap { - 'umb-property-editor-ui-tree-picker-source-type-picker': UmbPropertyEditorUITreePickerSourceTypePickerElement; + 'umb-property-editor-ui-content-picker-source-type': UmbPropertyEditorUIContentPickerSourceTypeElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source/input-content-picker-source/index.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source/input-content-picker-source/index.ts new file mode 100644 index 0000000000..f45438cbdc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source/input-content-picker-source/index.ts @@ -0,0 +1 @@ +export * from './input-content-picker-source.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree-picker-source/input-tree-picker-source.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source/input-content-picker-source/input-content-picker-source.element.ts similarity index 66% rename from src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree-picker-source/input-tree-picker-source.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source/input-content-picker-source/input-content-picker-source.element.ts index be4d8e0aeb..053b347712 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree-picker-source/input-tree-picker-source.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source/input-content-picker-source/input-content-picker-source.element.ts @@ -1,40 +1,23 @@ -import type { UmbInputDocumentRootPickerElement } from '@umbraco-cms/backoffice/document'; +import type { UmbContentPickerDynamicRoot, UmbContentPickerSourceType } from '../../../types.js'; +import type { UmbInputContentPickerDocumentRootElement } from '../../../dynamic-root/input-content-picker-document-root/input-content-picker-document-root.element.js'; import { html, customElement, property, css, state, nothing } from '@umbraco-cms/backoffice/external/lit'; import type { UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; import { UUIFormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; -export type UmbTreePickerSource = { - type: UmbTreePickerSourceType; - id?: string; - dynamicRoot?: UmbTreePickerDynamicRoot; -}; +import '../../../dynamic-root/input-content-picker-document-root/input-content-picker-document-root.element.js'; -export type UmbTreePickerSourceType = 'content' | 'member' | 'media'; - -export type UmbTreePickerDynamicRoot = { - originAlias: string; - originKey?: string; - querySteps?: Array; -}; - -export type UmbTreePickerDynamicRootQueryStep = { - unique: string; - alias: string; - anyOfDocTypeKeys?: Array; -}; - -@customElement('umb-input-tree-picker-source') -export class UmbInputTreePickerSourceElement extends UUIFormControlMixin(UmbLitElement, '') { +@customElement('umb-input-content-picker-source') +export class UmbInputContentPickerSourceElement extends UUIFormControlMixin(UmbLitElement, '') { protected getFormElement() { return undefined; } - #type: UmbTreePickerSourceType = 'content'; + #type: UmbContentPickerSourceType = 'content'; @property() - public set type(value: UmbTreePickerSourceType) { + public set type(value: UmbContentPickerSourceType) { if (value === undefined) { value = this.#type; } @@ -49,7 +32,7 @@ export class UmbInputTreePickerSourceElement extends UUIFormControlMixin(UmbLitE this.requestUpdate('type', oldValue); } - public get type(): UmbTreePickerSourceType { + public get type(): UmbContentPickerSourceType { return this.#type; } @@ -57,7 +40,7 @@ export class UmbInputTreePickerSourceElement extends UUIFormControlMixin(UmbLitE nodeId?: string; @property({ attribute: false }) - dynamicRoot?: UmbTreePickerDynamicRoot | undefined; + dynamicRoot?: UmbContentPickerDynamicRoot | undefined; @state() _options: Array