From b2232cef6e9dabcddca8497b3bbef4a682276e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 4 Jul 2024 11:12:22 +0200 Subject: [PATCH] property type workspace --- src/Umbraco.Web.UI.Client/package.json | 7 +- .../src/packages/core/manifests.ts | 2 + .../src/packages/core/property-type/index.ts | 1 + .../packages/core/property-type/manifests.ts | 4 + .../src/packages/core/property-type/types.ts | 3 + .../core/property-type/workspace/constants.ts | 1 + .../core/property-type/workspace/index.ts | 2 + .../core/property-type/workspace/manifests.ts | 57 +++ .../property-type-workspace-editor.element.ts | 64 +++ .../property-type-workspace.context-token.ts | 12 + .../property-type-workspace.context.ts | 189 +++++++ .../property-type-workspace.modal-token.ts | 18 + ...roperty-workspace-view-settings.element.ts | 469 ++++++++++++++++++ src/Umbraco.Web.UI.Client/tsconfig.json | 7 +- 14 files changed, 830 insertions(+), 6 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-type/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-type/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-type/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace-editor.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace.context-token.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace.context.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace.modal-token.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/views/settings/property-workspace-view-settings.element.ts diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 51043adc87..75cf4b4e06 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -25,8 +25,8 @@ "./code-editor": "./dist-cms/packages/templating/code-editor/index.js", "./collection": "./dist-cms/packages/core/collection/index.js", "./components": "./dist-cms/packages/core/components/index.js", - "./content": "./dist-cms/packages/core/content/index.js", "./content-type": "./dist-cms/packages/core/content-type/index.js", + "./content": "./dist-cms/packages/core/content/index.js", "./culture": "./dist-cms/packages/core/culture/index.js", "./current-user": "./dist-cms/packages/user/current-user/index.js", "./data-type": "./dist-cms/packages/data-type/index.js", @@ -35,9 +35,9 @@ "./document-blueprint": "./dist-cms/packages/documents/document-blueprints/index.js", "./document-type": "./dist-cms/packages/documents/document-types/index.js", "./document": "./dist-cms/packages/documents/documents/index.js", - "./entity": "./dist-cms/packages/core/entity/index.js", "./entity-action": "./dist-cms/packages/core/entity-action/index.js", "./entity-bulk-action": "./dist-cms/packages/core/entity-bulk-action/index.js", + "./entity": "./dist-cms/packages/core/entity/index.js", "./event": "./dist-cms/packages/core/event/index.js", "./extension-registry": "./dist-cms/packages/core/extension-registry/index.js", "./icon": "./dist-cms/packages/core/icon-registry/index.js", @@ -63,6 +63,7 @@ "./picker-input": "./dist-cms/packages/core/picker-input/index.js", "./property-action": "./dist-cms/packages/core/property-action/index.js", "./property-editor": "./dist-cms/packages/core/property-editor/index.js", + "./property-type": "./dist-cms/packages/core/property-type/index.js", "./property": "./dist-cms/packages/core/property/index.js", "./recycle-bin": "./dist-cms/packages/core/recycle-bin/index.js", "./relation-type": "./dist-cms/packages/relations/relation-types/index.js", @@ -73,8 +74,8 @@ "./script": "./dist-cms/packages/templating/scripts/index.js", "./search": "./dist-cms/packages/search/index.js", "./section": "./dist-cms/packages/core/section/index.js", - "./settings": "./dist-cms/packages/settings/index.js", "./server-file-system": "./dist-cms/packages/core/server-file-system/index.js", + "./settings": "./dist-cms/packages/settings/index.js", "./sorter": "./dist-cms/packages/core/sorter/index.js", "./static-file": "./dist-cms/packages/static-file/index.js", "./store": "./dist-cms/packages/core/store/index.js", diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts index 460f7acc8d..5f6f036ea4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts @@ -10,6 +10,7 @@ import { manifests as iconRegistryManifests } from './icon-registry/manifests.js import { manifests as localizationManifests } from './localization/manifests.js'; import { manifests as modalManifests } from './modal/common/manifests.js'; import { manifests as propertyActionManifests } from './property-action/manifests.js'; +import { manifests as propertyTypeManifests } from './property-type/manifests.js'; import { manifests as recycleBinManifests } from './recycle-bin/manifests.js'; import { manifests as sectionManifests } from './section/manifests.js'; import { manifests as serverFileSystemManifests } from './server-file-system/manifests.js'; @@ -33,6 +34,7 @@ export const manifests: Array = [ ...workspaceManifests, ...contentManifests, ...contentTypeManifests, + ...propertyTypeManifests, ...settingsManifests, ...modalManifests, ...entityActionManifests, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/index.ts new file mode 100644 index 0000000000..60f06132a7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/index.ts @@ -0,0 +1 @@ +export * from './workspace/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/manifests.ts new file mode 100644 index 0000000000..71c165c901 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/manifests.ts @@ -0,0 +1,4 @@ +import { manifests as workspaceManifests } from './workspace/manifests.js'; +import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [...workspaceManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/types.ts new file mode 100644 index 0000000000..b4e94d4763 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/types.ts @@ -0,0 +1,3 @@ +import type { UmbPropertyTypeModel, UmbPropertyTypeScaffoldModel } from '@umbraco-cms/backoffice/content-type'; + +export type UmbPropertyTypeData = UmbPropertyTypeModel | UmbPropertyTypeScaffoldModel; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/constants.ts new file mode 100644 index 0000000000..9ef9d5cc4e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/constants.ts @@ -0,0 +1 @@ +export const UMB_PROPERTY_TYPE_WORKSPACE_ALIAS = 'Umb.Workspace.PropertyType'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/index.ts new file mode 100644 index 0000000000..63e62a58f3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/index.ts @@ -0,0 +1,2 @@ +export * from './property-type-workspace.context-token.js'; +export * from './property-type-workspace.modal-token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/manifests.ts new file mode 100644 index 0000000000..dc63643c96 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/manifests.ts @@ -0,0 +1,57 @@ +import { UMB_PROPERTY_TYPE_WORKSPACE_ALIAS } from './constants.js'; +import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'workspace', + kind: 'routable', + name: 'Block Workspace', + alias: UMB_PROPERTY_TYPE_WORKSPACE_ALIAS, + api: () => import('./property-type-workspace.context.js'), + meta: { + entityType: 'property-type', + }, + }, + { + type: 'workspaceView', + alias: 'Umb.WorkspaceView.PropertyType.Settings', + name: 'Block Workspace Content View', + js: () => import('./views/settings/property-workspace-view-settings.element.js'), + weight: 1000, + meta: { + label: '#general_content', + pathname: 'content', + icon: 'icon-document', + }, + conditions: [ + { + alias: 'Umb.Condition.WorkspaceAlias', + match: UMB_PROPERTY_TYPE_WORKSPACE_ALIAS, + }, + ], + TODO_conditions: [ + { + alias: 'Umb.Condition.BlockEntryShowContentEdit', + }, + ], + }, + { + type: 'workspaceAction', + kind: 'default', + alias: 'Umb.WorkspaceAction.PropertyType.Submit', + name: 'Submit Property Type Workspace Action', + api: UmbSubmitWorkspaceAction, + meta: { + label: '#general_submit', + look: 'primary', + color: 'positive', + }, + conditions: [ + { + alias: 'Umb.Condition.WorkspaceAlias', + oneOf: [UMB_PROPERTY_TYPE_WORKSPACE_ALIAS], + }, + ], + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace-editor.element.ts new file mode 100644 index 0000000000..b3317b8407 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace-editor.element.ts @@ -0,0 +1,64 @@ +import { UMB_PROPERTY_TYPE_WORKSPACE_CONTEXT } from './property-type-workspace.context-token.js'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { customElement, css, html, state, property } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbRepositoryItemsManager } from '@umbraco-cms/backoffice/repository'; +import type { UmbDocumentTypeItemModel } from '@umbraco-cms/backoffice/document-type'; +import { UMB_DOCUMENT_TYPE_ITEM_REPOSITORY_ALIAS } from '@umbraco-cms/backoffice/document-type'; + +@customElement('umb-property-type-workspace-editor') +export class UmbPropertyTypeWorkspaceEditorElement extends UmbLitElement { + // + #itemManager = new UmbRepositoryItemsManager( + this, + UMB_DOCUMENT_TYPE_ITEM_REPOSITORY_ALIAS, + (x) => x.unique, + ); + + #workspaceContext?: typeof UMB_PROPERTY_TYPE_WORKSPACE_CONTEXT.TYPE; + + @state() + _name?: string; + + @property({ type: String, attribute: false }) + workspaceAlias?: string; + + constructor() { + super(); + + this.consumeContext(UMB_PROPERTY_TYPE_WORKSPACE_CONTEXT, (instance) => { + this.#workspaceContext = instance; + this.#workspaceContext?.createPropertyDatasetContext(this); + }); + } + + override render() { + return this.workspaceAlias + ? html` + + + ` + : ''; + } + + static override styles = [ + UmbTextStyles, + css` + :host { + display: block; + width: 100%; + height: 100%; + } + `, + ]; +} + +export default UmbPropertyTypeWorkspaceEditorElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-property-type-workspace-editor': UmbPropertyTypeWorkspaceEditorElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace.context-token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace.context-token.ts new file mode 100644 index 0000000000..60e6347d8f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace.context-token.ts @@ -0,0 +1,12 @@ +import type { UmbPropertyTypeWorkspaceContext } from './property-type-workspace.context.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; +import type { UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; + +export const UMB_PROPERTY_TYPE_WORKSPACE_CONTEXT = new UmbContextToken< + UmbWorkspaceContext, + UmbPropertyTypeWorkspaceContext +>( + 'UmbWorkspaceContext', + undefined, + (context): context is UmbPropertyTypeWorkspaceContext => (context as any).IS_PROPERTY_TYPE_WORKSPACE_CONTEXT, +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace.context.ts new file mode 100644 index 0000000000..d159e11a51 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace.context.ts @@ -0,0 +1,189 @@ +import { UmbPropertyTypeWorkspaceEditorElement } from './property-type-workspace-editor.element.js'; +import type { UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property'; +import type { + UmbInvariantDatasetWorkspaceContext, + UmbRoutableWorkspaceContext, +} from '@umbraco-cms/backoffice/workspace'; +import { + UmbSubmittableWorkspaceContextBase, + UmbInvariantWorkspacePropertyDatasetContext, + UmbWorkspaceIsNewRedirectController, +} from '@umbraco-cms/backoffice/workspace'; +import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { ManifestWorkspace } from '@umbraco-cms/backoffice/extension-registry'; +import { + UMB_CONTENT_TYPE_WORKSPACE_CONTEXT, + UmbPropertyTypeModel, + UmbPropertyTypeSettingsModalData, +} from '@umbraco-cms/backoffice/content-type'; +import { UmbId } from '@umbraco-cms/backoffice/id'; +import { UmbPropertyTypeData } from '../types.js'; + +export class UmbPropertyTypeWorkspaceContext + extends UmbSubmittableWorkspaceContextBase + implements UmbInvariantDatasetWorkspaceContext, UmbRoutableWorkspaceContext +{ + // Just for context token safety: + public readonly IS_PROPERTY_TYPE_WORKSPACE_CONTEXT = true; + + #entityType: string; + #data = new UmbObjectState(undefined); + readonly data = this.#data.asObservable(); + + readonly name = this.#data.asObservablePart((data) => data?.name); + readonly unique = this.#data.asObservablePart((data) => data?.id); + + constructor(host: UmbControllerHost, args: { manifest: ManifestWorkspace }) { + super(host, args.manifest.alias); + const manifest = args.manifest; + this.#entityType = manifest.meta?.entityType; + + this.routes.setRoutes([ + { + // Would it make more sense to have groupKey before elementTypeKey? + path: 'create/:containerUnique', + component: UmbPropertyTypeWorkspaceEditorElement, + setup: async (component, info) => { + (component as UmbPropertyTypeWorkspaceEditorElement).workspaceAlias = manifest.alias; + + const containerUnique = + info.match.params.containerUnique === 'null' ? null : info.match.params.containerUnique; + this.create(containerUnique); + + new UmbWorkspaceIsNewRedirectController( + this, + this, + this.getHostElement().shadowRoot!.querySelector('umb-router-slot')!, + ); + }, + }, + { + path: 'edit/:unique', + component: UmbPropertyTypeWorkspaceEditorElement, + setup: (component, info) => { + (component as UmbPropertyTypeWorkspaceEditorElement).workspaceAlias = manifest.alias; + + const unique = info.match.params.unique; + this.load(unique); + }, + }, + ]); + } + + protected override resetState() { + super.resetState(); + this.#data.setValue(undefined); + } + + createPropertyDatasetContext(host: UmbControllerHost): UmbPropertyDatasetContext { + return new UmbInvariantWorkspacePropertyDatasetContext(host, this); + } + + async load(unique: string) { + this.resetState(); + const context = await this.getContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT); + this.observe(await context.structure.propertyStructureById(unique), (property) => { + if (property) { + this.#data.setValue(property as PropertyTypeData); + } + // Fallback to undefined: + this.#data.setValue(undefined); + }); + } + + async create(containerId?: string | null) { + this.resetState(); + + let data: PropertyTypeData = { + id: UmbId.new(), + container: containerId ? { id: containerId } : null, + alias: '', + name: '', + description: '', + variesByCulture: false, + variesBySegment: false, + validation: { + mandatory: false, + mandatoryMessage: null, + regEx: null, + regExMessage: null, + }, + appearance: { + labelOnTop: false, + }, + sortOrder: 0, + } as PropertyTypeData; + + // If we have a modal context, we blend in the modal preset data: [NL] + if (this.modalContext) { + data = { ...data, ...this.modalContext.data.preset }; + } + + this.setIsNew(true); + this.#data.setValue(data); + return { data }; + } + + getData() { + return this.#data.getValue(); + } + updateData(partialData: Partial) { + this.#data?.update(partialData); + } + + getUnique() { + return this.getData()!.id; + } + + getEntityType() { + return this.#entityType; + } + + getName() { + return this.#data.getValue()?.name; + } + setName(name: string | undefined) { + this.#data.update({ name: name }); + } + + async propertyValueByAlias(propertyAlias: string) { + return this.#data.asObservablePart((data) => data?.[propertyAlias as keyof PropertyTypeData] as ReturnType); + } + + getPropertyValue(propertyAlias: string) { + return this.#data.getValue()?.[propertyAlias as keyof PropertyTypeData] as ReturnType; + } + + async setPropertyValue(alias: string, value: unknown) { + const currentData = this.#data.value; + if (currentData) { + this.#data.update({ ...currentData, [alias]: value }); + } + } + + async submit() { + if (!this.modalContext) { + throw new Error('Needs to be in a modal to submit.'); + } + const contentTypeUnique = (this.modalContext.data as unknown as UmbPropertyTypeSettingsModalData).contentTypeUnique; + + const data = this.#data.getValue(); + if (!data) { + throw new Error('No data to submit.'); + } + + const context = await this.getContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT); + + context.structure.insertProperty(contentTypeUnique, data); + + this.setIsNew(false); + } + + public override destroy(): void { + this.#data.destroy(); + super.destroy(); + } +} + +export { UmbPropertyTypeWorkspaceContext as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace.modal-token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace.modal-token.ts new file mode 100644 index 0000000000..12d1784ffa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace.modal-token.ts @@ -0,0 +1,18 @@ +import type { UmbWorkspaceModalData, UmbWorkspaceModalValue } from '@umbraco-cms/backoffice/modal'; +import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; + +export interface UmbBlockWorkspaceData extends UmbWorkspaceModalData { + originData: OriginDataType; +} + +export const UMB_PROPERTY_TYPE_WORKSPACE_MODAL = new UmbModalToken( + 'Umb.Modal.Workspace', + { + modal: { + type: 'sidebar', + size: 'small', + }, + data: { entityType: 'property-type', preset: {}, originData: {} }, + }, + // Recast the type, so the entityType data prop is not required: +) as UmbModalToken, UmbWorkspaceModalValue>; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/views/settings/property-workspace-view-settings.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/views/settings/property-workspace-view-settings.element.ts new file mode 100644 index 0000000000..59b5bf5115 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/views/settings/property-workspace-view-settings.element.ts @@ -0,0 +1,469 @@ +import { css, html, customElement, state, nothing } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbLitElement, umbFocus } from '@umbraco-cms/backoffice/lit-element'; +import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/extension-registry'; +import { UMB_CONTENT_TYPE_WORKSPACE_CONTEXT, UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type'; +import { UUIBooleanInputEvent, UUIInputEvent, UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; +import { generateAlias } from '@umbraco-cms/backoffice/utils'; +import { UMB_PROPERTY_TYPE_WORKSPACE_CONTEXT } from '../../../index.js'; + +@customElement('umb-property-type-workspace-view-settings') +export class UmbPropertyTypeWorkspaceViewSettingsElement extends UmbLitElement implements UmbWorkspaceViewElement { + #context?: typeof UMB_PROPERTY_TYPE_WORKSPACE_CONTEXT.TYPE; + + @state() private _customValidationOptions: Array