diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-actions/common/copy/property-action-copy.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-actions/common/copy/property-action-copy.element.ts index 37478f2940..f3b7011b46 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-actions/common/copy/property-action-copy.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-actions/common/copy/property-action-copy.element.ts @@ -5,6 +5,7 @@ import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN, } from '@umbraco-cms/backoffice/notification'; +import { UMB_WORKSPACE_PROPERTY_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/workspace'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; @customElement('umb-property-action-copy') @@ -17,9 +18,9 @@ export class UmbPropertyActionCopyElement extends UmbLitElement implements UmbPr constructor() { super(); - // TODO implement a property context - this.consumeContext('umbProperty', (property) => { - console.log('PROPERTY', property); + this.consumeContext(UMB_WORKSPACE_PROPERTY_CONTEXT_TOKEN, (property) => { + //console.log('Got a reference to the editor element', property.getEditor()); + // Be aware that the element might switch, so using the direct reference is not recommended, instead observe the .element Observable() }); this.consumeContext(UMB_NOTIFICATION_CONTEXT_TOKEN, (instance) => { 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 b8cd8f9ff4..c1e2a7f790 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,4 +1,6 @@ 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 { BehaviorSubject } from '@umbraco-cms/backoffice/external/rxjs'; import { UMB_WORKSPACE_VARIANT_CONTEXT_TOKEN, UMB_ENTITY_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import type { DataTypeResponseModel } from '@umbraco-cms/backoffice/backend-api'; @@ -37,6 +39,15 @@ export class UmbWorkspacePropertyContext { public readonly value = this._data.getObservablePart((data) => data.value); public readonly config = this._data.getObservablePart((data) => data.config); + private _editor = new BehaviorSubject(undefined); + public readonly editor = this._editor.asObservable(); + setEditor(editor: UmbPropertyEditorExtensionElement | undefined) { + this._editor.next(editor ?? undefined); + } + getEditor() { + return this._editor.getValue(); + } + #workspaceVariantId?: UmbVariantId; #variantId = new UmbClassState(undefined); 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 b616561453..7b809abfb5 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 @@ -165,6 +165,8 @@ export class UmbWorkspacePropertyElement extends UmbLitElement { } private _gotEditorUI(manifest?: ManifestPropertyEditorUi | null) { + this._propertyContext.setEditor(undefined); + if (!manifest) { // TODO: if propertyEditorUiAlias didn't exist in store, we should do some nice fail UI. return; @@ -178,6 +180,8 @@ export class UmbWorkspacePropertyElement extends UmbLitElement { this._element = el as ManifestPropertyEditorUi['ELEMENT_TYPE']; + this._propertyContext.setEditor(this._element); + this._valueObserver?.destroy(); this._configObserver?.destroy(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/structure/document-type-workspace-view-structure.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/structure/document-type-workspace-view-structure.element.ts index 5eaffe6831..c92d162035 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/structure/document-type-workspace-view-structure.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/structure/document-type-workspace-view-structure.element.ts @@ -34,9 +34,11 @@ export class UmbDocumentTypeWorkspaceViewStructureElement if (!this.#workspaceContext) return; this.observe(this.#workspaceContext.allowedAsRoot, (allowedAsRoot) => (this._allowedAsRoot = allowedAsRoot)); this.observe(this.#workspaceContext.allowedContentTypes, (allowedContentTypes) => { + const oldValue = this._allowedContentTypeIDs; this._allowedContentTypeIDs = allowedContentTypes ?.map((x) => x.id) .filter((x) => x !== undefined) as Array; + this.requestUpdate('_allowedContentTypeIDs', oldValue); }); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/templates/document-type-workspace-view-templates.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/templates/document-type-workspace-view-templates.element.ts index 89ba8e14cb..4292370aa8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/templates/document-type-workspace-view-templates.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/templates/document-type-workspace-view-templates.element.ts @@ -31,11 +31,22 @@ export class UmbDocumentTypeWorkspaceViewTemplatesElement if (!this.#workspaceContext) return; this.observe( this.#workspaceContext.defaultTemplateId, - (defaultTemplateId) => (this._defaultTemplateId = defaultTemplateId) + (defaultTemplateId) => { + const oldValue = this._defaultTemplateId; + this._defaultTemplateId = defaultTemplateId; + this.requestUpdate('_defaultTemplateId', oldValue); + }, + 'defaultTemplate' + ); + this.observe( + this.#workspaceContext.allowedTemplateIds, + (allowedTemplateIds) => { + const oldValue = this._allowedTemplateIds; + this._allowedTemplateIds = allowedTemplateIds; + this.requestUpdate('_allowedTemplateIds', oldValue); + }, + 'allowedTemplateIds' ); - this.observe(this.#workspaceContext.allowedTemplateIds, (allowedTemplateIds) => { - this._allowedTemplateIds = allowedTemplateIds; - }); } #templateInputChange(e: CustomEvent) { 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 a3fd61cfc9..6f4bb4830c 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 @@ -54,15 +54,7 @@ export class UmbDocumentWorkspaceContext new UmbObserverController(this.host, this.documentTypeKey, (id) => this.structure.loadType(id)); /* - TODO: Concept for ensure variant values: - new UmbObserverController(this.host, this.variants, (variants) => { - if (!variants) return; - const draft = this.#draft.getValue(); - if (!draft) return; - - // Gather all properties from all document types. - // Loop through all properties for each variant and insert missing value objects. - } + TODO: Make something to ensure all variants are present in data? Seems like a good idea?. */ } 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 fc55f86589..19ef22254f 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 @@ -116,9 +116,9 @@ export class UmbDocumentWorkspaceViewEditElement if (!this._routes || !this._tabs) return; return html` - ${this._routerPath && this._tabs.length > 1 + ${this._routerPath && (this._tabs.length > 0 || this._hasRootGroups) ? html` - ${this._hasRootGroups && this._tabs.length > 1 + ${this._hasRootGroups && this._tabs.length > 0 ? html` = []; - @property({ type: Array }) + _selectedIds: Array = []; + @property({ type: Array }) public get selectedIds() { return this._selectedIds; } - public set selectedIds(newKeys: Array) { + public set selectedIds(newKeys: Array) { this._selectedIds = newKeys; this.#observePickedTemplates(); } @@ -67,13 +67,14 @@ export class UmbInputTemplateElement extends FormControlMixin(UmbLitElement) { public set defaultId(newId: string) { this._defaultId = newId; super.value = newId; + this.#observePickedTemplates(); } private _modalContext?: UmbModalManagerContext; private _templateRepository: UmbTemplateRepository = new UmbTemplateRepository(this); @state() - _pickedTemplates: TemplateResponseModel[] = []; + _pickedTemplates: ItemResponseModelBaseModel[] = []; constructor() { super(); @@ -85,11 +86,13 @@ export class UmbInputTemplateElement extends FormControlMixin(UmbLitElement) { async #observePickedTemplates() { this.observe( - await this._templateRepository.itemsLegacy(this._selectedIds), + (await this._templateRepository.requestItems(this._selectedIds)).asObservable(), (data) => { + const oldValue = this._pickedTemplates; this._pickedTemplates = data; + this.requestUpdate('_pickedTemplates', oldValue); }, - '_templateRepositoryTreeItems' + '_observeTemplates' ); } @@ -109,11 +112,12 @@ export class UmbInputTemplateElement extends FormControlMixin(UmbLitElement) { const modalContext = this._modalContext?.open(UMB_TEMPLATE_PICKER_MODAL, { multiple: true, selection: [...this.selectedIds], + pickableFilter: (template: TemplateResponseModel) => template.id !== null, }); modalContext?.onSubmit().then((data) => { if (!data.selection) return; - this.selectedIds = data.selection; + this.selectedIds = data.selection.filter((x) => x !== null) as Array; this.dispatchEvent(new CustomEvent('change')); }); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/manifests.ts index 042cbce709..6ee22e480d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/manifests.ts @@ -1,7 +1,13 @@ import { UmbTemplateRepository } from '../repository/template.repository.js'; import { UmbTemplateTreeStore } from './template.tree.store.js'; import { UmbTemplateStore } from './template.store.js'; -import { ManifestStore, ManifestTreeStore, ManifestRepository } from '@umbraco-cms/backoffice/extension-registry'; +import { UmbTemplateItemStore } from './template-item.store.js'; +import { + ManifestStore, + ManifestTreeStore, + ManifestRepository, + ManifestItemStore, +} from '@umbraco-cms/backoffice/extension-registry'; export const TEMPLATE_REPOSITORY_ALIAS = 'Umb.Repository.Template'; @@ -14,6 +20,7 @@ const repository: ManifestRepository = { export const TEMPLATE_STORE_ALIAS = 'Umb.Store.Template'; export const TEMPLATE_TREE_STORE_ALIAS = 'Umb.Store.TemplateTree'; +export const TEMPLATE_ITEM_STORE_ALIAS = 'Umb.Store.TemplateItem'; const store: ManifestStore = { type: 'store', @@ -29,4 +36,11 @@ const treeStore: ManifestTreeStore = { class: UmbTemplateTreeStore, }; -export const manifests = [repository, store, treeStore]; +const itemStore: ManifestItemStore = { + type: 'itemStore', + alias: TEMPLATE_ITEM_STORE_ALIAS, + name: 'Template Item Store', + class: UmbTemplateItemStore, +}; + +export const manifests = [repository, store, treeStore, itemStore]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/sources/template.item.server.data.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/sources/template.item.server.data.ts new file mode 100644 index 0000000000..22ae84f8f0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/sources/template.item.server.data.ts @@ -0,0 +1,39 @@ +import type { UmbItemDataSource } from '@umbraco-cms/backoffice/repository'; +import { TemplateItemResponseModel, TemplateResource } from '@umbraco-cms/backoffice/backend-api'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; + +/** + * A data source for Data Type items that fetches data from the server + * @export + * @class UmbTemplateItemServerDataSource + * @implements {DocumentTreeDataSource} + */ +export class UmbTemplateItemServerDataSource implements UmbItemDataSource { + #host: UmbControllerHostElement; + + /** + * Creates an instance of UmbTemplateItemServerDataSource. + * @param {UmbControllerHostElement} host + * @memberof UmbTemplateItemServerDataSource + */ + constructor(host: UmbControllerHostElement) { + this.#host = host; + } + + /** + * Fetches the items for the given ids from the server + * @param {Array} ids + * @return {*} + * @memberof UmbTemplateItemServerDataSource + */ + async getItems(ids: Array) { + if (!ids) throw new Error('Ids are missing'); + return tryExecuteAndNotify( + this.#host, + TemplateResource.getTemplateItem({ + id: ids, + }) + ); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/template-item.store.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/template-item.store.ts new file mode 100644 index 0000000000..5eeac72c38 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/template-item.store.ts @@ -0,0 +1,36 @@ +import { TemplateItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { UmbItemStore, UmbStoreBase } from '@umbraco-cms/backoffice/store'; +import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; + +/** + * @export + * @class UmbTemplateItemStore + * @extends {UmbStoreBase} + * @description - Data Store for Template items + */ + +export class UmbTemplateItemStore + extends UmbStoreBase + implements UmbItemStore +{ + /** + * Creates an instance of UmbTemplateItemStore. + * @param {UmbControllerHostElement} host + * @memberof UmbTemplateItemStore + */ + constructor(host: UmbControllerHostElement) { + super( + host, + UMB_TEMPLATE_ITEM_STORE_CONTEXT_TOKEN.toString(), + new UmbArrayState([], (x) => x.id) + ); + } + + items(ids: Array) { + return this._data.getObservablePart((items) => items.filter((item) => ids.includes(item.id ?? ''))); + } +} + +export const UMB_TEMPLATE_ITEM_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbTemplateItemStore'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/template.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/template.repository.ts index dbf7a2212f..6b68cf7219 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/template.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/repository/template.repository.ts @@ -2,9 +2,12 @@ import { UmbTemplateTreeStore, UMB_TEMPLATE_TREE_STORE_CONTEXT_TOKEN } from './t import { UmbTemplateStore, UMB_TEMPLATE_STORE_CONTEXT_TOKEN } from './template.store.js'; import { UmbTemplateTreeServerDataSource } from './sources/template.tree.server.data.js'; import { UmbTemplateDetailServerDataSource } from './sources/template.detail.server.data.js'; +import { UMB_TEMPLATE_ITEM_STORE_CONTEXT_TOKEN, UmbTemplateItemStore } from './template-item.store.js'; +import { UmbTemplateItemServerDataSource } from './sources/template.item.server.data.js'; import { Observable } from '@umbraco-cms/backoffice/external/rxjs'; import type { UmbDetailRepository, + UmbItemDataSource, UmbItemRepository, UmbTreeDataSource, UmbTreeRepository, @@ -32,7 +35,9 @@ export class UmbTemplateRepository #treeDataSource: UmbTreeDataSource; #detailDataSource: UmbTemplateDetailServerDataSource; + #itemSource: UmbItemDataSource; + #itemStore?: UmbTemplateItemStore; #treeStore?: UmbTemplateTreeStore; #store?: UmbTemplateStore; @@ -43,8 +48,13 @@ export class UmbTemplateRepository this.#treeDataSource = new UmbTemplateTreeServerDataSource(this.#host); this.#detailDataSource = new UmbTemplateDetailServerDataSource(this.#host); + this.#itemSource = new UmbTemplateItemServerDataSource(this.#host); this.#init = Promise.all([ + new UmbContextConsumerController(this.#host, UMB_TEMPLATE_ITEM_STORE_CONTEXT_TOKEN, (instance) => { + this.#itemStore = instance; + }), + new UmbContextConsumerController(this.#host, UMB_TEMPLATE_TREE_STORE_CONTEXT_TOKEN, (instance) => { this.#treeStore = instance; }), @@ -147,17 +157,21 @@ export class UmbTemplateRepository this.#store?.append(data); } - return { data, error }; + return { data, error, asObservable: () => this.#treeStore!.items([id]) }; } - async requestItems(id: string[]) { + // ITEMS: + async requestItems(ids: Array) { + if (!ids) throw new Error('Ids are missing'); await this.#init; - if (!id) { - throw new Error('Id is missing'); + const { data, error } = await this.#itemSource.getItems(ids); + + if (data) { + this.#itemStore?.appendItems(data); } - const { data, error } = await this.#detailDataSource.getItem(id); - return { data, error }; + + return { data, error, asObservable: () => this.#itemStore!.items(ids) }; } async items(uniques: string[]): Promise> {