diff --git a/src/Umbraco.Web.UI.Client/.vscode/settings.json b/src/Umbraco.Web.UI.Client/.vscode/settings.json index f07b4ab2bd..15492763ab 100644 --- a/src/Umbraco.Web.UI.Client/.vscode/settings.json +++ b/src/Umbraco.Web.UI.Client/.vscode/settings.json @@ -5,8 +5,10 @@ "Backoffice", "combobox", "Elementable", + "invariantable", "Niels", "pickable", + "Registrator", "templating", "tinymce", "umbraco", diff --git a/src/Umbraco.Web.UI.Client/public-assets/App_Plugins/custom-bundle-package/index.js b/src/Umbraco.Web.UI.Client/public-assets/App_Plugins/custom-bundle-package/index.js new file mode 100644 index 0000000000..3301081811 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/public-assets/App_Plugins/custom-bundle-package/index.js @@ -0,0 +1,13 @@ +export const manifests = [ + { + type: 'section', + alias: 'MyBundle.Section.Custom', + name: 'Custom Section', + js: '/App_Plugins/section.js', + weight: 1, + meta: { + label: 'My Bundle Section', + pathname: 'my-custom-bundle', + }, + } +]; diff --git a/src/Umbraco.Web.UI.Client/public-assets/App_Plugins/custom-bundle-package/package-manifest.json b/src/Umbraco.Web.UI.Client/public-assets/App_Plugins/custom-bundle-package/package-manifest.json new file mode 100644 index 0000000000..97460d0244 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/public-assets/App_Plugins/custom-bundle-package/package-manifest.json @@ -0,0 +1,12 @@ +{ + "name": "My Package Name", + "version": "1.0.0", + "extensions": [ + { + "type": "bundle", + "alias": "My.Package.Bundle", + "name": "My Package Bundle", + "js": "/App_Plugins/custom-bundle-package/index.js" + } + ] +} diff --git a/src/Umbraco.Web.UI.Client/public-assets/App_Plugins/custom-entrypoint.js b/src/Umbraco.Web.UI.Client/public-assets/App_Plugins/custom-entrypoint.js index 8de2547b92..8c880ceb62 100644 --- a/src/Umbraco.Web.UI.Client/public-assets/App_Plugins/custom-entrypoint.js +++ b/src/Umbraco.Web.UI.Client/public-assets/App_Plugins/custom-entrypoint.js @@ -1,4 +1,4 @@ console.log('Hello from the custom entrypoint file!'); -export default function () { - console.log('Hello from the custom entrypoint inside the default function!'); +export function onInit() { + console.log('Hello from the custom entrypoint inside the onInit function!'); } 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 8c7779b9a9..5539d99b2a 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 @@ -1,5 +1,5 @@ import { UmbBackofficeContext, UMB_BACKOFFICE_CONTEXT_TOKEN } from './backoffice.context.js'; -import { UmbExtensionInitializer } from './extension-initializer.controller.js'; +import { UmbServerExtensionRegistrator } from './server-extension-registrator.controller.js'; import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { @@ -28,14 +28,28 @@ const CORE_PACKAGES = [ @customElement('umb-backoffice') export class UmbBackofficeElement extends UmbLitElement { + + /** + * Backoffice extension registry. + * This enables to register and unregister extensions via DevTools, or just via querying this element via the DOM. + * @type {UmbExtensionsRegistry} + */ + public extensionRegistry = umbExtensionsRegistry; + constructor() { super(); + this.provideContext(UMB_BACKOFFICE_CONTEXT_TOKEN, new UmbBackofficeContext(this)); new UmbBundleExtensionInitializer(this, umbExtensionsRegistry); new UmbEntryPointExtensionInitializer(this, umbExtensionsRegistry); + new UmbServerExtensionRegistrator(this, umbExtensionsRegistry); + + // So far local packages are this simple to registerer, so no need for a manager to do that: + CORE_PACKAGES.forEach(async (packageImport) => { + const packageModule = await packageImport; + umbExtensionsRegistry.registerMany(packageModule.extensions); + }); - const extensionInitializer = new UmbExtensionInitializer(this, umbExtensionsRegistry); - extensionInitializer.setLocalPackages(CORE_PACKAGES); } render() { diff --git a/src/Umbraco.Web.UI.Client/src/apps/backoffice/extension-initializer.controller.ts b/src/Umbraco.Web.UI.Client/src/apps/backoffice/server-extension-registrator.controller.ts similarity index 77% rename from src/Umbraco.Web.UI.Client/src/apps/backoffice/extension-initializer.controller.ts rename to src/Umbraco.Web.UI.Client/src/apps/backoffice/server-extension-registrator.controller.ts index 4c846ebe4c..fc56c583c0 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/backoffice/extension-initializer.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/backoffice/server-extension-registrator.controller.ts @@ -1,44 +1,22 @@ -import { Subject } from '@umbraco-cms/backoffice/external/rxjs'; import { PackageResource, OpenAPI } from '@umbraco-cms/backoffice/backend-api'; import { UmbBaseController, UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbBackofficeExtensionRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; import { ManifestBase, isManifestJSType } from '@umbraco-cms/backoffice/extension-api'; -// TODO: consider if this can be replaced by the new extension controllers. +// TODO: consider if this can be replaced by the new extension controllers // TODO: move local part out of this, and name something with server. -export class UmbExtensionInitializer extends UmbBaseController { +export class UmbServerExtensionRegistrator extends UmbBaseController { #extensionRegistry: UmbBackofficeExtensionRegistry; - #unobserve = new Subject(); - #localPackages: Array> = []; #apiBaseUrl = OpenAPI.BASE; constructor(host: UmbControllerHost, extensionRegistry: UmbBackofficeExtensionRegistry) { - super(host, UmbExtensionInitializer.name); + super(host, UmbServerExtensionRegistrator.name); this.#extensionRegistry = extensionRegistry; - } - - setLocalPackages(localPackages: Array>) { - this.#localPackages = localPackages; - this.#loadLocalPackages(); - } - - hostConnected(): void { + // TODO: This was before in hostConnected(), but I don't see the reason to wait. lets just do it right away. this.#loadServerPackages(); } - hostDisconnected(): void { - this.#unobserve.next(); - this.#unobserve.complete(); - } - - async #loadLocalPackages() { - this.#localPackages.forEach(async (packageImport) => { - const packageModule = await packageImport; - this.#extensionRegistry.registerMany(packageModule.extensions); - }); - } - async #loadServerPackages() { /* TODO: we need a new endpoint here, to remove the dependency on the package repository, to get the modules available for the backoffice scope / we will need a similar endpoint for the login, installer etc at some point. diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/bundle-extension-initializer.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/bundle-extension-initializer.ts index 72350d2002..f35a77972d 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/bundle-extension-initializer.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/bundle-extension-initializer.ts @@ -1,15 +1,16 @@ import type { ManifestBase, ManifestBundle } from './types.js'; import { loadExtension } from './load-extension.function.js'; import { UmbExtensionRegistry } from './registry/extension.registry.js'; -import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { UmbBaseController, UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; -export class UmbBundleExtensionInitializer { +export class UmbBundleExtensionInitializer extends UmbBaseController { #extensionRegistry; #bundleMap = new Map(); constructor(host: UmbControllerHostElement, extensionRegistry: UmbExtensionRegistry) { + super(host); this.#extensionRegistry = extensionRegistry; - extensionRegistry.extensionsOfType('bundle').subscribe((bundles) => { + this.observe(extensionRegistry.extensionsOfType('bundle'), (bundles) => { // Unregister removed bundles: this.#bundleMap.forEach((existingBundle) => { if (!bundles.find((b) => b.alias === existingBundle.alias)) { diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/entry-point-extension-initializer.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/entry-point-extension-initializer.ts index fb1558962c..0a8d863d32 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/entry-point-extension-initializer.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/entry-point-extension-initializer.ts @@ -1,18 +1,21 @@ +import { UmbBaseController } from '../controller-api/controller.class.js'; import type { ManifestEntryPoint } from './types.js'; import { hasInitExport } from './has-init-export.function.js'; import { loadExtension } from './load-extension.function.js'; import { UmbExtensionRegistry } from './registry/extension.registry.js'; import { UmbElement } from '@umbraco-cms/backoffice/element-api'; -export class UmbEntryPointExtensionInitializer { +export class UmbEntryPointExtensionInitializer extends UmbBaseController { + #host; #extensionRegistry; #entryPointMap = new Map(); constructor(host: UmbElement, extensionRegistry: UmbExtensionRegistry) { + super(host); this.#host = host; this.#extensionRegistry = extensionRegistry; - extensionRegistry.extensionsOfType('entryPoint').subscribe((entryPoints) => { + this.observe(extensionRegistry.extensionsOfType('entryPoint'), (entryPoints) => { entryPoints.forEach((entryPoint) => { if (this.#entryPointMap.has(entryPoint.alias)) return; this.#entryPointMap.set(entryPoint.alias, entryPoint); diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/manifests.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/manifests.handlers.ts index ff77a3abbf..d942db27ab 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/manifests.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/manifests.handlers.ts @@ -8,6 +8,18 @@ export const manifestDevelopmentHandler = rest.get(umbracoPath('/package/manifes // Respond with a 200 status code ctx.status(200), ctx.json([ + { + name: 'My Package Name', + version: '1.0.0', + extensions: [ + { + type: 'bundle', + alias: 'My.Package.Bundle', + name: 'My Package Bundle', + js: '/App_Plugins/custom-bundle-package/index.js', + }, + ], + }, { name: 'Named Package', version: '1.0.0', diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-selection-actions.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-selection-actions.element.ts index 9fabdf3601..ae694f4fe4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-selection-actions.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-selection-actions.element.ts @@ -51,12 +51,12 @@ export class UmbCollectionSelectionActionsElement extends UmbLitElement { // TODO: Make sure it only updates on length change. this.observe(this._collectionContext.items, (mediaItems) => { this._nodesLength = mediaItems.length; - }); + }, 'observeItem'); this.observe(this._collectionContext.selection, (selection) => { this._selectionLength = selection.length; this._selection = selection; - }); + }, 'observeSelection'); } private _renderSelectionCount() { @@ -74,6 +74,7 @@ export class UmbCollectionSelectionActionsElement extends UmbLitElement { (bulkActions) => { this._entityBulkActions = bulkActions; } + , 'observeEntityBulkActions' ); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/property-editor-config/property-editor-config.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/property-editor-config/property-editor-config.element.ts index 67c14c5490..31cdf53688 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/property-editor-config/property-editor-config.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/property-editor-config/property-editor-config.element.ts @@ -1,37 +1,22 @@ import { html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from "@umbraco-cms/backoffice/style"; import { - PropertyEditorConfigDefaultData, PropertyEditorConfigProperty, - umbExtensionsRegistry, } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import { UMB_PROPERTY_EDITOR_SCHEMA_ALIAS_DEFAULT } from '@umbraco-cms/backoffice/property-editor'; +import { UMB_DATA_TYPE_VARIANT_CONTEXT } from '@umbraco-cms/backoffice/data-type'; /** - * @element umb-property-editor-config - * @description - Element for displaying the configuration for a Property Editor based on a Property Editor UI Alias and a Property Editor Model alias. + * @element umb-property-editor-config + * @description - Element for displaying the configuration for a Property Editor based on a Property Editor UI Alias and a Property Editor Model alias. + * This element requires a UMB_DATA_TYPE_WORKSPACE_CONTEXT to be present. */ @customElement('umb-property-editor-config') export class UmbPropertyEditorConfigElement extends UmbLitElement { - /** - * Property Editor UI Alias. The element will render configuration for a Property Editor UI with this alias. - * @type {string} - * @attr - * @default '' - */ - private _propertyEditorUiAlias = ''; - @property({ type: String, attribute: 'property-editor-ui-alias' }) - public get propertyEditorUiAlias(): string { - return this._propertyEditorUiAlias; - } - public set propertyEditorUiAlias(value: string) { - const oldVal = this._propertyEditorUiAlias; - this._propertyEditorUiAlias = value; - this.requestUpdate('propertyEditorUiAlias', oldVal); - this._observePropertyEditorUIConfig(); - } + + // TODO: Make this element generic, so its not bound to DATA-TYPEs. This will require moving some functionality of Data-Type-Context to this. and this might need to self provide a variant Context for its inner property editor UIs. + #variantContext?: typeof UMB_DATA_TYPE_VARIANT_CONTEXT.TYPE; /** * Data. The element will render configuration editors with values from this data. @@ -46,62 +31,17 @@ export class UmbPropertyEditorConfigElement extends UmbLitElement { @state() private _properties: Array = []; - private _propertyEditorSchemaConfigDefaultData: Array = []; - private _propertyEditorUISettingsDefaultData: Array = []; - private _configDefaultData?: Array; + constructor() { + super(); - private _propertyEditorSchemaConfigProperties: Array = []; - private _propertyEditorUISettingsProperties: Array = []; + this.consumeContext(UMB_DATA_TYPE_VARIANT_CONTEXT, (instance) => { + this.#variantContext = instance; + this.observe(this.#variantContext.properties, (properties) => { + this._properties = properties as Array; + }, 'observeProperties'); + }); - private _observePropertyEditorUIConfig() { - if (!this._propertyEditorUiAlias) return; - - this.observe( - umbExtensionsRegistry.getByTypeAndAlias('propertyEditorUi', this.propertyEditorUiAlias), - (manifest) => { - this._observePropertyEditorSchemaConfig( - manifest?.meta.propertyEditorSchemaAlias || UMB_PROPERTY_EDITOR_SCHEMA_ALIAS_DEFAULT - ); - this._propertyEditorUISettingsProperties = manifest?.meta.settings?.properties || []; - this._propertyEditorUISettingsDefaultData = manifest?.meta.settings?.defaultData || []; - this._mergeConfigProperties(); - this._mergeConfigDefaultData(); - } - ); - } - - private _observePropertyEditorSchemaConfig(propertyEditorSchemaAlias: string) { - this.observe( - umbExtensionsRegistry.getByTypeAndAlias('propertyEditorSchema', propertyEditorSchemaAlias), - (manifest) => { - this._propertyEditorSchemaConfigProperties = manifest?.meta.settings?.properties || []; - this._propertyEditorSchemaConfigDefaultData = manifest?.meta.settings?.defaultData || []; - this._mergeConfigProperties(); - this._mergeConfigDefaultData(); - } - ); - } - - private _mergeConfigProperties() { - this._properties = [...this._propertyEditorSchemaConfigProperties, ...this._propertyEditorUISettingsProperties]; - } - - private _mergeConfigDefaultData() { - this._configDefaultData = [ - ...this._propertyEditorSchemaConfigDefaultData, - ...this._propertyEditorUISettingsDefaultData, - ]; - } - - /** - * Get the stored value for a property. It will render the default value from the configuration if no value is stored in the database. - */ - private _getValue(property: PropertyEditorConfigProperty) { - const value = this.data.find((data) => data.alias === property.alias)?.value; - if (value) return value; - const defaultValue = this._configDefaultData?.find((data) => data.alias === property.alias)?.value; - return defaultValue ?? null; } render() { @@ -115,7 +55,6 @@ export class UmbPropertyEditorConfigElement extends UmbLitElement { description="${ifDefined(property.description)}" alias="${property.alias}" property-editor-ui-alias="${property.propertyEditorUiAlias}" - .value=${this._getValue(property)} .config=${property.config}> ` )} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/property-type-based-property/property-type-based-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/property-type-based-property/property-type-based-property.element.ts index 880886367f..07d3dd1715 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/property-type-based-property/property-type-based-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/property-type-based-property/property-type-based-property.element.ts @@ -2,8 +2,6 @@ import { UmbPropertyEditorConfig } from '../../property-editor/index.js'; import { UmbTextStyles } from "@umbraco-cms/backoffice/style"; import { css, html, ifDefined, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbDataTypeRepository } from '@umbraco-cms/backoffice/data-type'; -import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/document'; -import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import type { DataTypeResponseModel, PropertyTypeModelBaseModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; @@ -19,7 +17,6 @@ export class UmbPropertyTypeBasedPropertyElement extends UmbLitElement { this._property = value; if (this._property?.dataTypeId !== oldProperty?.dataTypeId) { this._observeDataType(this._property?.dataTypeId); - this._observeProperty(); } } private _property?: PropertyTypeModelBaseModel; @@ -33,53 +30,6 @@ export class UmbPropertyTypeBasedPropertyElement extends UmbLitElement { private _dataTypeRepository: UmbDataTypeRepository = new UmbDataTypeRepository(this); private _dataTypeObserver?: UmbObserverController; - @state() - private _value?: unknown; - - /** - * propertyVariantId. A VariantID to identify which the variant of this properties value. - * @public - * @type {UmbVariantId} - * @attr - * @default undefined - */ - @property({ type: Object, attribute: false }) - public get propertyVariantId(): UmbVariantId | undefined { - return this._propertyVariantId; - } - public set propertyVariantId(value: UmbVariantId | undefined) { - const oldValue = this._propertyVariantId; - if (value && oldValue?.equal(value)) return; - this._propertyVariantId = value; - this._observeProperty(); - this.requestUpdate('propertyVariantId', oldValue); - } - private _propertyVariantId?: UmbVariantId | undefined; - - private _workspaceContext?: typeof UMB_DOCUMENT_WORKSPACE_CONTEXT.TYPE; - - constructor() { - super(); - this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (workspaceContext) => { - this._workspaceContext = workspaceContext; - this._observeProperty(); - }); - } - - private _observePropertyValue?: UmbObserverController; - private _observeProperty() { - if (!this._workspaceContext || !this.property || !this._property?.alias) return; - - this._observePropertyValue?.destroy(); - this._observePropertyValue = this.observe( - this._workspaceContext.propertyValueByAlias(this._property.alias, this._propertyVariantId), - (value) => { - this._value = value; - }, - '_observePropertyValue' - ); - } - private async _observeDataType(dataTypeId?: string) { this._dataTypeObserver?.destroy(); if (dataTypeId) { @@ -115,8 +65,6 @@ export class UmbPropertyTypeBasedPropertyElement extends UmbLitElement { label=${ifDefined(this._property?.name)} description=${ifDefined(this._property?.description || undefined)} property-editor-ui-alias=${ifDefined(this._propertyEditorUiAlias)} - .value=${this._value} - .propertyVariantId=${this.propertyVariantId} .config=${this._dataTypeData}>`; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/variant-selector/variant-selector.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/variant-selector/variant-selector.element.ts index 2e7b9cbe1e..f25308712f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/variant-selector/variant-selector.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/variant-selector/variant-selector.element.ts @@ -1,18 +1,18 @@ +import { UmbVariantId } from '../../variant/variant-id.class.js'; import { UUIInputElement, UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; import { css, html, nothing, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; import { - UmbWorkspaceVariantContext, - UMB_WORKSPACE_VARIANT_CONTEXT_TOKEN, + UmbWorkspaceSplitViewContext, + UMB_WORKSPACE_SPLIT_VIEW_CONTEXT, + UMB_VARIANT_CONTEXT, ActiveVariant, + isNameablePropertySetContext, } from '@umbraco-cms/backoffice/workspace'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { DocumentVariantResponseModel, ContentStateModel } from '@umbraco-cms/backoffice/backend-api'; @customElement('umb-variant-selector') export class UmbVariantSelectorElement extends UmbLitElement { - // TODO: not jet used: - @property() - alias!: string; @state() _variants: Array = []; @@ -21,12 +21,13 @@ export class UmbVariantSelectorElement extends UmbLitElement { @state() _activeVariants: Array = []; - @property() + @property({attribute: false}) public get _activeVariantsCultures(): string[] { return this._activeVariants.map((el) => el.culture ?? '') ?? []; } - private _variantContext?: UmbWorkspaceVariantContext; + #splitViewContext?: UmbWorkspaceSplitViewContext; + #variantContext?: typeof UMB_VARIANT_CONTEXT.TYPE; @state() private _name?: string; @@ -46,18 +47,21 @@ export class UmbVariantSelectorElement extends UmbLitElement { constructor() { super(); - this.consumeContext(UMB_WORKSPACE_VARIANT_CONTEXT_TOKEN, (instance) => { - this._variantContext = instance; + this.consumeContext(UMB_WORKSPACE_SPLIT_VIEW_CONTEXT, (instance) => { + this.#splitViewContext = instance; this._observeVariants(); this._observeActiveVariants(); + }); + this.consumeContext(UMB_VARIANT_CONTEXT, (instance) => { + this.#variantContext = instance; this._observeVariantContext(); }); } private async _observeVariants() { - if (!this._variantContext) return; + if (!this.#splitViewContext) return; - const workspaceContext = this._variantContext.getWorkspaceContext(); + const workspaceContext = this.#splitViewContext.getWorkspaceContext(); if (workspaceContext) { this.observe( workspaceContext.variants, @@ -72,9 +76,9 @@ export class UmbVariantSelectorElement extends UmbLitElement { } private async _observeActiveVariants() { - if (!this._variantContext) return; + if (!this.#splitViewContext) return; - const workspaceContext = this._variantContext.getWorkspaceContext(); + const workspaceContext = this.#splitViewContext.getWorkspaceContext(); if (workspaceContext) { this.observe( workspaceContext.splitView.activeVariantsInfo, @@ -89,31 +93,20 @@ export class UmbVariantSelectorElement extends UmbLitElement { } private async _observeVariantContext() { - if (!this._variantContext) return; + if (!this.#variantContext) return; + + const variantId = this.#variantContext.getVariantId(); + this._culture = variantId.culture; + this._segment = variantId.segment; + this.updateVariantDisplayName(); this.observe( - this._variantContext.name, + this.#variantContext.name, (name) => { this._name = name; }, '_name' ); - this.observe( - this._variantContext.culture, - (culture) => { - this._culture = culture; - this.updateVariantDisplayName(); - }, - '_culture' - ); - this.observe( - this._variantContext.segment, - (segment) => { - this._segment = segment; - this.updateVariantDisplayName(); - }, - '_segment' - ); } private updateVariantDisplayName() { @@ -130,9 +123,8 @@ export class UmbVariantSelectorElement extends UmbLitElement { if (event instanceof UUIInputEvent) { const target = event.composedPath()[0] as UUIInputElement; - if (typeof target?.value === 'string') { - // TODO: create a setName method on EntityWorkspace: - this._variantContext?.setName(target.value); + if (typeof target?.value === 'string' && this.#variantContext && isNameablePropertySetContext(this.#variantContext)) { + this.#variantContext.setName(target.value); } } } @@ -149,17 +141,17 @@ export class UmbVariantSelectorElement extends UmbLitElement { } private _switchVariant(variant: DocumentVariantResponseModel) { - this._variantContext?.switchVariant(variant); + this.#splitViewContext?.switchVariant(UmbVariantId.Create(variant)); this._close(); } private _openSplitView(variant: DocumentVariantResponseModel) { - this._variantContext?.openSplitView(variant); + this.#splitViewContext?.openSplitView(UmbVariantId.Create(variant)); this._close(); } private _closeSplitView() { - this._variantContext?.closeSplitView(); + this.#splitViewContext?.closeSplitView(); } private _isVariantActive(culture: string) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/variant-selector/variant-selector.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/variant-selector/variant-selector.stories.ts index c5e50500c7..4827af62be 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/variant-selector/variant-selector.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/variant-selector/variant-selector.stories.ts @@ -12,6 +12,5 @@ type Story = StoryObj; export const Overview: Story = { args: { - alias: 'myAlias', }, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/content-type-structure-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/content-type-structure-manager.class.ts index 44165935af..9801557be4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/content-type-structure-manager.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/content-type-structure-manager.class.ts @@ -313,6 +313,58 @@ export class UmbContentTypePropertyStructureManager { + for (const docType of docTypes) { + const foundProp = docType.properties?.find((property) => property.id === propertyId); + if(foundProp) { + return foundProp; + } + } + return undefined; + }); + } + async propertyStructureByAlias( + propertyAlias: string + ) { + await this.#init; + return this.#documentTypes.asObservablePart((docTypes) => { + for (const docType of docTypes) { + const foundProp = docType.properties?.find((property) => property.alias === propertyAlias); + if(foundProp) { + return foundProp; + } + } + return undefined; + }); + } + + async getPropertyStructureById(propertyId: string) { + await this.#init; + for (const docType of this.#documentTypes.getValue()) { + const foundProp = docType.properties?.find((property) => property.id === propertyId); + if(foundProp) { + return foundProp; + } + } + return undefined; + } + async getPropertyStructureByAlias(propertyAlias: string) { + await this.#init; + for (const docType of this.#documentTypes.getValue()) { + const foundProp = docType.properties?.find((property) => property.alias === propertyAlias); + if(foundProp) { + return foundProp; + } + } + return undefined; + } + + /* rootDocumentTypeName() { return this.#documentTypes.asObservablePart((docTypes) => { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/block-grid/property-editor-ui-block-grid.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/block-grid/property-editor-ui-block-grid.element.ts index 99f4bf64d3..11e60fb0db 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/block-grid/property-editor-ui-block-grid.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/block-grid/property-editor-ui-block-grid.element.ts @@ -1,4 +1,3 @@ -import { UMB_WORKSPACE_VARIANT_CONTEXT_TOKEN } from '../../../workspace/workspace-variant/workspace-variant.context.js'; import { UMB_WORKSPACE_PROPERTY_CONTEXT_TOKEN } from '../../../workspace/workspace-property/workspace-property.context.js'; import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from "@umbraco-cms/backoffice/style"; @@ -13,7 +12,6 @@ import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/ */ @customElement('umb-property-editor-ui-block-grid') export class UmbPropertyEditorUIBlockGridElement extends UmbLitElement implements UmbPropertyEditorExtensionElement { - private _variantContext?: typeof UMB_WORKSPACE_VARIANT_CONTEXT_TOKEN.TYPE; @property() value = ''; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repositories/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repositories/index.ts new file mode 100644 index 0000000000..a7d3719d54 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/repositories/index.ts @@ -0,0 +1 @@ +export * from './repository.interface.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repositories/repository.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repositories/repository.interface.ts new file mode 100644 index 0000000000..1ea5957a38 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/repositories/repository.interface.ts @@ -0,0 +1,12 @@ +export interface UmbRepository { + + /** + * Get the type of the entity + * + * @public + * @type {EntityType} + * @returns undefined + */ + readonly ENTITY_TYPE: EntityType; + +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts index 09824e4e46..2f750067d6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts @@ -1,15 +1,21 @@ export type variantObject = { culture?: string | null; segment?: string | null }; +export const INVARIANT_CULTURE = 'invariant'; + export class UmbVariantId { public static Create(variantData: variantObject): UmbVariantId { return Object.freeze(new UmbVariantId(variantData)); } + public static CreateInvariant(): UmbVariantId { + return Object.freeze(new UmbVariantId({})); + } + public readonly culture: string | null = null; public readonly segment: string | null = null; constructor(variantData: variantObject) { - this.culture = (variantData.culture === 'invariant' ? null : variantData.culture) ?? null; + this.culture = (variantData.culture === INVARIANT_CULTURE ? null : variantData.culture) ?? null; this.segment = variantData.segment ?? null; } @@ -22,13 +28,26 @@ export class UmbVariantId { } public toString(): string { - return (this.culture || 'invariant') + (this.segment ? `_${this.segment}` : ''); + return (this.culture || INVARIANT_CULTURE) + (this.segment ? `_${this.segment}` : ''); + } + + public toCultureString(): string { + return (this.culture || INVARIANT_CULTURE); + } + + public toSegmentString(): string { + return (this.segment || ''); + } + + public isInvariant(): boolean { + return this.culture === null && this.segment === null; } public toObject(): variantObject { return { culture: this.culture, segment: this.segment }; } + // TODO: needs localization option: public toDifferencesString(variantId: UmbVariantId): string { let r = ''; 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 22cd49d585..b03f096011 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 @@ -1,5 +1,7 @@ +export * from './variant-context/index.js'; export * from './workspace-action-menu/index.js'; export * from './workspace-action/index.js'; +export * from './workspace-alias.condition.js'; export * from './workspace-context/index.js'; export * from './workspace-editor/index.js'; export * from './workspace-footer/index.js'; @@ -8,5 +10,4 @@ export * from './workspace-modal/index.js'; export * from './workspace-property-layout/workspace-property-layout.element.js'; export * from './workspace-property/index.js'; export * from './workspace-split-view-manager.class.js'; -export * from './workspace-variant/index.js'; -export * from './workspace-alias.condition.js'; +export * from './workspace-split-view/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/index.ts new file mode 100644 index 0000000000..8c2af922ab --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/index.ts @@ -0,0 +1,5 @@ +export * from './variant-context.interface.js'; +export * from './variant-context.token.js'; +export * from './nameable-variant-context.interface.js'; +export * from './nameable-variant-context.token.js'; +export * from './invariant-workspace-variant-context.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/invariant-workspace-variant-context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/invariant-workspace-variant-context.ts new file mode 100644 index 0000000000..8eaeb84b17 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/invariant-workspace-variant-context.ts @@ -0,0 +1,63 @@ +import { DocumentVariantResponseModel } from "@umbraco-cms/backoffice/backend-api"; +import { UmbBaseController, UmbControllerHost } from "@umbraco-cms/backoffice/controller-api"; +import { UmbObjectState } from "@umbraco-cms/backoffice/observable-api"; +import { UmbVariantId } from "@umbraco-cms/backoffice/variant"; +import { UMB_VARIANT_CONTEXT, UmbVariantContext, UmbInvariantableWorkspaceContextInterface } from "@umbraco-cms/backoffice/workspace"; + +export class UmbInvariantWorkspaceVariantContext extends UmbBaseController implements UmbVariantContext { + + protected _workspace: WorkspaceType; + + #currentVariant = new UmbObjectState(undefined); + currentVariant = this.#currentVariant.asObservable(); + + name = this.#currentVariant.asObservablePart((x) => x?.name); + culture = this.#currentVariant.asObservablePart((x) => x?.culture); + segment = this.#currentVariant.asObservablePart((x) => x?.segment); + + // default data: + + getVariantId() { + return UmbVariantId.CreateInvariant(); + } + getType() { + return this._workspace.getEntityType(); + } + getUnique() { + return this._workspace.getEntityId(); + } + getName() { + return this._workspace.getName(); + } + setName(name: string) { + this._workspace.setName(name); + } + + + + constructor(host: UmbControllerHost, workspace: WorkspaceType) { + // The controller alias, is a very generic name cause we want only one of these for this controller host. + super(host, 'variantContext'); + this._workspace = workspace; + + this.provideContext(UMB_VARIANT_CONTEXT, this); + } + + + + /** + * TODO: Write proper JSDocs here. + * Ideally do not use these methods, its better to communicate directly with the workspace, but if you do not know the property variant id, then this will figure it out for you. So good for externals to set or get values of a property. + */ + async propertyValueByAlias(propertyAlias: string) { + return this._workspace.propertyValueByAlias(propertyAlias); + } + + /** + * TODO: Write proper JSDocs here. + * Ideally do not use these methods, its better to communicate directly with the workspace, but if you do not know the property variant id, then this will figure it out for you. So good for externals to set or get values of a property. + */ + async setPropertyValue(propertyAlias: string, value: unknown) { + return this._workspace.setPropertyValue(propertyAlias, value); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/nameable-variant-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/nameable-variant-context.interface.ts new file mode 100644 index 0000000000..e92e823a41 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/nameable-variant-context.interface.ts @@ -0,0 +1,8 @@ +import { UmbVariantContext } from "./variant-context.interface.js"; + +/** + * A variant context with ability to set the name of it. +*/ +export interface UmbNameableVariantContext extends UmbVariantContext { + setName(name:string): void +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/nameable-variant-context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/nameable-variant-context.token.ts new file mode 100644 index 0000000000..307962082f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/nameable-variant-context.token.ts @@ -0,0 +1,9 @@ +import { type UmbVariantContext } from "./variant-context.interface.js"; +import { UmbNameableVariantContext } from "./nameable-variant-context.interface.js"; +import { UmbContextToken } from "@umbraco-cms/backoffice/context-api"; + +export const isNameablePropertySetContext = (context: UmbVariantContext): context is UmbNameableVariantContext => 'setName' in context; + +export const UMB_NAMEABLE_VARIANT_CONTEXT = new UmbContextToken( + "UmbVariantContext", + isNameablePropertySetContext); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/variant-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/variant-context.interface.ts new file mode 100644 index 0000000000..48fe5bc9a3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/variant-context.interface.ts @@ -0,0 +1,34 @@ +import type { UmbVariantId } from "../../variant/variant-id.class.js"; +import type { Observable } from "@umbraco-cms/backoffice/external/rxjs"; + +/** + * A variant context, represents a set of properties. + * This can take form as many, so to list a few: + * - A specific variant of content + * - Content that does not vary + * - A block. + * - A DataType configuration. + * + * The base type of this holds a Name and some Properties. + * Some might be enriches with Variant Info, like culture and segment. + * Others might have saved publishing status. + * Also setting the name is an additional feature. + */ +export interface UmbVariantContext { + + getType(): string; + getUnique(): string | undefined; + //getUniqueName(): string; + getVariantId: (() => UmbVariantId); + + getName(): string | undefined; + readonly name: Observable; + + destroy(): void; + + // Property methods: + propertyVariantId?: ((propertyAlias: string) => Promise>); + propertyValueByAlias(propertyAlias: string): Promise>; + setPropertyValue(propertyAlias: string, value: unknown): Promise; + +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/variant-context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/variant-context.token.ts new file mode 100644 index 0000000000..1827b68e81 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/variant-context.token.ts @@ -0,0 +1,4 @@ +import { type UmbVariantContext } from "./variant-context.interface.js"; +import { UmbContextToken } from "@umbraco-cms/backoffice/context-api"; + +export const UMB_VARIANT_CONTEXT = new UmbContextToken("UmbVariantContext"); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/index.ts index 39790ef18e..776e5ce113 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/index.ts @@ -1,7 +1,10 @@ export * from './entity-manager-controller.js'; -export * from './workspace-context.js'; +export * from './property-structure-workspace-context.interface.js'; +export * from './publishable-workspace-context.interface.js'; +export * from './saveable-workspace-context.interface.js'; +export * from './variant-workspace-context.token.js'; export * from './workspace-context.interface.js'; -export * from './workspace-entity-context.interface.js'; -export * from './workspace-invariantable-entity-context.interface.js'; -export * from './workspace-variable-entity-context.interface.js'; +export * from './workspace-context.js'; export * from './workspace-context.token.js'; +export * from './workspace-invariantable-context.interface.js'; +export * from './workspace-variantable-context.interface.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/property-structure-workspace-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/property-structure-workspace-context.interface.ts new file mode 100644 index 0000000000..5826e02d2c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/property-structure-workspace-context.interface.ts @@ -0,0 +1,10 @@ +import type { UmbWorkspaceContextInterface } from './workspace-context.interface.js'; +import { Observable } from '@umbraco-cms/backoffice/external/rxjs'; +import type { ValueModelBaseModel } from '@umbraco-cms/backoffice/backend-api'; + +export interface UmbPropertyStructureWorkspaceContextInterface + extends UmbWorkspaceContextInterface { + + propertyStructureById(id: string): Promise>; + +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/publishable-workspace-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/publishable-workspace-context.interface.ts new file mode 100644 index 0000000000..2c562b628d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/publishable-workspace-context.interface.ts @@ -0,0 +1,7 @@ +import type { UmbSaveableWorkspaceContextInterface } from './saveable-workspace-context.interface.js'; + +export interface UmbPublishableWorkspaceContextInterface + extends UmbSaveableWorkspaceContextInterface { + //getData(): EntityType | undefined; + publish(): Promise; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/saveable-workspace-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/saveable-workspace-context.interface.ts new file mode 100644 index 0000000000..289e233fa5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/saveable-workspace-context.interface.ts @@ -0,0 +1,7 @@ +import type { UmbWorkspaceContextInterface } from './workspace-context.interface.js'; + +export interface UmbSaveableWorkspaceContextInterface + extends UmbWorkspaceContextInterface { + //getData(): EntityType | undefined; + save(): Promise; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/variant-workspace-context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/variant-workspace-context.token.ts new file mode 100644 index 0000000000..7c0a70e7a1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/variant-workspace-context.token.ts @@ -0,0 +1,9 @@ +import type { UmbWorkspaceContextInterface } from './workspace-context.interface.js'; +import type { UmbVariantableWorkspaceContextInterface } from './workspace-variantable-context.interface.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; +import type { UmbEntityBase } from '@umbraco-cms/backoffice/models'; + +export const UMB_VARIANT_WORKSPACE_CONTEXT_TOKEN = new UmbContextToken, UmbVariantableWorkspaceContextInterface>( + 'UmbWorkspaceContext', + (context): context is UmbVariantableWorkspaceContextInterface => 'variants' in context, +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-context.interface.ts index 2eaf8c53ac..4ff5c395bb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-context.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-context.interface.ts @@ -1,17 +1,19 @@ import { Observable } from '@umbraco-cms/backoffice/external/rxjs'; export interface UmbWorkspaceContextInterface { + destroy(): void; workspaceAlias: string; - repository: any; // TODO: add type + + save(): Promise; + // TODO: temp solution to bubble validation errors to the UI + setValidationErrors?(errorMap: any): void; + + getEntityId(): string | undefined; // Consider if this should go away now that we have getUnique() + // TODO: should we consider another name than entity type. File system files are not entities but still have this type. + getEntityType(): string; + isNew: Observable; getIsNew(): boolean | undefined; setIsNew(value: boolean): void; - getEntityId(): string | undefined; // COnsider if this should go away now that we have getUnique() - // TODO: should we consider another name than entity type. File system files are not entities but still have this type. - getEntityType(): string; - getData(): DataType | undefined; - save(): Promise; - destroy(): void; - // TODO: temp solution to bubble validation errors to the UI - setValidationErrors?(errorMap: any): void; + } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-context.ts index 9a332bf0e2..790f96c653 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-context.ts @@ -1,4 +1,4 @@ -import { UmbEntityWorkspaceContextInterface } from './workspace-entity-context.interface.js'; +import { UmbSaveableWorkspaceContextInterface } from './saveable-workspace-context.interface.js'; import { UmbBaseController, UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbBooleanState } from '@umbraco-cms/backoffice/observable-api'; import type { UmbEntityBase } from '@umbraco-cms/backoffice/models'; @@ -12,13 +12,13 @@ If so we need to align on a interface that all of these implements. otherwise co */ export abstract class UmbWorkspaceContext extends UmbBaseController - implements UmbEntityWorkspaceContextInterface + implements UmbSaveableWorkspaceContextInterface { public readonly host: UmbControllerHostElement; public readonly workspaceAlias: string; public readonly repository: RepositoryType; - // TODO: We could make a base type for workspace modal data, and use this here: As well as a base for the result, to make sure we always include the unique. + // TODO: We could make a base type for workspace modal data, and use this here: As well as a base for the result, to make sure we always include the unique (instead of the object type) public readonly modalContext?: UmbModalContext<{ preset: object }>; #isNew = new UmbBooleanState(undefined); @@ -60,4 +60,5 @@ export abstract class UmbWorkspaceContext; + } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-entity-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-entity-context.interface.ts deleted file mode 100644 index 28c083c53c..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-entity-context.interface.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { UmbWorkspaceContextInterface } from './workspace-context.interface.js'; - -export interface UmbEntityWorkspaceContextInterface - extends UmbWorkspaceContextInterface { - getEntityType(): string; // TODO: consider of this should be on the repository because a repo is responsible for one entity type - //getData(): EntityType | undefined; - save(): Promise; -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-invariantable-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-invariantable-context.interface.ts new file mode 100644 index 0000000000..07a16286ea --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-invariantable-context.interface.ts @@ -0,0 +1,20 @@ +import { UmbVariantId } from '../../variant/variant-id.class.js'; +import { UmbVariantContext } from '../variant-context/variant-context.interface.js'; +import type { UmbSaveableWorkspaceContextInterface } from './saveable-workspace-context.interface.js'; +import { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; + +export interface UmbInvariantableWorkspaceContextInterface + extends UmbSaveableWorkspaceContextInterface { + + // Name: + getName(): string | undefined; + setName(name: string): void; + + // Property: + propertyValueByAlias(alias: string): Promise>; + getPropertyValue(alias: string): ReturnType; + setPropertyValue(alias: string, value: unknown): Promise; + + createVariantContext(host: UmbControllerHost, variantId?: UmbVariantId): UmbVariantContext; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-invariantable-entity-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-invariantable-entity-context.interface.ts deleted file mode 100644 index d36117b3ee..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-invariantable-entity-context.interface.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { UmbEntityWorkspaceContextInterface } from './workspace-entity-context.interface.js'; -import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; -import type { ValueModelBaseModel } from '@umbraco-cms/backoffice/backend-api'; - -export interface UmbWorkspaceInvariantableEntityContextInterface - extends UmbEntityWorkspaceContextInterface { - getName(): void; - setName(name: string): void; - - propertyDataByAlias(alias: string): Observable; - propertyValueByAlias(alias: string): Observable; - getPropertyValue(alias: string): void; - setPropertyValue(alias: string, value: unknown): void; -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-variable-entity-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-variable-entity-context.interface.ts deleted file mode 100644 index a7f2da15ac..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-variable-entity-context.interface.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { UmbWorkspaceSplitViewManager } from '../workspace-split-view-manager.class.js'; -import type { UmbEntityWorkspaceContextInterface } from './workspace-entity-context.interface.js'; -import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; -import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; -import type { ValueModelBaseModel, VariantResponseModelBaseModel } from '@umbraco-cms/backoffice/backend-api'; - -export interface UmbWorkspaceVariableEntityContextInterface extends UmbEntityWorkspaceContextInterface { - variants: Observable>; - - splitView: UmbWorkspaceSplitViewManager; - - getName(variantId?: UmbVariantId): void; - setName(name: string, variantId?: UmbVariantId): void; - - getVariant(variantId: UmbVariantId): VariantResponseModelBaseModel | undefined; - - propertyDataByAlias(alias: string, variantId?: UmbVariantId): Observable; - propertyValueByAlias(alias: string, variantId?: UmbVariantId): Observable; - getPropertyValue(alias: string, variantId?: UmbVariantId): void; - setPropertyValue(alias: string, value: unknown, variantId?: UmbVariantId): void; -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-variantable-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-variantable-context.interface.ts new file mode 100644 index 0000000000..3ce0dd3238 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-variantable-context.interface.ts @@ -0,0 +1,28 @@ +import type { UmbWorkspaceSplitViewManager } from '../workspace-split-view-manager.class.js'; +import { UmbVariantContext } from '../variant-context/variant-context.interface.js'; +import type { UmbSaveableWorkspaceContextInterface } from './saveable-workspace-context.interface.js'; +import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; +import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import type { VariantResponseModelBaseModel } from '@umbraco-cms/backoffice/backend-api'; +import { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +export interface UmbVariantableWorkspaceContextInterface extends UmbSaveableWorkspaceContextInterface { + + // Name: + getName(variantId?: UmbVariantId): string | undefined; + setName(name: string, variantId?: UmbVariantId): void; + + // Variant: + variants: Observable>; + splitView: UmbWorkspaceSplitViewManager; + getVariant(variantId: UmbVariantId): VariantResponseModelBaseModel | undefined; + + // Property: + // This one is async cause it needs to structure to provide this data: + propertyValueByAlias(alias: string, variantId?: UmbVariantId): Promise>; + getPropertyValue(alias: string, variantId?: UmbVariantId): ReturnValue | undefined; + setPropertyValue(alias: string, value: unknown, variantId?: UmbVariantId): Promise; + //propertyDataByAlias(alias: string, variantId?: UmbVariantId): Observable; + + createVariantContext(host: UmbControllerHost, variantId?: UmbVariantId): UmbVariantContext; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-property/workspace-property.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-property/workspace-property.context.ts index ef68b157b3..dbcae01279 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-property/workspace-property.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-property/workspace-property.context.ts @@ -1,9 +1,8 @@ -import { UmbWorkspaceVariableEntityContextInterface } from '../workspace-context/workspace-variable-entity-context.interface.js'; import { UmbPropertyEditorExtensionElement } from '../../extension-registry/interfaces/property-editor-ui-extension-element.interface.js'; import { type WorkspacePropertyData } from '../types/workspace-property-data.type.js'; -import { UMB_WORKSPACE_VARIANT_CONTEXT_TOKEN, UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; +import { UMB_VARIANT_CONTEXT } from '@umbraco-cms/backoffice/workspace'; import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; -import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { UmbBaseController, type UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbClassState, UmbObjectState, @@ -12,14 +11,12 @@ import { UmbBasicState, } from '@umbraco-cms/backoffice/observable-api'; import { - UmbContextConsumerController, UmbContextProviderController, UmbContextToken, } from '@umbraco-cms/backoffice/context-api'; import { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; -export class UmbWorkspacePropertyContext { - #host: UmbControllerHostElement; +export class UmbWorkspacePropertyContext extends UmbBaseController { private _providerController: UmbContextProviderController; @@ -43,52 +40,77 @@ export class UmbWorkspacePropertyContext { return this._editor.getValue(); } - #workspaceVariantId?: UmbVariantId; - + // property variant ID: #variantId = new UmbClassState(undefined); public readonly variantId = this.#variantId.asObservable(); private _variantDifference = new UmbStringState(undefined); public readonly variantDifference = this._variantDifference.asObservable(); - private _workspaceContext?: UmbWorkspaceVariableEntityContextInterface; - private _workspaceVariantConsumer?: UmbContextConsumerController; + #variantContext?: typeof UMB_VARIANT_CONTEXT.TYPE; constructor(host: UmbControllerHostElement) { - this.#host = host; - new UmbContextConsumerController(host, UMB_WORKSPACE_CONTEXT, (workspaceContext) => { - this._workspaceContext = workspaceContext as UmbWorkspaceVariableEntityContextInterface; + super(host); + + this.consumeContext(UMB_VARIANT_CONTEXT, (variantContext) => { + this.#variantContext = variantContext; + this._generateVariantDifferenceString(); + this._observeProperty(); + }); + + this.observe(this.alias, () => { + this._observeProperty(); }); this._providerController = new UmbContextProviderController(host, UMB_WORKSPACE_PROPERTY_CONTEXT_TOKEN, this); - this.configValues.subscribe((configValues) => { + this.observe(this.configValues, (configValues) => { this.#configCollection.next(configValues ? new UmbPropertyEditorConfigCollection(configValues) : undefined); }); - this.variantId.subscribe((propertyVariantId) => { - if (propertyVariantId) { - if (!this._workspaceVariantConsumer) { - this._workspaceVariantConsumer = new UmbContextConsumerController( - this.#host, - UMB_WORKSPACE_VARIANT_CONTEXT_TOKEN, - (workspaceVariantContext) => { - new UmbObserverController(this.#host, workspaceVariantContext.variantId, (workspaceVariantId) => { - this.#workspaceVariantId = workspaceVariantId; - this._generateVariantDifferenceString(); - }); - } - ); - } else { - this._generateVariantDifferenceString(); - } - } + this.observe(this.variantId, () => { + this._generateVariantDifferenceString(); }); } + + private _observePropertyVariant?: UmbObserverController; + private _observePropertyValue?: UmbObserverController; + private async _observeProperty() { + const alias = this.#data.getValue().alias; + if (!this.#variantContext || !alias) return; + + const variantIdSubject = await this.#variantContext.propertyVariantId?.(alias) ?? undefined; + this._observePropertyVariant?.destroy(); + if(variantIdSubject) { + this._observePropertyVariant = this.observe( + variantIdSubject, + (variantId) => { + this.#variantId.next(variantId); + } + ); + } + + // TODO: Verify if we need to optimize runtime by parsing the propertyVariantID, cause this method retrieves it again: + const subject = await this.#variantContext.propertyValueByAlias(alias) + + this._observePropertyValue?.destroy(); + if(subject) { + this._observePropertyValue = this.observe( + subject, + (value) => { + // Note: Do not try to compare new / old value, as it can of any type. We trust the UmbObjectState in doing such. + this.#data.update({ value }); + } + ); + } + } + private _generateVariantDifferenceString() { + if(!this.#variantContext) return; + const contextVariantId = this.#variantContext.getVariantId?.() ?? undefined; this._variantDifference.next( - this.#workspaceVariantId ? this.#variantId.getValue()?.toDifferencesString(this.#workspaceVariantId) : '' + contextVariantId ? this.#variantId.getValue()?.toDifferencesString(contextVariantId) : '' ); } @@ -102,16 +124,10 @@ export class UmbWorkspacePropertyContext { this.#data.update({ description }); } public setValue(value: WorkspacePropertyData['value']) { - // Note: Do not try to compare new / old value, as it can of any type. We trust the UmbObjectState in doing such. - this.#data.update({ value }); - } - public changeValue(value: WorkspacePropertyData['value']) { - this.setValue(value); - const alias = this.#data.getValue().alias; - if (alias) { - this._workspaceContext?.setPropertyValue(alias, value, this.#variantId.getValue()); - } + if (!this.#variantContext || !alias) return; + + this.#variantContext?.setPropertyValue(alias, value); } public setConfig(config: WorkspacePropertyData['config'] | undefined) { this.#data.update({ config }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-property/workspace-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-property/workspace-property.element.ts index 7e6b5b181a..24ef4a841a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-property/workspace-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-property/workspace-property.element.ts @@ -2,7 +2,6 @@ import { type UmbPropertyEditorConfig } from '../../property-editor/index.js'; import { UmbWorkspacePropertyContext } from './workspace-property.context.js'; import { UmbTextStyles } from "@umbraco-cms/backoffice/style"; import { css, html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; -import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import { createExtensionElement } from '@umbraco-cms/backoffice/extension-api'; import { ManifestPropertyEditorUi, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; @@ -66,18 +65,6 @@ export class UmbWorkspacePropertyElement extends UmbLitElement { this._observePropertyEditorUI(); } - /** - * Property Value, this is the value stored in the property. - * @public - * @type {unknown} - * @attr - * @default undefined - */ - @property({ attribute: false }) - public set value(value: unknown) { - this._propertyContext.setValue(value); - } - /** * Config. Configuration to pass to the Property Editor UI. This is also the configuration data stored on the Data Type. * @public @@ -90,19 +77,6 @@ export class UmbWorkspacePropertyElement extends UmbLitElement { this._propertyContext.setConfig(value); } - /** - * PropertyVariantId. A Variant ID to identify which variant its value is stored on. - * @public - * @type {UmbVariantId} - * @attr - * @default null - */ - @property({ type: Object, attribute: false }) - public set propertyVariantId(value: UmbVariantId | undefined) { - this._propertyContext.setVariantId(value); - //this._variantDisplayName = value?.toString(); - } - @state() private _variantDifference?: string; @@ -147,7 +121,7 @@ export class UmbWorkspacePropertyElement extends UmbLitElement { const target = e.composedPath()[0] as any; //this.value = target.value; // Sets value in context. - this._propertyContext.changeValue(target.value); + this._propertyContext.setValue(target.value); e.stopPropagation(); }; @@ -234,8 +208,8 @@ export class UmbWorkspacePropertyElement extends UmbLitElement { ? html`` + .propertyEditorUiAlias=${this._propertyEditorUiAlias} + .value=${this._value}>` : ''}`; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-split-view/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-split-view/index.ts new file mode 100644 index 0000000000..1312849754 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-split-view/index.ts @@ -0,0 +1,2 @@ +export * from './workspace-split-view.context.js'; +export * from './workspace-split-view.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-split-view/workspace-split-view.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-split-view/workspace-split-view.context.ts new file mode 100644 index 0000000000..b408e5ec7d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-split-view/workspace-split-view.context.ts @@ -0,0 +1,107 @@ +import { UmbVariantContext } from '../variant-context/index.js'; +import { UMB_VARIANT_WORKSPACE_CONTEXT_TOKEN } from '../index.js'; +import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import { + UmbContextToken, +} from '@umbraco-cms/backoffice/context-api'; +import { UmbBaseController, UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { + UmbNumberState, +} from '@umbraco-cms/backoffice/observable-api'; + + +export class UmbWorkspaceSplitViewContext extends UmbBaseController { + + #workspaceContext?: typeof UMB_VARIANT_WORKSPACE_CONTEXT_TOKEN.TYPE; + public getWorkspaceContext() { + return this.#workspaceContext; + } + + #variantContext?: UmbVariantContext; + + #index = new UmbNumberState(undefined); + index = this.#index.asObservable(); + + //#variantId = new UmbClassState(undefined); + //variantId = this.#variantId.asObservable(); + + constructor(host: UmbControllerHost) { + super(host); + + this.consumeContext(UMB_VARIANT_WORKSPACE_CONTEXT_TOKEN, (context) => { + this.#workspaceContext = context; + this._observeVariant(); + }); + + this.observe(this.#index, () => { + this._observeVariant(); + }); + + + this.provideContext(UMB_WORKSPACE_SPLIT_VIEW_CONTEXT, this); + } + + private _observeVariant() { + if (!this.#workspaceContext) return; + + const index = this.#index.getValue(); + if (index === undefined) return; + + // TODO: Should splitView be put into its own context?... a split view manager context? one which might have a reference to the workspace context, so we still can ask that about how to create the variant context. + this.observe( + this.#workspaceContext.splitView.activeVariantByIndex(index), + async (activeVariantInfo) => { + if (!activeVariantInfo) return; + + // TODO: Ask workspace context to create the specific variant context. + + this.#variantContext?.destroy(); + const variantId = UmbVariantId.Create(activeVariantInfo); + this.#variantContext = this.#workspaceContext?.createVariantContext(this, variantId); + }, + '_observeActiveVariant' + ); + } + + + public switchVariant(variant: UmbVariantId) { + const index = this.#index.value; + if (index === undefined) return; + this.#workspaceContext?.splitView.switchVariant(index, variant); + } + + public closeSplitView() { + const index = this.#index.value; + if (index === undefined) return; + this.#workspaceContext?.splitView.closeSplitView(index); + } + + public openSplitView(variant: UmbVariantId) { + this.#workspaceContext?.splitView.openSplitView(variant); + } + + public getSplitViewIndex() { + return this.#index.getValue(); + } + public setSplitViewIndex(index: number) { + this.#index.next(index); + } + + + + + /** + * + * concept this class could have methods to set and get the culture and segment of the active variant? just by using the index. + */ + + /* + public destroy(): void { + + } + */ +} + +export const UMB_WORKSPACE_SPLIT_VIEW_CONTEXT = new UmbContextToken( + 'umbWorkspaceSplitViewContext' +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-variant/workspace-variant.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-split-view/workspace-split-view.element.ts similarity index 67% rename from src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-variant/workspace-variant.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-split-view/workspace-split-view.element.ts index 86f3279931..dfd8b7398e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-variant/workspace-variant.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-split-view/workspace-split-view.element.ts @@ -1,6 +1,6 @@ -import { UmbWorkspaceVariantContext } from './workspace-variant.context.js'; +import { UmbWorkspaceSplitViewContext } from './workspace-split-view.context.js'; import { UmbTextStyles } from "@umbraco-cms/backoffice/style"; -import { css, html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; /** @@ -9,8 +9,8 @@ import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; * As well breadcrumbs etc. * */ -@customElement('umb-workspace-variant') -export class UmbWorkspaceVariantContentElement extends UmbLitElement { +@customElement('umb-workspace-split-view') +export class UmbWorkspaceSplitViewElement extends UmbLitElement { // TODO: stop prop drilling this alias. Instead use the workspace context. @property() alias!: string; @@ -20,19 +20,14 @@ export class UmbWorkspaceVariantContentElement extends UmbLitElement { @property({ type: Number }) public set splitViewIndex(index: number) { - this._splitViewIndex = index; - this.variantContext.setSplitViewIndex(index); + this.splitViewContext.setSplitViewIndex(index); } - @state() - private _splitViewIndex = 0; - - variantContext = new UmbWorkspaceVariantContext(this); + splitViewContext = new UmbWorkspaceSplitViewContext(this); render() { return html` @@ -67,10 +62,10 @@ export class UmbWorkspaceVariantContentElement extends UmbLitElement { ]; } -export default UmbWorkspaceVariantContentElement; +export default UmbWorkspaceSplitViewElement; declare global { interface HTMLElementTagNameMap { - 'umb-workspace-variant': UmbWorkspaceVariantContentElement; + 'umb-workspace-split-view': UmbWorkspaceSplitViewElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-variant/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-variant/index.ts deleted file mode 100644 index 3bc643d7aa..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-variant/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './variantable-property/variantable-property.element.js'; -export * from './workspace-variant.context.js'; -export * from './workspace-variant.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-variant/variantable-property/variantable-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-variant/variantable-property/variantable-property.element.ts deleted file mode 100644 index 2631ff0de4..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-variant/variantable-property/variantable-property.element.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { UMB_WORKSPACE_VARIANT_CONTEXT_TOKEN } from '../workspace-variant.context.js'; -import { UmbTextStyles } from "@umbraco-cms/backoffice/style"; -import { css, html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; -import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; -import type { PropertyTypeModelBaseModel } from '@umbraco-cms/backoffice/backend-api'; -import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; - -@customElement('umb-variantable-property') -export class UmbVariantablePropertyElement extends UmbLitElement { - private _property?: PropertyTypeModelBaseModel | undefined; - @property({ type: Object, attribute: false }) - public get property(): PropertyTypeModelBaseModel | undefined { - return this._property; - } - public set property(property: PropertyTypeModelBaseModel | undefined) { - this._property = property; - this._updatePropertyVariantId(); - } - - private _variantContext?: typeof UMB_WORKSPACE_VARIANT_CONTEXT_TOKEN.TYPE; - - @state() - private _workspaceVariantId?: UmbVariantId; - - @state() - private _propertyVariantId?: UmbVariantId; - - constructor() { - super(); - this.consumeContext(UMB_WORKSPACE_VARIANT_CONTEXT_TOKEN, (workspaceContext) => { - this._variantContext = workspaceContext; - this._observeVariantContext(); - }); - } - - private _observeVariantContext() { - if (!this._variantContext || !this.property) return; - this.observe(this._variantContext.variantId, (variantId) => { - this._workspaceVariantId = variantId; - this._updatePropertyVariantId(); - }); - } - - private _updatePropertyVariantId() { - if (this._workspaceVariantId && this.property) { - const newVariantId = UmbVariantId.Create({ - culture: this.property.variesByCulture ? this._workspaceVariantId.culture : null, - segment: this.property.variesBySegment ? this._workspaceVariantId.segment : null, - }); - if (!this._propertyVariantId || !newVariantId.equal(this._propertyVariantId)) { - this._propertyVariantId = newVariantId; - } - } - } - - render() { - return html``; - } - - static styles = [ - UmbTextStyles, - css` - :host { - display: block; - } - `, - ]; -} - -declare global { - interface HTMLElementTagNameMap { - 'umb-variantable-property': UmbVariantablePropertyElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-variant/variantable-property/variantable-property.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-variant/variantable-property/variantable-property.stories.ts deleted file mode 100644 index 9bb61ac331..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-variant/variantable-property/variantable-property.stories.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Meta, StoryObj } from '@storybook/web-components'; -import './variantable-property.element.js'; -import type { UmbVariantablePropertyElement } from './variantable-property.element.js'; - -const meta: Meta = { - title: 'Components/Variantable Property', - component: 'umb-variantable-property', -}; - -export default meta; -type Story = StoryObj; - -export const Overview: Story = { - args: { - property: { - name: 'Header', - alias: 'headerAlias', - appearance: { - labelOnTop: false, - }, - description: 'This is a description', - variesByCulture: true, - variesBySegment: true, - validation: { - mandatory: true, - mandatoryMessage: 'This is a mandatory message', - }, - }, - }, -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-variant/workspace-variant.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-variant/workspace-variant.context.ts deleted file mode 100644 index 723ef79d03..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-variant/workspace-variant.context.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { UmbWorkspaceVariableEntityContextInterface } from '../workspace-context/workspace-variable-entity-context.interface.js'; -import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; -import { - UmbContextConsumerController, - UmbContextProviderController, - UmbContextToken, -} from '@umbraco-cms/backoffice/context-api'; -import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; -import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; -import { - UmbClassState, - UmbNumberState, - UmbObjectState, - UmbObserverController, -} from '@umbraco-cms/backoffice/observable-api'; -import { DocumentVariantResponseModel } from '@umbraco-cms/backoffice/backend-api'; - -//type EntityType = DocumentModel; - -export class UmbWorkspaceVariantContext { - #host: UmbControllerHostElement; - - #workspaceContext?: UmbWorkspaceVariableEntityContextInterface; - public getWorkspaceContext() { - return this.#workspaceContext; - } - - #index = new UmbNumberState(undefined); - index = this.#index.asObservable(); - - #currentVariant = new UmbObjectState(undefined); - currentVariant = this.#currentVariant.asObservable(); - - name = this.#currentVariant.asObservablePart((x) => x?.name); - culture = this.#currentVariant.asObservablePart((x) => x?.culture); - segment = this.#currentVariant.asObservablePart((x) => x?.segment); - - #variantId = new UmbClassState(undefined); - variantId = this.#variantId.asObservable(); - - constructor(host: UmbControllerHostElement) { - this.#host = host; - - new UmbContextProviderController(host, UMB_WORKSPACE_VARIANT_CONTEXT_TOKEN.toString(), this); - - // How do we ensure this connects to a document workspace context? and not just any other context? (We could start providing workspace contexts twice, under the general name and under a specific name) - // TODO: Figure out if this is the best way to consume the context or if it can be strongly typed with an UmbContextToken - new UmbContextConsumerController(host, UMB_WORKSPACE_CONTEXT, (context) => { - this.#workspaceContext = context as UmbWorkspaceVariableEntityContextInterface; - this._observeVariant(); - }); - - new UmbObserverController(host, this.#index, () => { - this._observeVariant(); - }); - } - - public switchVariant(variant: DocumentVariantResponseModel) { - const index = this.#index.value; - if (index === undefined) return; - this.#workspaceContext?.splitView.switchVariant(index, new UmbVariantId(variant)); - } - - public closeSplitView() { - const index = this.#index.value; - if (index === undefined) return; - this.#workspaceContext?.splitView.closeSplitView(index); - } - - public openSplitView(variant: DocumentVariantResponseModel) { - this.#workspaceContext?.splitView.openSplitView(new UmbVariantId(variant)); - } - - private _setVariantId(variantId: UmbVariantId) { - this.#variantId.next(variantId); - return variantId; - } - - private _observeVariant() { - if (!this.#workspaceContext) return; - - const index = this.#index.getValue(); - if (index === undefined) return; - - new UmbObserverController( - this.#host, - this.#workspaceContext.splitView.activeVariantByIndex(index), - async (activeVariantInfo) => { - if (!activeVariantInfo) return; - const variantId = this._setVariantId(UmbVariantId.Create(activeVariantInfo)); - const currentVariant = await this.#workspaceContext?.getVariant(variantId); - this.#currentVariant.next(currentVariant); - }, - '_observeActiveVariant' - ); - } - - public changeVariant(culture: string | null, segment: string | null) { - const index = this.#index.getValue(); - if (index === undefined) return; - this.#workspaceContext?.splitView.setActiveVariant(index, culture, segment); - } - - public getSplitViewIndex() { - return this.#index.getValue(); - } - public setSplitViewIndex(index: number) { - this.#index.next(index); - } - - public setName(newName: string) { - const variantId = this.#variantId.getValue(); - if (!this.#workspaceContext || !variantId) return; - this.#workspaceContext.setName(newName, variantId); - } - - /** - * - * concept this class could have methods to set and get the culture and segment of the active variant? just by using the index. - */ - - /* - public destroy(): void { - - } - */ -} - -export const UMB_WORKSPACE_VARIANT_CONTEXT_TOKEN = new UmbContextToken( - 'umbWorkspaceVariantContext' -); diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/workspace/dictionary-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/workspace/dictionary-workspace.context.ts index 65a134993d..2990d526eb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/workspace/dictionary-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/workspace/dictionary-workspace.context.ts @@ -1,5 +1,5 @@ import { UmbDictionaryRepository } from '../repository/dictionary.repository.js'; -import { UmbEntityWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; +import { UmbSaveableWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import { DictionaryItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; @@ -7,7 +7,7 @@ import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; export class UmbDictionaryWorkspaceContext extends UmbWorkspaceContext - implements UmbEntityWorkspaceContextInterface + implements UmbSaveableWorkspaceContextInterface { #data = new UmbObjectState(undefined); data = this.#data.asObservable(); @@ -86,7 +86,7 @@ export class UmbDictionaryWorkspaceContext } -export const UMB_DICTIONARY_WORKSPACE_CONTEXT = new UmbContextToken( +export const UMB_DICTIONARY_WORKSPACE_CONTEXT = new UmbContextToken( 'UmbWorkspaceContext', (context): context is UmbDictionaryWorkspaceContext => context.getEntityType?.() === 'dictionary-item' ); 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 28582fa254..4cc9b70c01 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,6 @@ import { UmbDocumentTypeRepository } from '../repository/document-type.repository.js'; import { UmbContentTypePropertyStructureManager } from '@umbraco-cms/backoffice/content-type'; -import { UmbWorkspaceContext, UmbEntityWorkspaceContextInterface } from '@umbraco-cms/backoffice/workspace'; +import { UmbWorkspaceContext, UmbSaveableWorkspaceContextInterface } from '@umbraco-cms/backoffice/workspace'; import type { ContentTypeCompositionModel, ContentTypeSortModel, @@ -12,7 +12,7 @@ import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; type EntityType = DocumentTypeResponseModel; export class UmbDocumentTypeWorkspaceContext extends UmbWorkspaceContext - implements UmbEntityWorkspaceContextInterface + implements UmbSaveableWorkspaceContextInterface { // Draft is located in structure manager @@ -157,7 +157,7 @@ export class UmbDocumentTypeWorkspaceContext } -export const UMB_DOCUMENT_TYPE_WORKSPACE_CONTEXT = new UmbContextToken( +export const UMB_DOCUMENT_TYPE_WORKSPACE_CONTEXT = new UmbContextToken( 'UmbWorkspaceContext', (context): context is UmbDocumentTypeWorkspaceContext => context.getEntityType?.() === 'document-type' ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/variant-context/document-variant-context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/variant-context/document-variant-context.token.ts new file mode 100644 index 0000000000..cade2573c3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/variant-context/document-variant-context.token.ts @@ -0,0 +1,9 @@ +import type { UmbDocumentVariantContext } from "./document-variant-context.js"; +import { UmbContextToken } from "@umbraco-cms/backoffice/context-api"; +import { UmbVariantContext } from "@umbraco-cms/backoffice/workspace"; + +export const IsDocumentVariantContext = (context: UmbVariantContext): context is UmbDocumentVariantContext => context.getType() === 'document'; + +export const UMB_DOCUMENT_VARIANT_CONTEXT = new UmbContextToken( + "UmbVariantContext", + IsDocumentVariantContext); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/variant-context/document-variant-context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/variant-context/document-variant-context.ts new file mode 100644 index 0000000000..2e894b719f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/variant-context/document-variant-context.ts @@ -0,0 +1,112 @@ +import { UmbDocumentWorkspaceContext } from "../workspace/index.js"; +import { DocumentVariantResponseModel, PropertyTypeModelBaseModel } from "@umbraco-cms/backoffice/backend-api"; +import { UmbBaseController, UmbControllerHost } from "@umbraco-cms/backoffice/controller-api"; +import { map } from "@umbraco-cms/backoffice/external/rxjs"; +import { UmbObjectState } from "@umbraco-cms/backoffice/observable-api"; +import { UmbVariantId } from "@umbraco-cms/backoffice/variant"; +import { UMB_VARIANT_CONTEXT, UmbVariantContext } from "@umbraco-cms/backoffice/workspace"; + +// TODO: This code can be split into a UmbContentTypeVariantContext, leaving just the publishing state and methods to this class. +export class UmbDocumentVariantContext extends UmbBaseController implements UmbVariantContext { + + #workspace: UmbDocumentWorkspaceContext; + #variantId: UmbVariantId; + public getVariantId() { + return this.#variantId; + } + + #currentVariant = new UmbObjectState(undefined); + currentVariant = this.#currentVariant.asObservable(); + + name = this.#currentVariant.asObservablePart((x) => x?.name); + culture = this.#currentVariant.asObservablePart((x) => x?.culture); + segment = this.#currentVariant.asObservablePart((x) => x?.segment); + + // TODO: Refactor: Make a properties observable. (with such I think i mean a property value object array.. array with object with properties, alias, value, culture and segment) + // TO make such happen I think we need to maintain all properties and their value of this object. + // This will actually make it simpler if multiple are watching the same property. + // But it will also mean that we wil watch all properties and their structure, for variantID, all the time for all of the properties. + + + getType(): string { + return this.#workspace.getEntityType(); + } + getUnique(): string | undefined { + return this.#workspace.getEntityId(); + } + getName(): string | undefined { + return this.#workspace.getName(this.#variantId); + } + setName(name: string) { + this.#workspace.setName(name, this.#variantId); + } + getVariantInfo() { + return this.#workspace.getVariant(this.#variantId); + } + + + + constructor(host: UmbControllerHost, workspace: UmbDocumentWorkspaceContext, variantId?: UmbVariantId) { + // The controller alias, is a very generic name cause we want only one of these for this controller host. + super(host, 'variantContext'); + this.#workspace = workspace; + this.#variantId = variantId ?? UmbVariantId.CreateInvariant(); + + this.observe( + this.#workspace.variantById(this.#variantId), + async (variantInfo) => { + if (!variantInfo) return; + this.#currentVariant.next(variantInfo); + }, + '_observeActiveVariant' + ); + + // TODO: Refactor: use the document dataset context token. + this.provideContext(UMB_VARIANT_CONTEXT, this); + } + + + #createPropertyVariantId(property:PropertyTypeModelBaseModel) { + return UmbVariantId.Create({ + culture: property.variesByCulture ? this.#variantId.culture : null, + segment: property.variesBySegment ? this.#variantId.segment : null, + }); + } + + /** + * TODO: Write proper JSDocs here. + * Ideally do not use these methods, its better to communicate directly with the workspace, but if you do not know the property variant id, then this will figure it out for you. So good for externals to set or get values of a property. + */ + async propertyVariantId(propertyAlias: string) { + return (await this.#workspace.structure.propertyStructureByAlias(propertyAlias)).pipe(map((property) => property ? this.#createPropertyVariantId(property) : undefined)); + } + + /** + * TODO: Write proper JSDocs here. + * Ideally do not use these methods, its better to communicate directly with the workspace, but if you do not know the property variant id, then this will figure it out for you. So good for externals to set or get values of a property. + */ + async propertyValueByAlias(propertyAlias: string) { + await this.#workspace.isLoaded(); + return (await this.#workspace.structure.propertyStructureByAlias(propertyAlias)).pipe(map((property) => property?.alias ? this.#workspace.getPropertyValue(property.alias, this.#createPropertyVariantId(property)) : undefined)); + } + + // TODO: Refactor: Not used currently, but should investigate if we can implement this, to spare some energy. + async propertyValueByAliasAndCulture(propertyAlias: string, propertyVariantId: UmbVariantId) { + return this.#workspace.propertyValueByAlias(propertyAlias, propertyVariantId); + } + + /** + * TODO: Write proper JSDocs here. + * Ideally do not use these methods, its better to communicate directly with the workspace, but if you do not know the property variant id, then this will figure it out for you. So good for externals to set or get values of a property. + */ + async setPropertyValue(propertyAlias: string, value: unknown) { + // This is not reacting to if the property variant settings changes while running. + const property = await this.#workspace.structure.getPropertyStructureByAlias(propertyAlias); + if(property) { + const variantId = this.#createPropertyVariantId(property); + + // This is not reacting to if the property variant settings changes while running. + this.#workspace.setPropertyValue(propertyAlias, value, variantId); + } + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-editor.element.ts index 27c162a97f..5145be6ade 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-editor.element.ts @@ -10,6 +10,8 @@ import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; @customElement('umb-document-workspace-editor') export class UmbDocumentWorkspaceEditorElement extends UmbLitElement { //private _defaultVariant?: VariantViewModelBaseModel; + + // TODO: Refactor: when having a split view/variants context token, we can rename the split view/variants component to a generic and make this component generic as well. private splitViewElement = new UmbDocumentWorkspaceSplitViewElement(); @state() diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-split-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-split-view.element.ts index 206b1f6f7b..966f21ca45 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-split-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-split-view.element.ts @@ -5,17 +5,16 @@ import { ActiveVariant } from '@umbraco-cms/backoffice/workspace'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; @customElement('umb-document-workspace-split-view') export class UmbDocumentWorkspaceSplitViewElement extends UmbLitElement { + // TODO: Refactor: use the split view context token: private _workspaceContext?: typeof UMB_DOCUMENT_WORKSPACE_CONTEXT.TYPE; - @state() - _unique?: string; - @state() _variants?: Array; constructor() { super(); + // TODO: Refactor: use a split view workspace context token: this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (context) => { this._workspaceContext = context; this._observeActiveVariantInfo(); @@ -41,10 +40,10 @@ export class UmbDocumentWorkspaceSplitViewElement extends UmbLitElement { (view) => view.index + '_' + (view.culture ?? '') + '_' + (view.segment ?? '') + '_' + this._variants!.length, (view) => html` - + .displayNavigation=${view.index === this._variants!.length - 1}> ` )} 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 f893bbc8e9..c4fbfb841a 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 @@ -1,12 +1,14 @@ import { UmbDocumentRepository } from '../repository/document.repository.js'; import { UmbDocumentTypeRepository } from '../../document-types/repository/document-type.repository.js'; +import { UmbDocumentVariantContext } from '../variant-context/document-variant-context.js'; import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import { UmbContentTypePropertyStructureManager } from '@umbraco-cms/backoffice/content-type'; import { - UmbEntityWorkspaceContextInterface, + UmbSaveableWorkspaceContextInterface, UmbWorkspaceContext, UmbWorkspaceSplitViewManager, - UmbWorkspaceVariableEntityContextInterface, + UmbVariantableWorkspaceContextInterface, + type UmbVariantContext, } from '@umbraco-cms/backoffice/workspace'; import type { CreateDocumentRequestModel, DocumentResponseModel } from '@umbraco-cms/backoffice/backend-api'; import { @@ -15,34 +17,36 @@ import { UmbObjectState, UmbObserverController, } from '@umbraco-cms/backoffice/observable-api'; -import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { UmbControllerHost, UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; -// TODO: should this context be called DocumentDraft instead of workspace? or should the draft be part of this? -// TODO: Should we have a DocumentStructureContext and maybe even a DocumentDraftContext? - type EntityType = DocumentResponseModel; export class UmbDocumentWorkspaceContext extends UmbWorkspaceContext - implements UmbWorkspaceVariableEntityContextInterface + implements UmbVariantableWorkspaceContextInterface { /** * The document is the current stored version of the document. * For now lets not share this publicly as it can become confusing. - * TODO: Use this to compare, for variants with changes. + * TODO: This concept is to be able to compare if there is changes since the saved one. */ - #document = new UmbObjectState(undefined); + //#persistedData = new UmbObjectState(undefined); /** * The document is the current state/draft version of the document. */ - #draft = new UmbObjectState(undefined); - readonly unique = this.#draft.asObservablePart((data) => data?.id); - readonly documentTypeKey = this.#draft.asObservablePart((data) => data?.contentTypeId); + #currentData = new UmbObjectState(undefined); + #getDataPromise?: Promise; + public isLoaded() { + return this.#getDataPromise; + } - readonly variants = this.#draft.asObservablePart((data) => data?.variants || []); - readonly urls = this.#draft.asObservablePart((data) => data?.urls || []); - readonly templateId = this.#draft.asObservablePart((data) => data?.templateId || null); + readonly unique = this.#currentData.asObservablePart((data) => data?.id); + readonly documentTypeKey = this.#currentData.asObservablePart((data) => data?.contentTypeId); + + readonly variants = this.#currentData.asObservablePart((data) => data?.variants || []); + readonly urls = this.#currentData.asObservablePart((data) => data?.urls || []); + readonly templateId = this.#currentData.asObservablePart((data) => data?.templateId || null); readonly structure; readonly splitView; @@ -61,27 +65,28 @@ export class UmbDocumentWorkspaceContext } async load(entityId: string) { - const { data } = await this.repository.requestById(entityId); + this.#getDataPromise = this.repository.requestById(entityId); + const { data } = await this.#getDataPromise; if (!data) return undefined; this.setIsNew(false); - this.#document.next(data); - this.#draft.next(data); + //this.#persisted.next(data); + this.#currentData.next(data); return data || undefined; } async create(documentTypeKey: string, parentId: string | null) { - const { data } = await this.repository.createScaffold(documentTypeKey, { parentId }); + this.#getDataPromise = this.repository.createScaffold(documentTypeKey, { parentId }); + const { data } = await this.#getDataPromise; if (!data) return undefined; this.setIsNew(true); - this.#document.next(data); - this.#draft.next(data); + this.#currentData.next(data); return data || undefined; } getData() { - return this.#draft.getValue() || {}; + return this.#currentData.getValue() || {}; } /* @@ -102,12 +107,16 @@ export class UmbDocumentWorkspaceContext return this.getData().contentTypeId; } + variantById(variantId: UmbVariantId) { + return this.#currentData.asObservablePart((data) => data?.variants?.find((x) => variantId.compare(x))); + } + getVariant(variantId: UmbVariantId) { - return this.#draft.getValue()?.variants?.find((x) => variantId.compare(x)); + return this.#currentData.getValue()?.variants?.find((x) => variantId.compare(x)); } getName(variantId?: UmbVariantId) { - const variants = this.#draft.getValue()?.variants; + const variants = this.#currentData.getValue()?.variants; if (!variants) return; if (variantId) { return variants.find((x) => variantId.compare(x))?.name; @@ -117,67 +126,67 @@ export class UmbDocumentWorkspaceContext } setName(name: string, variantId?: UmbVariantId) { - const oldVariants = this.#draft.getValue()?.variants || []; + const oldVariants = this.#currentData.getValue()?.variants || []; const variants = partialUpdateFrozenArray( oldVariants, { name }, variantId ? (x) => variantId.compare(x) : () => true ); - this.#draft.update({ variants }); + this.#currentData.update({ variants }); } - propertyValuesOf(variantId?: UmbVariantId) { - return this.#draft.asObservablePart((data) => - variantId ? data?.values?.filter((x) => variantId.compare(x)) : data?.values - ); + async propertyStructureById(propertyId: string) { + return this.structure.propertyStructureById(propertyId); } - propertyDataByAlias(propertyAlias: string, variantId?: UmbVariantId) { - return this.#draft.asObservablePart((data) => - data?.values?.find((x) => x?.alias === propertyAlias && (variantId ? variantId.compare(x) : true)) - ); - } - propertyValueByAlias(propertyAlias: string, variantId?: UmbVariantId) { - return this.#draft.asObservablePart( + async propertyValueByAlias(propertyAlias: string, variantId?: UmbVariantId) { + return this.#currentData.asObservablePart( (data) => - data?.values?.find((x) => x?.alias === propertyAlias && (variantId ? variantId.compare(x) : true))?.value + data?.values?.find((x) => x?.alias === propertyAlias && (variantId ? variantId.compare(x) : true))?.value as PropertyValueType ); } - getPropertyValue(alias: string, variantId?: UmbVariantId): void { - const currentData = this.#draft.value; + /** + * Get the current value of the property with the given alias and variantId. + * @param alias + * @param variantId + * @returns The value or undefined if not set or found. + */ + getPropertyValue(alias: string, variantId?: UmbVariantId) { + const currentData = this.#currentData.value; if (currentData) { const newDataSet = currentData.values?.find( (x) => x.alias === alias && (variantId ? variantId.compare(x) : true) ); - return newDataSet?.value; + return newDataSet?.value as ReturnType; } + return undefined; } - setPropertyValue(alias: string, value: unknown, variantId?: UmbVariantId) { + async setPropertyValue(alias: string, value: PropertyValueType, variantId?: UmbVariantId) { const entry = { ...variantId?.toObject(), alias, value }; - const currentData = this.#draft.value; + const currentData = this.#currentData.value; if (currentData) { const values = appendToFrozenArray( currentData.values || [], entry, (x) => x.alias === alias && (variantId ? variantId.compare(x) : true) ); - this.#draft.update({ values }); + this.#currentData.update({ values }); } } async save() { - if (!this.#draft.value) return; - if (!this.#draft.value.id) return; + if (!this.#currentData.value) return; + if (!this.#currentData.value.id) return; if (this.getIsNew()) { // TODO: typescript hack until we get the create type - const value = this.#draft.value as CreateDocumentRequestModel & { id: string }; + const value = this.#currentData.value as CreateDocumentRequestModel & { id: string }; if ((await this.repository.create(value)).data !== undefined) { this.setIsNew(false); } } else { - await this.repository.save(this.#draft.value.id, this.#draft.value); + await this.repository.save(this.#currentData.value.id, this.#currentData.value); } this.saveComplete(this.getData()); @@ -199,8 +208,12 @@ export class UmbDocumentWorkspaceContext } */ + public createVariantContext(host: UmbControllerHost, variantId: UmbVariantId) { + return new UmbDocumentVariantContext(host, this, variantId); + } + public destroy(): void { - this.#draft.complete(); + this.#currentData.complete(); this.structure.destroy(); super.destroy(); } @@ -209,7 +222,8 @@ export class UmbDocumentWorkspaceContext export default UmbDocumentWorkspaceContext; -export const UMB_DOCUMENT_WORKSPACE_CONTEXT = new UmbContextToken( +export const UMB_DOCUMENT_WORKSPACE_CONTEXT = new UmbContextToken( 'UmbWorkspaceContext', + // TODO: Refactor: make a better generic way to identify workspaces, maybe workspaceType or workspaceAlias?. (context): context is UmbDocumentWorkspaceContext => context.getEntityType?.() === 'document' ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/edit/document-workspace-view-edit-properties.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/edit/document-workspace-view-edit-properties.element.ts index 58c0112570..43eccabe75 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/edit/document-workspace-view-edit-properties.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/edit/document-workspace-view-edit-properties.element.ts @@ -42,7 +42,7 @@ export class UmbDocumentWorkspaceViewEditPropertiesElement extends UmbLitElement return repeat( this._propertyStructure, (property) => property.alias, - (property) => html` ` + (property) => html` ` ); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/edit/document-workspace-view-edit.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/edit/document-workspace-view-edit.element.ts index 58dcca19ad..80189d3cfb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/edit/document-workspace-view-edit.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/edit/document-workspace-view-edit.element.ts @@ -115,7 +115,7 @@ export class UmbDocumentWorkspaceViewEditElement if (!this._routes || !this._tabs) return; return html` - ${this._routerPath && (this._tabs.length > 0 || this._hasRootGroups) + ${this._routerPath && (this._tabs.length > 1 || (this._tabs.length === 1 && this._hasRootGroups)) ? html` ${this._hasRootGroups && this._tabs.length > 0 ? html` 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 af3dc52196..89d0e4568a 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 @@ -1,6 +1,6 @@ import { UmbMediaTypeRepository } from '../repository/media-type.repository.js'; import type { MediaTypeDetails } from '../types.js'; -import { UmbEntityWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; +import { UmbSaveableWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; @@ -8,7 +8,7 @@ import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; type EntityType = MediaTypeDetails; export class UmbMediaTypeWorkspaceContext extends UmbWorkspaceContext - implements UmbEntityWorkspaceContextInterface + implements UmbSaveableWorkspaceContextInterface { #data = new UmbObjectState(undefined); data = this.#data.asObservable(); @@ -64,7 +64,7 @@ export class UmbMediaTypeWorkspaceContext } -export const UMB_MEDIA_TYPE_WORKSPACE_CONTEXT = new UmbContextToken( +export const UMB_MEDIA_TYPE_WORKSPACE_CONTEXT = new UmbContextToken( 'UmbWorkspaceContext', (context): context is UmbMediaTypeWorkspaceContext => context.getEntityType?.() === 'media-type' ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts index be6dc2a30c..8d5148c25b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts @@ -1,6 +1,6 @@ import { UmbMediaRepository } from '../repository/media.repository.js'; import type { MediaDetails } from '../index.js'; -import { UmbEntityWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; +import { UmbSaveableWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; import { appendToFrozenArray, UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; @@ -8,7 +8,7 @@ import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; type EntityType = MediaDetails; export class UmbMediaWorkspaceContext extends UmbWorkspaceContext - implements UmbEntityWorkspaceContextInterface + implements UmbSaveableWorkspaceContextInterface { #data = new UmbObjectState(undefined); data = this.#data.asObservable(); @@ -83,7 +83,7 @@ export class UmbMediaWorkspaceContext } } -export const UMB_MEDIA_WORKSPACE_CONTEXT = new UmbContextToken( +export const UMB_MEDIA_WORKSPACE_CONTEXT = new UmbContextToken( 'UmbWorkspaceContext', (context): context is UmbMediaWorkspaceContext => context.getEntityType?.() === 'media' ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-groups/workspace/member-group-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-groups/workspace/member-group-workspace.context.ts index 3de2301159..73ae447b63 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-groups/workspace/member-group-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-groups/workspace/member-group-workspace.context.ts @@ -1,6 +1,6 @@ import { UmbMemberGroupRepository } from '../repository/member-group.repository.js'; import type { MemberGroupDetails } from '../types.js'; -import { UmbEntityWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; +import { UmbSaveableWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; @@ -8,7 +8,7 @@ import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; type EntityType = MemberGroupDetails; export class UmbMemberGroupWorkspaceContext extends UmbWorkspaceContext - implements UmbEntityWorkspaceContextInterface + implements UmbSaveableWorkspaceContextInterface { #data = new UmbObjectState(undefined); data = this.#data.asObservable(); @@ -67,7 +67,7 @@ export class UmbMemberGroupWorkspaceContext -export const UMB_MEMBER_GROUP_WORKSPACE_CONTEXT = new UmbContextToken( +export const UMB_MEMBER_GROUP_WORKSPACE_CONTEXT = new UmbContextToken( 'UmbWorkspaceContext', (context): context is UmbMemberGroupWorkspaceContext => context.getEntityType?.() === 'member-group' ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-types/workspace/member-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/workspace/member-type-workspace.context.ts index 89d557f61c..49994e0df6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-types/workspace/member-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/workspace/member-type-workspace.context.ts @@ -1,5 +1,5 @@ import { UmbMemberTypeRepository } from '../repository/member-type.repository.js'; -import { UmbEntityWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; +import { UmbSaveableWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; @@ -9,7 +9,7 @@ type EntityType = any; export class UmbMemberTypeWorkspaceContext extends UmbWorkspaceContext - implements UmbEntityWorkspaceContextInterface + implements UmbSaveableWorkspaceContextInterface { #data = new UmbObjectState(undefined); name = this.#data.asObservablePart((data) => data?.name); @@ -75,7 +75,7 @@ export class UmbMemberTypeWorkspaceContext } } -export const UMB_MEMBER_TYPE_WORKSPACE_CONTEXT = new UmbContextToken( +export const UMB_MEMBER_TYPE_WORKSPACE_CONTEXT = new UmbContextToken( 'UmbWorkspaceContext', (context): context is UmbMemberTypeWorkspaceContext => context.getEntityType?.() === 'member-type' ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/members/workspace/member-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/members/members/workspace/member-workspace.context.ts index 20aecf8e28..17804d541f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/members/workspace/member-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/members/workspace/member-workspace.context.ts @@ -1,12 +1,12 @@ import { UmbMemberRepository } from '../repository/member.repository.js'; import type { MemberDetails } from '../types.js'; -import { UmbEntityWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; +import { UmbSaveableWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; export class UmbMemberWorkspaceContext extends UmbWorkspaceContext - implements UmbEntityWorkspaceContextInterface + implements UmbSaveableWorkspaceContextInterface { constructor(host: UmbControllerHostElement) { super(host, 'Umb.Workspace.Member', new UmbMemberRepository(host)); @@ -37,7 +37,7 @@ export class UmbMemberWorkspaceContext } } -export const UMB_MEMBER_WORKSPACE_CONTEXT = new UmbContextToken( +export const UMB_MEMBER_WORKSPACE_CONTEXT = new UmbContextToken( 'UmbWorkspaceContext', (context): context is UmbMemberWorkspaceContext => context.getEntityType?.() === 'member' ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/search/manifests.ts index 0a9f1dfa4c..a5b5fd73fb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/search/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/search/manifests.ts @@ -40,13 +40,13 @@ export const manifests: Array = [ { type: 'headerApp', kind: 'button', - alias: 'Umb.HeaderApp.HackDemo', - name: 'Header App Search', + alias: 'My.HeaderApp.Wand', + name: 'My Header App', weight: 10, meta: { - label: 'Hack Demo', - icon: 'document', - href: '/section/content/workspace/document/edit/c05da24d-7740-447b-9cdc-bd8ce2172e38/en-us/view/content/tab/Local%20blog%20tab', + label: 'My Header App', + icon: 'wand', + href: '/section/content/workspace/document/edit/c05da24d-7740-447b-9cdc-bd8ce2172e38', }, }, { diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/index.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/index.ts index 10ee495e1d..59558cec93 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/index.ts @@ -3,3 +3,4 @@ import './components/index.js'; export type { UmbDataTypeModel } from './models.js'; export * from './entities.js'; export * from './repository/index.js'; +export * from './variant-context/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/variant-context/data-type-variant-context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/variant-context/data-type-variant-context.token.ts new file mode 100644 index 0000000000..2bd6edee08 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/variant-context/data-type-variant-context.token.ts @@ -0,0 +1,8 @@ +import type { UmbDataTypeVariantContext } from "./data-type-variant-context.js"; +import { UmbVariantContext } from "@umbraco-cms/backoffice/workspace"; +import { UmbContextToken } from "@umbraco-cms/backoffice/context-api"; + +export const isDataTypeVariantContext = (context: UmbVariantContext): context is UmbDataTypeVariantContext => ('properties' in context && context.getType() === 'data-type'); + +export const UMB_DATA_TYPE_VARIANT_CONTEXT = new UmbContextToken( + "UmbVariantContext", isDataTypeVariantContext); diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/variant-context/data-type-variant-context.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/variant-context/data-type-variant-context.ts new file mode 100644 index 0000000000..506f8ef439 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/variant-context/data-type-variant-context.ts @@ -0,0 +1,16 @@ +import { UmbDataTypeWorkspaceContext } from "../workspace/data-type-workspace.context.js"; +import { UmbControllerHost } from "@umbraco-cms/backoffice/controller-api"; +import { UmbInvariantWorkspaceVariantContext } from "@umbraco-cms/backoffice/workspace"; + +export class UmbDataTypeVariantContext extends UmbInvariantWorkspaceVariantContext { + + + properties = this._workspace.properties + + // default data: + + constructor(host: UmbControllerHost, workspace: UmbDataTypeWorkspaceContext) { + super(host, workspace); + } + +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/variant-context/index.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/variant-context/index.ts new file mode 100644 index 0000000000..95cb72dcdc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/variant-context/index.ts @@ -0,0 +1,2 @@ +export * from './data-type-variant-context.token.js'; +export * from './data-type-variant-context.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/workspace/data-type-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/workspace/data-type-workspace-editor.element.ts index ca31a7da7e..0f5753cfe2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/workspace/data-type-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/workspace/data-type-workspace-editor.element.ts @@ -23,6 +23,7 @@ export class UmbDataTypeWorkspaceEditorElement extends UmbLitElement { this.consumeContext(UMB_DATA_TYPE_WORKSPACE_CONTEXT, (workspaceContext) => { this.#workspaceContext = workspaceContext; + this.#workspaceContext?.createVariantContext(this); this.#observeIsNew(); this.#observeName(); }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/workspace/data-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/workspace/data-type-workspace.context.ts index 4019f99469..4df074b8e9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/workspace/data-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/workspace/data-type-workspace.context.ts @@ -1,27 +1,112 @@ import { UmbDataTypeRepository } from '../repository/data-type.repository.js'; -import { UmbEntityWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; +import { UmbDataTypeVariantContext } from '../variant-context/data-type-variant-context.js'; +import { UmbInvariantableWorkspaceContextInterface, UmbWorkspaceContext, UmbWorkspaceContextInterface } from '@umbraco-cms/backoffice/workspace'; import type { DataTypeResponseModel } from '@umbraco-cms/backoffice/backend-api'; -import { appendToFrozenArray, UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; -import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { appendToFrozenArray, UmbArrayState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; +import { UmbControllerHost, UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; +import { Observable, combineLatest, map } from '@umbraco-cms/backoffice/external/rxjs'; +import { PropertyEditorConfigDefaultData, PropertyEditorConfigProperty, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import { UMB_PROPERTY_EDITOR_SCHEMA_ALIAS_DEFAULT } from '@umbraco-cms/backoffice/property-editor'; export class UmbDataTypeWorkspaceContext extends UmbWorkspaceContext - implements UmbEntityWorkspaceContextInterface + implements UmbInvariantableWorkspaceContextInterface { // TODO: revisit. temp solution because the create and response models are different. #data = new UmbObjectState(undefined); data = this.#data.asObservable(); + #getDataPromise?: Promise; name = this.#data.asObservablePart((data) => data?.name); id = this.#data.asObservablePart((data) => data?.id); + propertyEditorUiAlias = this.#data.asObservablePart((data) => data?.propertyEditorUiAlias); + propertyEditorSchemaAlias = this.#data.asObservablePart((data) => data?.propertyEditorAlias); + + #properties = new UmbObjectState | undefined>(undefined); + properties: Observable | undefined> = this.#properties.asObservable(); + + private _propertyEditorSchemaConfigDefaultData: Array = []; + private _propertyEditorUISettingsDefaultData: Array = []; + + private _propertyEditorSchemaConfigProperties?: Array; + private _propertyEditorUISettingsProperties?: Array; + + private _configDefaultData?: Array; + + #defaults = new UmbArrayState([], (entry) => entry.alias); + defaults = this.#defaults.asObservable(); + constructor(host: UmbControllerHostElement) { super(host, 'Umb.Workspace.DataType', new UmbDataTypeRepository(host)); + + this.observe(this.propertyEditorUiAlias, (propertyEditorUiAlias) => { + if (!propertyEditorUiAlias) { + // No property editor ui alias, so we clean up and reset the properties. + this.removeControllerByAlias('propertyEditorUiAlias'); + this._propertyEditorUISettingsProperties = []; + this._propertyEditorUISettingsDefaultData = []; + this._mergeConfigProperties(); + this._mergeConfigDefaultData(); + return; + } + + this.observe( + umbExtensionsRegistry.getByTypeAndAlias('propertyEditorUi', propertyEditorUiAlias), + (manifest) => { + this._observePropertyEditorSchemaConfig( + manifest?.meta.propertyEditorSchemaAlias || UMB_PROPERTY_EDITOR_SCHEMA_ALIAS_DEFAULT + ); + this._propertyEditorUISettingsProperties = manifest?.meta.settings?.properties || []; + this._propertyEditorUISettingsDefaultData = manifest?.meta.settings?.defaultData || []; + this._mergeConfigProperties(); + this._mergeConfigDefaultData(); + } + , 'observePropertyEditorUiAlias' + ); + }); + } + + private _observePropertyEditorSchemaConfig(propertyEditorSchemaAlias: string) { + this.observe( + umbExtensionsRegistry.getByTypeAndAlias('propertyEditorSchema', propertyEditorSchemaAlias), + (manifest) => { + this._propertyEditorSchemaConfigProperties = manifest?.meta.settings?.properties || []; + this._propertyEditorSchemaConfigDefaultData = manifest?.meta.settings?.defaultData || []; + this._mergeConfigProperties(); + this._mergeConfigDefaultData(); + } + ); + } + + private _mergeConfigProperties() { + if(this._propertyEditorSchemaConfigProperties && this._propertyEditorUISettingsProperties) { + this.#properties.next([...this._propertyEditorSchemaConfigProperties, ...this._propertyEditorUISettingsProperties]); + } + } + + private _mergeConfigDefaultData() { + if(!this._propertyEditorSchemaConfigDefaultData || !this._propertyEditorUISettingsDefaultData) return; + + this._configDefaultData = [ + ...this._propertyEditorSchemaConfigDefaultData, + ...this._propertyEditorUISettingsDefaultData, + ]; + this.#defaults.next(this._configDefaultData); + } + + public getPropertyDefaultValue(alias: string) { + return this._configDefaultData?.find((x) => x.alias === alias)?.value; + } + + createVariantContext(host: UmbControllerHost): UmbDataTypeVariantContext { + return new UmbDataTypeVariantContext(host, this); } async load(id: string) { - const { data } = await this.repository.requestById(id); + this.#getDataPromise = this.repository.requestById(id); + const { data } = await this.#getDataPromise; if (data) { this.setIsNew(false); this.#data.update(data); @@ -29,7 +114,8 @@ export class UmbDataTypeWorkspaceContext } async create(parentId: string | null) { - let { data } = await this.repository.createScaffold(parentId); + this.#getDataPromise = this.repository.createScaffold(parentId); + let { data } = await this.#getDataPromise; if (this.modalContext) { data = { ...data, ...this.modalContext.data.preset }; } @@ -52,6 +138,9 @@ export class UmbDataTypeWorkspaceContext return 'data-type'; } + getName() { + return this.#data.getValue()?.name; + } setName(name: string) { this.#data.update({ name }); } @@ -63,8 +152,29 @@ export class UmbDataTypeWorkspaceContext this.#data.update({ propertyEditorUiAlias: alias }); } + async propertyValueByAlias(propertyAlias: string) { + await this.#getDataPromise; + + // TODO: Merge map.. + + return combineLatest([ + this.#data.asObservablePart((data) => data?.values?.find((x) => x.alias === propertyAlias)?.value as ReturnType), + this.#defaults.asObservablePart((defaults) => defaults?.find((x) => x.alias === propertyAlias)?.value as ReturnType), + ]).pipe( + map(([value, defaultValue]) => { + return (value ?? defaultValue); + }) + ); + //return this.#data.asObservablePart((data) => data?.values?.find((x) => x.alias === propertyAlias)?.value ?? this.getPropertyDefaultValue(propertyAlias) as ReturnType); + } + + getPropertyValue(propertyAlias: string) { + return this.#data.getValue()?.values?.find((x) => x.alias === propertyAlias)?.value as ReturnType ?? this.getPropertyDefaultValue(propertyAlias) as ReturnType; + } + // TODO: its not called a property in the model, but we do consider this way in our front-end - setPropertyValue(alias: string, value: unknown) { + async setPropertyValue(alias: string, value: unknown) { + await this.#getDataPromise; const entry = { alias: alias, value: value }; const currentData = this.#data.value; @@ -97,7 +207,7 @@ export class UmbDataTypeWorkspaceContext } } -export const UMB_DATA_TYPE_WORKSPACE_CONTEXT = new UmbContextToken( +export const UMB_DATA_TYPE_WORKSPACE_CONTEXT = new UmbContextToken( 'UmbWorkspaceContext', (context): context is UmbDataTypeWorkspaceContext => context.getEntityType?.() === 'data-type' ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/workspace/views/details/data-type-details-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/workspace/views/details/data-type-details-workspace-view.element.ts index f743eaa864..9ff081efdc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/workspace/views/details/data-type-details-workspace-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/data-types/workspace/views/details/data-type-details-workspace-view.element.ts @@ -73,7 +73,7 @@ export class UmbDataTypeDetailsWorkspaceViewEditElement if (!propertyEditorSchema) return; this._setPropertyEditorUiAlias(propertyEditorSchema.meta.defaultPropertyEditorUiAlias ?? undefined); }, - '_observepropertyEditorSchemaForDefaultUI' + '_observePropertyEditorSchemaForDefaultUI' ); } else { this._setPropertyEditorUiAlias(undefined); @@ -103,8 +103,8 @@ export class UmbDataTypeDetailsWorkspaceViewEditElement return; } - // remove the '_observepropertyEditorSchemaForDefaultUI' controller, as we do not want to observe for default value anymore: - this.removeControllerByAlias('_observepropertyEditorSchemaForDefaultUI'); + // remove the '_observePropertyEditorSchemaForDefaultUI' controller, as we do not want to observe for default value anymore: + this.removeControllerByAlias('_observePropertyEditorSchemaForDefaultUI'); this.observe( umbExtensionsRegistry.getByTypeAndAlias('propertyEditorUi', propertyEditorUiAlias), diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/extensions/workspace/extension-root-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/extensions/workspace/extension-root-workspace.element.ts index ada92e37b6..a413079617 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/extensions/workspace/extension-root-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/extensions/workspace/extension-root-workspace.element.ts @@ -76,7 +76,7 @@ export class UmbExtensionRootWorkspaceElement extends UmbLitElement { ${extension.type} - ${isManifestElementNameType(extension) ? extension.name : `[Custom extension] ${extension.name}`} + ${extension.name} ${extension.alias} ${extension.weight ? extension.weight : ''} diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/languages/workspace/language/language-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/languages/workspace/language/language-workspace.context.ts index 1b8609c9b1..b749c5f662 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/languages/workspace/language/language-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/languages/workspace/language/language-workspace.context.ts @@ -1,5 +1,5 @@ import { UmbLanguageRepository } from '../../repository/language.repository.js'; -import { UmbEntityWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; +import { UmbSaveableWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; import { ApiError, LanguageResponseModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; @@ -7,7 +7,7 @@ import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; export class UmbLanguageWorkspaceContext extends UmbWorkspaceContext - implements UmbEntityWorkspaceContextInterface + implements UmbSaveableWorkspaceContextInterface { #data = new UmbObjectState(undefined); data = this.#data.asObservable(); @@ -103,7 +103,7 @@ export class UmbLanguageWorkspaceContext } -export const UMB_LANGUAGE_WORKSPACE_CONTEXT = new UmbContextToken( +export const UMB_LANGUAGE_WORKSPACE_CONTEXT = new UmbContextToken( 'UmbWorkspaceContext', (context): context is UmbLanguageWorkspaceContext => context.getEntityType?.() === 'language' ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/relation-types/workspace/relation-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/relation-types/workspace/relation-type-workspace.context.ts index d8e9eb97d4..b871b61208 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/relation-types/workspace/relation-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/relation-types/workspace/relation-type-workspace.context.ts @@ -1,5 +1,5 @@ import { UmbRelationTypeRepository } from '../repository/relation-type.repository.js'; -import { UmbEntityWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; +import { UmbSaveableWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; import type { RelationTypeBaseModel, RelationTypeResponseModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; @@ -7,7 +7,7 @@ import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; export class UmbRelationTypeWorkspaceContext extends UmbWorkspaceContext - implements UmbEntityWorkspaceContextInterface + implements UmbSaveableWorkspaceContextInterface { #data = new UmbObjectState(undefined); data = this.#data.asObservable(); @@ -79,7 +79,7 @@ export class UmbRelationTypeWorkspaceContext -export const UMB_RELATION_TYPE_WORKSPACE_CONTEXT = new UmbContextToken( +export const UMB_RELATION_TYPE_WORKSPACE_CONTEXT = new UmbContextToken( 'UmbWorkspaceContext', (context): context is UmbRelationTypeWorkspaceContext => context.getEntityType?.() === 'relation-type' ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/workspace/partial-view-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/workspace/partial-view-workspace.context.ts index 89792790cc..e333029321 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/workspace/partial-view-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/workspace/partial-view-workspace.context.ts @@ -2,7 +2,7 @@ import { UmbPartialViewsRepository } from '../repository/partial-views.repositor import { PartialViewDetails } from '../config.js'; import { createObservablePart, UmbBooleanState, UmbDeepState } from '@umbraco-cms/backoffice/observable-api'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; -import { UmbEntityWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; +import { UmbSaveableWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; import { loadCodeEditor } from '@umbraco-cms/backoffice/code-editor'; import { UpdatePartialViewRequestModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; @@ -10,7 +10,7 @@ import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; export class UmbPartialViewWorkspaceContext extends UmbWorkspaceContext< UmbPartialViewsRepository, PartialViewDetails -> implements UmbEntityWorkspaceContextInterface { +> implements UmbSaveableWorkspaceContextInterface { getEntityId(): string | undefined { return this.getData()?.path; } @@ -100,7 +100,7 @@ export class UmbPartialViewWorkspaceContext extends UmbWorkspaceContext< -export const UMB_PARTIAL_VIEW_WORKSPACE_CONTEXT = new UmbContextToken( +export const UMB_PARTIAL_VIEW_WORKSPACE_CONTEXT = new UmbContextToken( 'UmbWorkspaceContext', (context): context is UmbPartialViewWorkspaceContext => context.getEntityType?.() === 'partial-view' ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/stylesheet-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/stylesheet-workspace.context.ts index b91f3fc128..b511adfeba 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/stylesheet-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/stylesheet-workspace.context.ts @@ -1,6 +1,6 @@ import { UmbStylesheetRepository } from '../repository/stylesheet.repository.js'; import { StylesheetDetails } from '../index.js'; -import { UmbEntityWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; +import { UmbSaveableWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbArrayState, UmbBooleanState, UmbObjectState, createObservablePart } from '@umbraco-cms/backoffice/observable-api'; import { loadCodeEditor } from '@umbraco-cms/backoffice/code-editor'; @@ -9,7 +9,7 @@ import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; export type RichTextRuleModelSortable = RichTextRuleModel & { sortOrder?: number }; -export class UmbStylesheetWorkspaceContext extends UmbWorkspaceContext implements UmbEntityWorkspaceContextInterface { +export class UmbStylesheetWorkspaceContext extends UmbWorkspaceContext implements UmbSaveableWorkspaceContextInterface { #data = new UmbObjectState(undefined); #rules = new UmbArrayState([], (rule) => rule.name); data = this.#data.asObservable(); @@ -166,7 +166,7 @@ export class UmbStylesheetWorkspaceContext extends UmbWorkspaceContext( +export const UMB_STYLESHEET_WORKSPACE_CONTEXT = new UmbContextToken( 'UmbWorkspaceContext', (context): context is UmbStylesheetWorkspaceContext => context.getEntityType?.() === 'stylesheet' ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/workspace/template-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/workspace/template-workspace.context.ts index eb16956d22..3513ae3800 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/workspace/template-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/workspace/template-workspace.context.ts @@ -1,6 +1,6 @@ import { UmbTemplateRepository } from '../repository/template.repository.js'; import { loadCodeEditor } from '@umbraco-cms/backoffice/code-editor'; -import { UmbEntityWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; +import { UmbSaveableWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; import { createObservablePart, UmbBooleanState, @@ -11,7 +11,7 @@ import type { TemplateItemResponseModel, TemplateResponseModel } from '@umbraco- import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; -export class UmbTemplateWorkspaceContext extends UmbWorkspaceContext implements UmbEntityWorkspaceContextInterface { +export class UmbTemplateWorkspaceContext extends UmbWorkspaceContext implements UmbSaveableWorkspaceContextInterface { #data = new UmbDeepState(undefined); data = this.#data.asObservable(); #masterTemplate = new UmbObjectState(null); @@ -170,7 +170,7 @@ ${currentContent}`; -export const UMB_TEMPLATE_WORKSPACE_CONTEXT = new UmbContextToken( +export const UMB_TEMPLATE_WORKSPACE_CONTEXT = new UmbContextToken( 'UmbWorkspaceContext', (context): context is UmbTemplateWorkspaceContext => context.getEntityType?.() === 'template' ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/users/user-groups/workspace/user-group-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/users/user-groups/workspace/user-group-workspace.context.ts index b9ce1e2fe3..04b1193c9a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/users/user-groups/workspace/user-group-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/users/user-groups/workspace/user-group-workspace.context.ts @@ -1,6 +1,6 @@ import { UmbUserGroupRepository } from '../repository/user-group.repository.js'; import { UmbUserRepository } from '../../users/repository/user.repository.js'; -import { UmbEntityWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; +import { UmbSaveableWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; import type { UserGroupResponseModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbArrayState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; @@ -8,7 +8,7 @@ import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; export class UmbUserGroupWorkspaceContext extends UmbWorkspaceContext - implements UmbEntityWorkspaceContextInterface + implements UmbSaveableWorkspaceContextInterface { #data = new UmbObjectState(undefined); data = this.#data.asObservable(); @@ -55,9 +55,11 @@ export class UmbUserGroupWorkspaceContext getEntityId(): string | undefined { throw new Error('Method not implemented.'); } + getEntityType(): string { - throw new Error('Method not implemented.'); + return 'user-group'; } + getData(): UserGroupResponseModel | undefined { throw new Error('Method not implemented.'); } @@ -105,7 +107,7 @@ export class UmbUserGroupWorkspaceContext } -export const UMB_USER_GROUP_WORKSPACE_CONTEXT = new UmbContextToken( +export const UMB_USER_GROUP_WORKSPACE_CONTEXT = new UmbContextToken( 'UmbWorkspaceContext', (context): context is UmbUserGroupWorkspaceContext => context.getEntityType?.() === 'user-group' ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/users/users/workspace/user-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/users/users/workspace/user-workspace.context.ts index b9c4567217..9ac4fc84a3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/users/users/workspace/user-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/users/users/workspace/user-workspace.context.ts @@ -1,6 +1,6 @@ import { UmbUserRepository } from '../repository/user.repository.js'; import { type UmbUserDetail } from '../index.js'; -import { UmbEntityWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; +import { UmbSaveableWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import type { UpdateUserRequestModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; @@ -10,7 +10,7 @@ import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; export class UmbUserWorkspaceContext extends UmbWorkspaceContext - implements UmbEntityWorkspaceContextInterface + implements UmbSaveableWorkspaceContextInterface { #authContext?: typeof UMB_AUTH.TYPE; @@ -82,7 +82,7 @@ export class UmbUserWorkspaceContext } } -export const UMB_USER_WORKSPACE_CONTEXT = new UmbContextToken( +export const UMB_USER_WORKSPACE_CONTEXT = new UmbContextToken( 'UmbWorkspaceContext', (context): context is UmbUserWorkspaceContext => context.getEntityType?.() === 'user' ); diff --git a/src/Umbraco.Web.UI.Client/storybook/stories/extending/variant-context.mdx b/src/Umbraco.Web.UI.Client/storybook/stories/extending/variant-context.mdx new file mode 100644 index 0000000000..98a085344b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/storybook/stories/extending/variant-context.mdx @@ -0,0 +1,28 @@ +import { Meta } from '@storybook/addon-docs'; + + + +# Variant Context + +The Variant Context is a context that holds the data for a set of properties. +Property Editors UIs require the Variant Context to be present to work. This enables Property Editor UIs to have a generic relation with its ownership. + +The Variant context holds a name and a set of properties. What makes a property can vary but we require an alias and a value. + +## Variant Context concerning Property Editors and Workspaces. + +A Variant Context is the connection point between a Property Editor and a Workspace. + +The hierarchy is as follows: +- Workspace Context + - Variant Context + - Property Editor UIs + +A variant context covers a set of properties, in some cases a workspace then needs to have multiple variants. An example of such is Document Workspace. Each variant has its own set of properties and a name. + +## Setup a Variant Context + +It would be good to have examples for developers to see how to set up a Variant Context, in code. (This might need to be a tutorial demonstrating implementing a simple workspace with a variant with Property Editor UIs) diff --git a/src/Umbraco.Web.UI.Client/storybook/stories/extending/workspaces/context.mdx b/src/Umbraco.Web.UI.Client/storybook/stories/extending/workspaces/context.mdx index 1ea5c76b9b..cb9e6a0b86 100644 --- a/src/Umbraco.Web.UI.Client/storybook/stories/extending/workspaces/context.mdx +++ b/src/Umbraco.Web.UI.Client/storybook/stories/extending/workspaces/context.mdx @@ -8,12 +8,14 @@ import { Meta } from '@storybook/addon-docs'; # Workspace Context A Workspace context is a container for the data of a workspace. It is a wrapper around the data of the entity that the workspace is working on. It is also responsible for loading and saving the data to the server. -TODO: extend the description of a workspace +TODO: Extend the description of a workspace (rough notes) -- A workspace context knows about its entity type (e.g. content, media, member, etc.) and holds its unique string (ex: key). -- Most workspaces contexts holds a draft state of its entities data. It is a copy of the entity data that can be modified at runtime and send to the server to be saved. +- A workspace context knows about its entity type (e.g. content, media, member, etc.) and holds its unique string (e.g.: key). +- Most workspaces contexts hold a draft state of its entity data. It is a copy of the entity data that can be modified at runtime and sent to the server to be saved. + +If a workspace wants to utilize Property Editor UIs, then it must provide a variant context for the property editors. The variant-context is the generic interface between workspace and property editors. See variant contexts for more info. TODO: More points and examples: diff --git a/src/Umbraco.Web.UI.Client/vite.config.ts b/src/Umbraco.Web.UI.Client/vite.config.ts index ecbef57bf6..85f5f135ab 100644 --- a/src/Umbraco.Web.UI.Client/vite.config.ts +++ b/src/Umbraco.Web.UI.Client/vite.config.ts @@ -13,6 +13,10 @@ export const plugins: PluginOption[] = [ src: 'public-assets/App_Plugins/*.js', dest: 'App_Plugins', }, + { + src: 'public-assets/App_Plugins/custom-bundle-package/*.js', + dest: 'App_Plugins/custom-bundle-package', + }, { src: 'src/assets/*.svg', dest: 'umbraco/backoffice/assets',