render tree
This commit is contained in:
@@ -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<UmbDocumentTypeCompositionCompatibleModel>;
|
||||
}
|
||||
|
||||
@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<UmbDocumentTypeCompositionReferenceModel> = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
@state()
|
||||
private _compatibleCompositions?: Array<CompatibleCompositions>;
|
||||
|
||||
@state()
|
||||
private _selection: Array<string> = [];
|
||||
|
||||
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`
|
||||
<umb-body-layout headline="${this.localize.term('contentTypeEditor_compositions')}">
|
||||
<umb-localize key="contentTypeEditor_compositionsDescription">
|
||||
Inherit tabs and properties from an existing Document Type. New tabs will be<br />added to the current
|
||||
Document Type or merged if a tab with an identical name exists.<br />
|
||||
</umb-localize>
|
||||
<uui-input id="search" placeholder=${this.localize.term('placeholders_filter')}>
|
||||
<uui-icon name="icon-search" slot="prepend"></uui-icon>
|
||||
</uui-input>
|
||||
<umb-tree
|
||||
hide-tree-root
|
||||
alias="Umb.Tree.DocumentType"
|
||||
@selection-change=${this.#onSelectionChange}
|
||||
.selectionConfiguration=${this._selectionConfiguration}
|
||||
.selectableFilter=${(item: any) => item.unique !== this.data?.unique}></umb-tree>
|
||||
${this._references.length ? this.#renderHasReference() : this.#renderAvailableCompositions()}
|
||||
<div slot="actions">
|
||||
<uui-button label=${this.localize.term('general_close')} @click=${this._rejectModal}></uui-button>
|
||||
<uui-button
|
||||
label=${this.localize.term('general_submit')}
|
||||
look="primary"
|
||||
color="positive"
|
||||
@click=${this._submitModal}></uui-button>
|
||||
${!this._references.length
|
||||
? html`<uui-button
|
||||
label=${this.localize.term('general_submit')}
|
||||
look="primary"
|
||||
color="positive"
|
||||
@click=${this._submitModal}></uui-button>`
|
||||
: nothing}
|
||||
</div>
|
||||
</umb-body-layout>
|
||||
`;
|
||||
}
|
||||
|
||||
#renderHasReference() {
|
||||
return html` <umb-localize key="contentTypeEditor_compositionInUse">
|
||||
This Content Type is used in a composition, and therefore cannot be composed itself.
|
||||
</umb-localize>
|
||||
<h4>
|
||||
<umb-localize key="contentTypeEditor_compositionUsageHeading">Where is this composition used?</umb-localize>
|
||||
</h4>
|
||||
<umb-localize key="contentTypeEditor_compositionUsageSpecification">
|
||||
This composition is currently used in the composition of the following Content Types:
|
||||
</umb-localize>
|
||||
<div class="reference-list">
|
||||
${repeat(
|
||||
this._references,
|
||||
(item) => item.unique,
|
||||
(item) =>
|
||||
html`<uui-ref-node-document-type
|
||||
href=${'/section/settings/workspace/document-type/edit/' + item.unique}
|
||||
name=${item.name}>
|
||||
<uui-icon slot="icon" name=${item.icon}></uui-icon>
|
||||
</uui-ref-node-document-type>`,
|
||||
)}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
#renderAvailableCompositions() {
|
||||
if (this._compatibleCompositions) {
|
||||
return html`<umb-localize key="contentTypeEditor_compositionsDescription">
|
||||
Inherit tabs and properties from an existing Document Type. New tabs will be<br />added to the current
|
||||
Document Type or merged if a tab with an identical name exists.<br />
|
||||
</umb-localize>
|
||||
<!--- TODO: Implement search functionality
|
||||
<uui-input id="search" placeholder=this.localize.term('placeholders_filter')>
|
||||
<uui-icon name="icon-search" slot="prepend"></uui-icon>
|
||||
</uui-input> -->
|
||||
<div class="compositions-list">
|
||||
${repeat(
|
||||
this._compatibleCompositions,
|
||||
(folder) => folder.path,
|
||||
(folder) =>
|
||||
html`${this._compatibleCompositions!.length > 1
|
||||
? html`<strong><uui-icon name="icon-folder"></uui-icon>${folder.path}</strong>`
|
||||
: nothing}
|
||||
${this.#renderCompositionsItems(folder.compositions)}`,
|
||||
)}
|
||||
</div>`;
|
||||
} else {
|
||||
return html`<umb-localize key="contentTypeEditor_noAvailableCompositions">
|
||||
There are no Content Types available to use as a composition
|
||||
</umb-localize>`;
|
||||
}
|
||||
}
|
||||
|
||||
#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<UmbDocumentTypeCompositionCompatibleModel>) {
|
||||
return repeat(
|
||||
compositionsList,
|
||||
(compositions) => compositions.unique,
|
||||
(compositions) =>
|
||||
html`<uui-menu-item
|
||||
label=${compositions.name}
|
||||
selectable
|
||||
@selected=${() => this.#onSelectionAdd(compositions.unique)}
|
||||
@deselected=${() => this.#onSelectionRemove(compositions.unique)}
|
||||
?selected=${this._selection.find((unique) => unique === compositions.unique)}>
|
||||
<uui-icon name=${compositions.icon} slot="icon"></uui-icon>
|
||||
</uui-menu-item>`,
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
export interface UmbCompositionPickerModalData {
|
||||
unique: string;
|
||||
selection: Array<string>;
|
||||
unique: string;
|
||||
//Do we really need to send this to the server - Why isn't unique enough?
|
||||
isElement: boolean;
|
||||
currentPropertyAliases: Array<string>;
|
||||
}
|
||||
|
||||
export interface UmbCompositionPickerModalValue {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<UmbDocumentTypeCompositionReferenceModel> | 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<UmbDocumentTypeCompositionCompatibleModel> | undefined = response.data?.map((composition) => {
|
||||
return {
|
||||
unique: composition.id,
|
||||
name: composition.name,
|
||||
icon: composition.icon,
|
||||
folderPath: composition.folderPath,
|
||||
isCompatible: composition.isCompatible,
|
||||
};
|
||||
});
|
||||
|
||||
return { data, error };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<string>;
|
||||
currentCompositeUniques: Array<string>;
|
||||
}
|
||||
|
||||
export interface UmbDocumentTypeCompositionCompatibleModel {
|
||||
unique: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
folderPath: Array<string>;
|
||||
isCompatible: boolean;
|
||||
}
|
||||
|
||||
export interface UmbDocumentTypeCompositionReferenceModel {
|
||||
unique: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user