diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts index 2a1d7e72da..06d5436491 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts @@ -3,12 +3,8 @@ import { UmbDocumentRepository } from '../repository/document.repository'; import { UmbDocumentTypeRepository } from '../../document-types/repository/document-type.repository'; import { UmbWorkspaceVariableEntityContextInterface } from '../../../shared/components/workspace/workspace-context/workspace-variable-entity-context.interface'; import { UmbVariantId } from '../../../shared/variants/variant-id.class'; -import type { - DocumentModel, - DocumentTypeModel, - DocumentTypePropertyTypeContainerModel, - DocumentTypePropertyTypeModel, -} from '@umbraco-cms/backend-api'; +import { UmbWorkspacePropertyStructureManager } from '../../../shared/components/workspace/workspace-context/workspace-property-structure-manager.class'; +import type { DocumentModel } from '@umbraco-cms/backend-api'; import { partialUpdateFrozenArray, ObjectState, ArrayState, UmbObserverController } from '@umbraco-cms/observable-api'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; @@ -28,7 +24,6 @@ export class UmbDocumentWorkspaceContext { #host: UmbControllerHostInterface; #documentRepository: UmbDocumentRepository; - #documentTypeRepository: UmbDocumentTypeRepository; /** * The document is the current stored version of the document. @@ -50,19 +45,17 @@ export class UmbDocumentWorkspaceContext #activeVariantsInfo = new ArrayState([], (x) => x.index); activeVariantsInfo = this.#activeVariantsInfo.asObservable(); - #documentTypes = new ArrayState([], (x) => x.key); - documentTypes = this.#documentTypes.asObservable(); - - // Notice the DocumentTypePropertyTypeContainerModel is equivalent to PropertyTypeContainerViewModelBaseModel, making it easy to generalize. - #containers = new ArrayState([], (x) => x.key); + readonly structure; constructor(host: UmbControllerHostInterface) { super(host); this.#host = host; - this.#documentRepository = new UmbDocumentRepository(this.#host); - this.#documentTypeRepository = new UmbDocumentTypeRepository(this.#host); - new UmbObserverController(this._host, this.documentTypeKey, (key) => this._loadDocumentType(key)); + this.#documentRepository = new UmbDocumentRepository(this.#host); + + this.structure = new UmbWorkspacePropertyStructureManager(this.#host, new UmbDocumentTypeRepository(this.#host)); + + new UmbObserverController(this._host, this.documentTypeKey, (key) => this.structure.loadType(key)); } async load(entityKey: string) { @@ -85,40 +78,6 @@ export class UmbDocumentWorkspaceContext return data || undefined; } - private async _loadDocumentType(key?: string) { - if (!key) return; - - const { data } = await this.#documentTypeRepository.requestByKey(key); - if (!data) return; - - // Load inherited and composed types: - await data?.compositions?.forEach(async (composition) => { - if (composition.key) { - this._loadDocumentType(composition.key); - } - }); - - new UmbObserverController(this._host, await this.#documentTypeRepository.byKey(key), (docType) => { - if (docType) { - this.#documentTypes.appendOne(docType); - this._initDocumentTypeContainers(docType); - this._loadDocumentTypeCompositions(docType); - } - }); - } - - private async _loadDocumentTypeCompositions(documentType: DocumentTypeModel) { - documentType.compositions?.forEach((composition) => { - this._loadDocumentType(composition.key); - }); - } - - private async _initDocumentTypeContainers(documentType: DocumentTypeModel) { - documentType.containers?.forEach((container) => { - this.#containers.appendOne(container); - }); - } - getData() { return this.#draft.getValue() || {}; } @@ -193,61 +152,6 @@ export class UmbDocumentWorkspaceContext ); } - // TODO: Structure methods: - - hasPropertyStructuresOf(containerKey: string | null) { - return this.#documentTypes.getObservablePart((docTypes) => { - return ( - docTypes.find((docType) => { - return docType.properties?.find((property) => property.containerKey === containerKey); - }) !== undefined - ); - }); - } - rootPropertyStructures() { - return this.propertyStructuresOf(null); - } - propertyStructuresOf(containerKey: string | null) { - return this.#documentTypes.getObservablePart((docTypes) => { - const props: DocumentTypePropertyTypeModel[] = []; - docTypes.forEach((docType) => { - docType.properties?.forEach((property) => { - if (property.containerKey === containerKey) { - props.push(property); - } - }); - }); - return props; - }); - } - - rootContainers(containerType: 'Group' | 'Tab') { - return this.#containers.getObservablePart((data) => { - return data.filter((x) => x.parentKey === null && x.type === containerType); - }); - } - - hasRootContainers(containerType: 'Group' | 'Tab') { - return this.#containers.getObservablePart((data) => { - return data.filter((x) => x.parentKey === null && x.type === containerType).length > 0; - }); - } - - containersOfParentKey( - parentKey: DocumentTypePropertyTypeContainerModel['parentKey'], - containerType: 'Group' | 'Tab' - ) { - return this.#containers.getObservablePart((data) => { - return data.filter((x) => x.parentKey === parentKey && x.type === containerType); - }); - } - - containersByNameAndType(name: string, containerType: 'Group' | 'Tab') { - return this.#containers.getObservablePart((data) => { - return data.filter((x) => x.name === name && x.type === containerType); - }); - } - getPropertyValue(alias: string, variantId?: UmbVariantId): void { const currentData = this.#draft.value; if (currentData) { @@ -259,7 +163,6 @@ export class UmbDocumentWorkspaceContext } setPropertyValue(alias: string, value: unknown, variantId?: UmbVariantId) { const partialEntry = { value }; - console.log('€€€€€setPropertyValue', alias, value, variantId?.toString()); const currentData = this.#draft.value; if (currentData) { const values = partialUpdateFrozenArray( diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit-properties.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit-properties.element.ts index 13cca1df0f..6bd3840d8a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit-properties.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit-properties.element.ts @@ -67,7 +67,7 @@ export class UmbDocumentWorkspaceViewEditPropertiesElement extends UmbLitElement // TODO: Should be no need to update this observable if its already there. this.observe( - this._workspaceContext!.containersByNameAndType(this._containerName, this._containerType), + this._workspaceContext!.structure.containersByNameAndType(this._containerName, this._containerType), (groupContainers) => { this._groupContainers = groupContainers || []; groupContainers.forEach((group) => { @@ -86,7 +86,7 @@ export class UmbDocumentWorkspaceViewEditPropertiesElement extends UmbLitElement // TODO: Should be no need to update this observable if its already there. this.observe( - this._workspaceContext.propertyStructuresOf(group.key), + this._workspaceContext.structure.propertyStructuresOf(group.key), (properties) => { // If this need to be able to remove properties, we need to clean out the ones of this group.key before inserting them: this._propertyStructure = this._propertyStructure.filter((x) => x.containerKey !== group.key); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit-tab.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit-tab.element.ts index b05f13291d..cd75390b36 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit-tab.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit-tab.element.ts @@ -80,7 +80,7 @@ export class UmbDocumentWorkspaceViewEditTabElement extends UmbLitElement { this._tabContainers.forEach((container) => { this.observe( - this._workspaceContext!.hasPropertyStructuresOf(container.key!), + this._workspaceContext!.structure.hasPropertyStructuresOf(container.key!), (hasTabProperties) => { this._hasTabProperties = hasTabProperties; }, @@ -95,7 +95,7 @@ export class UmbDocumentWorkspaceViewEditTabElement extends UmbLitElement { if (this._tabName) { this._groups = []; this.observe( - this._workspaceContext.containersByNameAndType(this._tabName, 'Tab'), + this._workspaceContext.structure.containersByNameAndType(this._tabName, 'Tab'), (tabContainers) => { this._tabContainers = tabContainers || []; if (this._tabContainers.length > 0) { @@ -116,7 +116,7 @@ export class UmbDocumentWorkspaceViewEditTabElement extends UmbLitElement { this._tabContainers.forEach((container) => { this.observe( - this._workspaceContext!.containersOfParentKey(container.key, 'Group'), + this._workspaceContext!.structure.containersOfParentKey(container.key, 'Group'), this._insertGroupContainers, '_observeGroupsOf_' + container.key ); @@ -127,7 +127,11 @@ export class UmbDocumentWorkspaceViewEditTabElement extends UmbLitElement { if (!this._workspaceContext || !this._noTabName) return; // This is where we potentially could observe root properties as well. - this.observe(this._workspaceContext!.rootContainers('Group'), this._insertGroupContainers, '_observeRootGroups'); + this.observe( + this._workspaceContext!.structure.rootContainers('Group'), + this._insertGroupContainers, + '_observeRootGroups' + ); } private _insertGroupContainers = (groupContainers: PropertyTypeContainerViewModelBaseModel[]) => { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit.element.ts index c5f19d1b0f..aadbc4f820 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/views/document-workspace-view-edit.element.ts @@ -19,8 +19,6 @@ export class UmbDocumentWorkspaceViewEditElement extends UmbLitElement { `, ]; - // TODO: get variant information via variant-content? - //private _hasRootProperties = false; private _hasRootGroups = false; @@ -52,7 +50,7 @@ export class UmbDocumentWorkspaceViewEditElement extends UmbLitElement { if (!this._workspaceContext) return; this.observe( - this._workspaceContext.rootContainers('Tab'), + this._workspaceContext.structure.rootContainers('Tab'), (tabs) => { tabs.forEach((tab) => { // Only add each tab name once, as our containers merge on name: @@ -79,7 +77,7 @@ export class UmbDocumentWorkspaceViewEditElement extends UmbLitElement { */ this.observe( - this._workspaceContext.hasRootContainers('Group'), + this._workspaceContext.structure.hasRootContainers('Group'), (hasRootGroups) => { this._hasRootGroups = hasRootGroups; this._createRoutes(); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-property-structure-manager.class.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-property-structure-manager.class.ts new file mode 100644 index 0000000000..96a9c72b21 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-property-structure-manager.class.ts @@ -0,0 +1,126 @@ +import { UmbDocumentTypeRepository } from '../../../../documents/document-types/repository/document-type.repository'; +import { + DocumentTypeModel, + DocumentTypePropertyTypeModel, + PropertyTypeContainerViewModelBaseModel, +} from '@umbraco-cms/backend-api'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { ArrayState, UmbObserverController } from '@umbraco-cms/observable-api'; + +export type PropertyContainerTypes = 'Group' | 'Tab'; + +// TODO: get this type from the repository, or use some generic type. +type T = DocumentTypeModel; + +// TODO: make general interface for NodeTypeRepository, to replace UmbDocumentTypeRepository: +export class UmbWorkspacePropertyStructureManager { + #host: UmbControllerHostInterface; + + #documentTypeRepository: R; + + #documentTypes = new ArrayState([], (x) => x.key); + + #containers = new ArrayState([], (x) => x.key); + + constructor(host: UmbControllerHostInterface, typeRepository: R) { + this.#host = host; + this.#documentTypeRepository = typeRepository; + } + + /** + * loadType will load the node type and all inherited and composed types. + * This will give us all the structure for properties and containers. + */ + public async loadType(key?: string) { + // TODO: I guess it would make sense to clean up, in this case we most likely don't need any of the old document types: + //this.#documentTypes.next([]); + await this._loadType(key); + } + + private async _loadType(key?: string) { + if (!key) return; + + const { data } = await this.#documentTypeRepository.requestByKey(key); + if (!data) return; + + // Load inherited and composed types: + await data?.compositions?.forEach(async (composition) => { + if (composition.key) { + this.loadType(composition.key); + } + }); + + new UmbObserverController(this.#host, await this.#documentTypeRepository.byKey(key), (docType) => { + if (docType) { + this.#documentTypes.appendOne(docType); + this._initDocumentTypeContainers(docType); + this._loadDocumentTypeCompositions(docType); + } + }); + } + + private async _loadDocumentTypeCompositions(documentType: T) { + documentType.compositions?.forEach((composition) => { + this._loadType(composition.key); + }); + } + + private async _initDocumentTypeContainers(documentType: T) { + documentType.containers?.forEach((container) => { + this.#containers.appendOne(container); + }); + } + + hasPropertyStructuresOf(containerKey: string | null) { + return this.#documentTypes.getObservablePart((docTypes) => { + return ( + docTypes.find((docType) => { + return docType.properties?.find((property) => property.containerKey === containerKey); + }) !== undefined + ); + }); + } + rootPropertyStructures() { + return this.propertyStructuresOf(null); + } + propertyStructuresOf(containerKey: string | null) { + return this.#documentTypes.getObservablePart((docTypes) => { + const props: DocumentTypePropertyTypeModel[] = []; + docTypes.forEach((docType) => { + docType.properties?.forEach((property) => { + if (property.containerKey === containerKey) { + props.push(property); + } + }); + }); + return props; + }); + } + + rootContainers(containerType: PropertyContainerTypes) { + return this.#containers.getObservablePart((data) => { + return data.filter((x) => x.parentKey === null && x.type === containerType); + }); + } + + hasRootContainers(containerType: PropertyContainerTypes) { + return this.#containers.getObservablePart((data) => { + return data.filter((x) => x.parentKey === null && x.type === containerType).length > 0; + }); + } + + containersOfParentKey( + parentKey: PropertyTypeContainerViewModelBaseModel['parentKey'], + containerType: PropertyContainerTypes + ) { + return this.#containers.getObservablePart((data) => { + return data.filter((x) => x.parentKey === parentKey && x.type === containerType); + }); + } + + containersByNameAndType(name: string, containerType: PropertyContainerTypes) { + return this.#containers.getObservablePart((data) => { + return data.filter((x) => x.name === name && x.type === containerType); + }); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/multiple-text-string/input-multiple-text-string-item/input-multiple-text-string-item.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/multiple-text-string/input-multiple-text-string-item/input-multiple-text-string-item.element.ts index d4906b2a54..09b8e2b407 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/multiple-text-string/input-multiple-text-string-item/input-multiple-text-string-item.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/multiple-text-string/input-multiple-text-string-item/input-multiple-text-string-item.element.ts @@ -4,8 +4,8 @@ import { customElement, property, query } from 'lit/decorators.js'; import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins'; import { UUIInputEvent } from '@umbraco-ui/uui-input'; import { UUIInputElement } from '@umbraco-ui/uui'; -import { UmbChangeEvent, UmbInputEvent, UmbDeleteEvent } from '@umbraco-cms/events'; import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '../../../../../../core/modal'; +import { UmbChangeEvent, UmbInputEvent, UmbDeleteEvent } from '@umbraco-cms/events'; import { UmbLitElement } from '@umbraco-cms/element'; /**