diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/modals/composition-picker/composition-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/modals/composition-picker/composition-picker-modal.element.ts index e48e97ee00..2ead4db13a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/modals/composition-picker/composition-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/modals/composition-picker/composition-picker-modal.element.ts @@ -3,83 +3,175 @@ import type { UmbCompositionPickerModalData, UmbCompositionPickerModalValue, } from './composition-picker-modal.token.js'; -import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement, state, repeat, nothing } from '@umbraco-cms/backoffice/external/lit'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; -import type { UmbTreeElement } from '@umbraco-cms/backoffice/tree'; +import type { + UmbDocumentTypeCompositionCompatibleModel, + UmbDocumentTypeCompositionReferenceModel, +} from '@umbraco-cms/backoffice/document-type'; + +interface CompatibleCompositions { + path: string; + compositions: Array; +} @customElement('umb-composition-picker-modal') export class UmbCompositionPickerModalElement extends UmbModalBaseElement< UmbCompositionPickerModalData, UmbCompositionPickerModalValue > { - // TODO: We need to make a filter for the tree that hides the items that can't be used for composition. - // From what I've observed in old BO: Document types that inherit from other document types cannot be picked as a composition. (document types that are made under other document types) - // As well as other document types that has a composition already. - // OBS: If a document type 1 has a composition document type 2, document type 2 cannot add any compositions. - #compositionRepository = new UmbDocumentTypeCompositionRepository(this); + #unique?: string; @state() - private _selectionConfiguration = { - multiple: true, - selectable: true, - selection: [], - }; + private _references: Array = []; - constructor() { - super(); - } + @state() + private _compatibleCompositions?: Array; + + @state() + private _selection: Array = []; connectedCallback() { super.connectedCallback(); - this._selectionConfiguration = { ...this._selectionConfiguration, selection: (this.data?.selection as []) ?? [] }; - this.#getcomposition() + + this._selection = this.data?.selection ?? []; + this.modalContext?.setValue({ selection: this._selection }); + + this.#requestReference(); } - async #getcomposition() { - const unique = this.data?.unique; + async #requestReference() { + this.#unique = this.data?.unique; + if (!this.#unique) return; - if (!unique) throw new Error('Unique is required'); + const { data } = await this.#compositionRepository.getReferences(this.#unique); - const something = await this.#compositionRepository.getReferences(unique); - console.log(something) + this._references = data ?? []; + if (!this._references.length) { + this.#requestAvailableCompositions(); + } } - #onSelectionChange(e: CustomEvent) { - const values = (e.target as UmbTreeElement).getSelection() ?? []; - this.value = { selection: values as string[] }; - this._selectionConfiguration = { ...this._selectionConfiguration, selection: values as [] }; + async #requestAvailableCompositions() { + if (!this.#unique) return; + + const isElement = this.data?.isElement; + const currentPropertyAliases = this.data?.currentPropertyAliases; + + const { data } = await this.#compositionRepository.availableCompositions({ + unique: this.#unique, + isElement: isElement ?? false, + currentCompositeUniques: this._selection, + currentPropertyAliases: currentPropertyAliases ?? [], + }); + + if (!data) return; + + const folders = Array.from(new Set(data.map((c) => '/' + c.folderPath.join('/')))); + this._compatibleCompositions = folders.map((path) => ({ + path, + compositions: data.filter((c) => '/' + c.folderPath.join('/') === path), + })); } render() { return html` - - Inherit tabs and properties from an existing Document Type. New tabs will be
added to the current - Document Type or merged if a tab with an identical name exists.
-
- - - - item.unique !== this.data?.unique}> + ${this._references.length ? this.#renderHasReference() : this.#renderAvailableCompositions()}
- + ${!this._references.length + ? html`` + : nothing}
`; } + #renderHasReference() { + return html` + This Content Type is used in a composition, and therefore cannot be composed itself. + +

+ Where is this composition used? +

+ + This composition is currently used in the composition of the following Content Types: + +
+ ${repeat( + this._references, + (item) => item.unique, + (item) => + html` + + `, + )} +
`; + } + + #renderAvailableCompositions() { + if (this._compatibleCompositions) { + return html` + Inherit tabs and properties from an existing Document Type. New tabs will be
added to the current + Document Type or merged if a tab with an identical name exists.
+
+ +
+ ${repeat( + this._compatibleCompositions, + (folder) => folder.path, + (folder) => + html`${this._compatibleCompositions!.length > 1 + ? html`${folder.path}` + : nothing} + ${this.#renderCompositionsItems(folder.compositions)}`, + )} +
`; + } else { + return html` + There are no Content Types available to use as a composition + `; + } + } + + #onSelectionAdd(unique: string) { + this._selection = [...this._selection, unique]; + this.modalContext?.setValue({ selection: this._selection }); + } + + #onSelectionRemove(unique: string) { + this._selection = this._selection.filter((s) => s !== unique); + this.modalContext?.setValue({ selection: this._selection }); + } + + #renderCompositionsItems(compositionsList: Array) { + return repeat( + compositionsList, + (compositions) => compositions.unique, + (compositions) => + html` this.#onSelectionAdd(compositions.unique)} + @deselected=${() => this.#onSelectionRemove(compositions.unique)} + ?selected=${this._selection.find((unique) => unique === compositions.unique)}> + + `, + ); + } + static styles = [ css` uui-input { @@ -87,9 +179,22 @@ export class UmbCompositionPickerModalElement extends UmbModalBaseElement< display: flex; align-items: center; } - uui-icon { + + #search uui-icon { padding-left: var(--uui-size-3); } + + .reference-list { + margin-block: var(--uui-size-3); + display: grid; + gap: var(--uui-size-1); + } + + .compositions-list strong { + display: flex; + align-items: center; + gap: var(--uui-size-3); + } `, ]; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/modals/composition-picker/composition-picker-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/modals/composition-picker/composition-picker-modal.token.ts index d884f91971..7a006e6642 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/modals/composition-picker/composition-picker-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/modals/composition-picker/composition-picker-modal.token.ts @@ -1,8 +1,11 @@ import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; export interface UmbCompositionPickerModalData { - unique: string; selection: Array; + unique: string; + //Do we really need to send this to the server - Why isn't unique enough? + isElement: boolean; + currentPropertyAliases: Array; } export interface UmbCompositionPickerModalValue { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/composition/document-type-composition.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/composition/document-type-composition.repository.ts index 59ca027b36..1901072d48 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/composition/document-type-composition.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/composition/document-type-composition.repository.ts @@ -1,5 +1,6 @@ import { UmbDocumentTypeCompositionServerDataSource } from './document-type-composition.server.data-source.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbDocumentTypeCompositionRequestModel } from '@umbraco-cms/backoffice/document-type'; import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository'; export class UmbDocumentTypeCompositionRepository extends UmbRepositoryBase { @@ -10,11 +11,11 @@ export class UmbDocumentTypeCompositionRepository extends UmbRepositoryBase { this.#compositionSource = new UmbDocumentTypeCompositionServerDataSource(this); } - async getReferences(unique:string) { + async getReferences(unique: string) { return this.#compositionSource.getReferences(unique); } - async update(args: any) { - return this.#compositionSource.update(args); + async availableCompositions(args: UmbDocumentTypeCompositionRequestModel) { + return this.#compositionSource.availableCompositions(args); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/composition/document-type-composition.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/composition/document-type-composition.server.data-source.ts index 81f623da40..d311304085 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/composition/document-type-composition.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/composition/document-type-composition.server.data-source.ts @@ -1,3 +1,8 @@ +import type { + UmbDocumentTypeCompositionCompatibleModel, + UmbDocumentTypeCompositionReferenceModel, + UmbDocumentTypeCompositionRequestModel, +} from '../../types.js'; import { type DocumentTypeCompositionRequestModel, DocumentTypeResource } from '@umbraco-cms/backoffice/backend-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; @@ -25,7 +30,20 @@ export class UmbDocumentTypeCompositionServerDataSource { * @memberof UmbDocumentTypeCompositionServerDataSource */ async getReferences(unique: string) { - return tryExecuteAndNotify(this.#host, DocumentTypeResource.getDocumentTypeByIdCompositionReferences({id:unique})); + const response = await tryExecuteAndNotify( + this.#host, + DocumentTypeResource.getDocumentTypeByIdCompositionReferences({ id: unique }), + ); + const error = response.error; + const data: Array | undefined = response.data?.map((reference) => { + return { + unique: reference.id, + icon: reference.icon, + name: reference.name, + }; + }); + + return { data, error }; } /** * Updates the compositions for a document type on the server @@ -33,13 +51,29 @@ export class UmbDocumentTypeCompositionServerDataSource { * @return {*} * @memberof UmbDocumentTypeCompositionServerDataSource */ - async update(data: any) { + async availableCompositions(args: UmbDocumentTypeCompositionRequestModel) { const requestBody: DocumentTypeCompositionRequestModel = { - id: '', - isElement: false, - currentCompositeIds: [], - currentPropertyAliases: [], - } - return tryExecuteAndNotify(this.#host, DocumentTypeResource.postDocumentTypeAvailableCompositions({ requestBody })); + id: args.unique, + isElement: args.isElement, + currentCompositeIds: args.currentCompositeUniques, + currentPropertyAliases: args.currentPropertyAliases, + }; + + const response = await tryExecuteAndNotify( + this.#host, + DocumentTypeResource.postDocumentTypeAvailableCompositions({ requestBody }), + ); + const error = response.error; + const data: Array | undefined = response.data?.map((composition) => { + return { + unique: composition.id, + name: composition.name, + icon: composition.icon, + folderPath: composition.folderPath, + isCompatible: composition.isCompatible, + }; + }); + + return { data, error }; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts index 7042af6b84..f09417720b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/detail/document-type-detail.server.data-source.ts @@ -143,6 +143,7 @@ export class UmbDocumentTypeDetailServerDataSource implements UmbDetailDataSourc // TODO: make data mapper to prevent errors const requestBody: CreateDocumentTypeRequestModel = { + folder: model.parentUnique ? { id: model.parentUnique } : null, alias: model.alias, name: model.name, description: model.description, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/types.ts index b680a5c8ff..2c8898bba3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/types.ts @@ -8,3 +8,25 @@ export interface UmbDocumentTypeDetailModel extends UmbContentTypeModel { defaultTemplate: { id: string } | null; cleanup: ContentTypeCleanupBaseModel; } + +export interface UmbDocumentTypeCompositionRequestModel { + unique: string; + //Do we really need to send this to the server - Why isn't unique enough? + isElement: boolean; + currentPropertyAliases: Array; + currentCompositeUniques: Array; +} + +export interface UmbDocumentTypeCompositionCompatibleModel { + unique: string; + name: string; + icon: string; + folderPath: Array; + isCompatible: boolean; +} + +export interface UmbDocumentTypeCompositionReferenceModel { + unique: string; + name: string; + icon: string; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit.element.ts index 06cee2b153..956461f1a1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit.element.ts @@ -122,10 +122,13 @@ export class UmbDocumentTypeWorkspaceViewEditElement extends UmbLitElement imple const unique = this._workspaceContext.getEntityId(); + //TODO Figure out the correct data that needs to be sent to the compositions modal. Do we really have to send isElement, currentPropertyAliases - isn't unique enough? this.observe(this._workspaceContext.structure.contentTypes, (contentTypes) => { this._compositionConfiguration = { unique: unique ?? '', selection: contentTypes.map((contentType) => contentType.unique).filter((id) => id !== unique), + isElement: contentTypes.find((contentType) => contentType.unique === unique)?.isElement ?? false, + currentPropertyAliases: [], }; }); @@ -286,13 +289,11 @@ export class UmbDocumentTypeWorkspaceViewEditElement extends UmbLitElement imple } async #openCompositionModal() { - const modalContext = this._modalManagerContext?.open(UMB_COMPOSITION_PICKER_MODAL, { data: this._compositionConfiguration, }); await modalContext?.onSubmit(); - if (!modalContext?.value) return; const compositionIds = modalContext.getValue().selection;