structure manager refactor implementation (#19970)
* todos * navigation context * replace raw manifests with view context * Array State has method * rename to hint and much more * Notes for later * correcting one word * more notes * update JS Docs * update tests for getHasOne * fix context api usage * update code for v.16 * correct test * export UMB_WORKSPACE_VIEW_CONTEXT * minor corrections * rename to _hintMap * refactor part 1 * update version number in comment * clear method for array states * declare hint import map * mega refactor * final corrections for working POC * clean up path logic * implement scaffold * propagation and inheritance from view to workspace * separate types from classes * refactor to view context * rename editor navigation context to editor context * propagate removals * clean up notes * Hints for Content Tabs * use const path * handle gone parent * added comments on something to be looked at * hints context types * contentTypeMergedContainers * lint fixes * public contentTypeMergedContainers * refactor property structure helper class * a few notes for Presets * set variant ID instead of parsing it to the constructor * do not inject root to the path * adjust structure manager logic * UmbPropertyTypeContainerMergedModel type update * correct mergedContainersOfParentIdAndType * refactor to utilize new observable for better outcome and performance * fix lint errors * fix missing import * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/Umbraco.Web.UI.Client/src/packages/core/hint/context/hints.controller.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-validation-to-hints.manager.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-validation-to-hints.manager.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * clean up * remove console.log * declare new exports of core * Update src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/views/design/content-type-design-editor-tab.element.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * clean up * fix const export * remove root from hints path * also check for invariant * name more as legacy * fix eslint * fix container id setting * fix resetting inherited property * fix re-rendering problem --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
This commit is contained in:
@@ -4,7 +4,7 @@ import { css, html, customElement, state, repeat, nothing } from '@umbraco-cms/b
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UmbContentTypeContainerStructureHelper } from '@umbraco-cms/backoffice/content-type';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import type { UmbPropertyTypeContainerModel } from '@umbraco-cms/backoffice/content-type';
|
||||
import type { UmbPropertyTypeContainerMergedModel } from '@umbraco-cms/backoffice/content-type';
|
||||
import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/workspace';
|
||||
|
||||
/**
|
||||
@@ -20,10 +20,10 @@ export class UmbBlockWorkspaceViewEditContentNoRouterElement extends UmbLitEleme
|
||||
private _hasRootGroups = false;
|
||||
|
||||
@state()
|
||||
private _tabs?: Array<UmbPropertyTypeContainerModel>;
|
||||
private _tabs?: Array<UmbPropertyTypeContainerMergedModel>;
|
||||
|
||||
@state()
|
||||
private _activeTabId?: string | null | undefined;
|
||||
private _activeTabKey?: string | null | undefined;
|
||||
|
||||
//@state()
|
||||
//private _activeTabName?: string | null | undefined;
|
||||
@@ -36,7 +36,7 @@ export class UmbBlockWorkspaceViewEditContentNoRouterElement extends UmbLitEleme
|
||||
|
||||
this.#tabsStructureHelper.setIsRoot(true);
|
||||
this.#tabsStructureHelper.setContainerChildType('Tab');
|
||||
this.observe(this.#tabsStructureHelper.mergedContainers, (tabs) => {
|
||||
this.observe(this.#tabsStructureHelper.childContainers, (tabs) => {
|
||||
this._tabs = tabs;
|
||||
this.#checkDefaultTabName();
|
||||
});
|
||||
@@ -68,20 +68,20 @@ export class UmbBlockWorkspaceViewEditContentNoRouterElement extends UmbLitEleme
|
||||
if (!this._tabs || !this.#blockWorkspace) return;
|
||||
|
||||
// Find the default tab to grab:
|
||||
if (this._activeTabId === undefined) {
|
||||
if (this._activeTabKey === undefined) {
|
||||
if (this._hasRootGroups) {
|
||||
//this._activeTabName = null;
|
||||
this._activeTabId = null;
|
||||
this._activeTabKey = null;
|
||||
} else if (this._tabs.length > 0) {
|
||||
//this._activeTabName = this._tabs[0].name;
|
||||
this._activeTabId = this._tabs[0].id;
|
||||
this._activeTabKey = this._tabs[0].key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#setTabName(tabName: string | undefined | null, tabId: string | null | undefined) {
|
||||
#setTabName(tabName: string | undefined | null, tabKey: string | null | undefined) {
|
||||
//this._activeTabName = tabName;
|
||||
this._activeTabId = tabId;
|
||||
this._activeTabKey = tabKey;
|
||||
}
|
||||
|
||||
override render() {
|
||||
@@ -93,7 +93,7 @@ export class UmbBlockWorkspaceViewEditContentNoRouterElement extends UmbLitEleme
|
||||
? html`
|
||||
<uui-tab
|
||||
label="Content"
|
||||
.active=${null === this._activeTabId}
|
||||
.active=${null === this._activeTabKey}
|
||||
@click=${() => this.#setTabName(null, null)}
|
||||
>Content</uui-tab
|
||||
>
|
||||
@@ -105,19 +105,19 @@ export class UmbBlockWorkspaceViewEditContentNoRouterElement extends UmbLitEleme
|
||||
(tab) => {
|
||||
return html`<uui-tab
|
||||
label=${tab.name ?? 'Unnamed'}
|
||||
.active=${tab.id === this._activeTabId}
|
||||
@click=${() => this.#setTabName(tab.name, tab.id)}
|
||||
.active=${tab.key === this._activeTabKey}
|
||||
@click=${() => this.#setTabName(tab.name, tab.key)}
|
||||
>${tab.name}</uui-tab
|
||||
>`;
|
||||
},
|
||||
)}
|
||||
</uui-tab-group>`
|
||||
: nothing}
|
||||
${this._activeTabId !== undefined
|
||||
${this._activeTabKey !== undefined
|
||||
? html`<umb-block-workspace-view-edit-tab
|
||||
.managerName=${'content'}
|
||||
.hideSingleGroup=${true}
|
||||
.containerId=${this._activeTabId}>
|
||||
.containerId=${this._activeTabKey}>
|
||||
</umb-block-workspace-view-edit-tab>`
|
||||
: nothing}
|
||||
`;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { UMB_BLOCK_WORKSPACE_CONTEXT } from '../../block-workspace.context-token.js';
|
||||
import { css, html, customElement, property, state, repeat } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type { UmbContentTypeModel, UmbPropertyTypeContainerModel } from '@umbraco-cms/backoffice/content-type';
|
||||
import type { UmbContentTypeModel, UmbPropertyTypeContainerMergedModel } from '@umbraco-cms/backoffice/content-type';
|
||||
import { UmbContentTypeContainerStructureHelper } from '@umbraco-cms/backoffice/content-type';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
|
||||
@@ -42,7 +42,7 @@ export class UmbBlockWorkspaceViewEditTabElement extends UmbLitElement {
|
||||
hideSingleGroup = false;
|
||||
|
||||
@state()
|
||||
private _groups: Array<UmbPropertyTypeContainerModel> = [];
|
||||
private _groups: Array<UmbPropertyTypeContainerMergedModel> = [];
|
||||
|
||||
@state()
|
||||
private _hasProperties = false;
|
||||
@@ -60,7 +60,7 @@ export class UmbBlockWorkspaceViewEditTabElement extends UmbLitElement {
|
||||
if (!this.#blockWorkspace || !this.#managerName) return;
|
||||
this.#groupStructureHelper.setStructureManager(this.#blockWorkspace[this.#managerName].structure);
|
||||
this.observe(
|
||||
this.#groupStructureHelper.mergedContainers,
|
||||
this.#groupStructureHelper.childContainers,
|
||||
(groups) => {
|
||||
this._groups = groups;
|
||||
},
|
||||
@@ -89,18 +89,18 @@ export class UmbBlockWorkspaceViewEditTabElement extends UmbLitElement {
|
||||
? this.renderGroup(this._groups[0])
|
||||
: repeat(
|
||||
this._groups,
|
||||
(group) => group.id,
|
||||
(group) => group.key,
|
||||
(group) => html` <uui-box .headline=${group.name}>${this.renderGroup(group)}</uui-box>`,
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
renderGroup(group: UmbPropertyTypeContainerModel) {
|
||||
renderGroup(group: UmbPropertyTypeContainerMergedModel) {
|
||||
return html`
|
||||
<umb-block-workspace-view-edit-properties
|
||||
.managerName=${this.#managerName}
|
||||
data-mark="property-group:${group.name}"
|
||||
.containerId=${group.id}></umb-block-workspace-view-edit-properties>
|
||||
.containerId=${group.ids[0]}></umb-block-workspace-view-edit-properties>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { UMB_BLOCK_WORKSPACE_CONTEXT } from '../../block-workspace.context-token
|
||||
import type { UmbBlockWorkspaceViewEditTabElement } from './block-workspace-view-edit-tab.element.js';
|
||||
import { css, html, customElement, state, repeat, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type { UmbContentTypeModel, UmbPropertyTypeContainerModel } from '@umbraco-cms/backoffice/content-type';
|
||||
import type { UmbContentTypeModel, UmbPropertyTypeContainerMergedModel } from '@umbraco-cms/backoffice/content-type';
|
||||
import { UmbContentTypeContainerStructureHelper } from '@umbraco-cms/backoffice/content-type';
|
||||
import type { UmbRoute, UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router';
|
||||
import { encodeFolderName } from '@umbraco-cms/backoffice/router';
|
||||
@@ -34,7 +34,7 @@ export class UmbBlockWorkspaceViewEditElement extends UmbLitElement implements U
|
||||
private _routes: UmbRoute[] = [];
|
||||
|
||||
@state()
|
||||
private _tabs?: Array<UmbPropertyTypeContainerModel>;
|
||||
private _tabs?: Array<UmbPropertyTypeContainerMergedModel>;
|
||||
|
||||
@state()
|
||||
private _routerPath?: string;
|
||||
@@ -48,7 +48,7 @@ export class UmbBlockWorkspaceViewEditElement extends UmbLitElement implements U
|
||||
this.#tabsStructureHelper.setIsRoot(true);
|
||||
this.#tabsStructureHelper.setContainerChildType('Tab');
|
||||
this.observe(
|
||||
this.#tabsStructureHelper.mergedContainers,
|
||||
this.#tabsStructureHelper.childContainers,
|
||||
(tabs) => {
|
||||
this._tabs = tabs;
|
||||
this.#createRoutes();
|
||||
@@ -102,7 +102,7 @@ export class UmbBlockWorkspaceViewEditElement extends UmbLitElement implements U
|
||||
component: () => import('./block-workspace-view-edit-tab.element.js'),
|
||||
setup: (component) => {
|
||||
(component as UmbBlockWorkspaceViewEditTabElement).managerName = this.#managerName;
|
||||
(component as UmbBlockWorkspaceViewEditTabElement).containerId = tab.id;
|
||||
(component as UmbBlockWorkspaceViewEditTabElement).containerId = tab.ids[0];
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import type { UmbContentTypeModel, UmbPropertyContainerTypes, UmbPropertyTypeContainerModel } from '../types.js';
|
||||
import type {
|
||||
UmbContentTypeModel,
|
||||
UmbPropertyContainerTypes,
|
||||
UmbPropertyTypeContainerMergedModel,
|
||||
UmbPropertyTypeContainerModel,
|
||||
} from '../types.js';
|
||||
import type { UmbContentTypeStructureManager } from './content-type-structure-manager.class.js';
|
||||
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import type { UmbController, UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { UmbArrayState, UmbBooleanState } from '@umbraco-cms/backoffice/observable-api';
|
||||
|
||||
/**
|
||||
* This class is a helper class for managing the structure of containers in a content type.
|
||||
@@ -11,6 +16,7 @@ import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
|
||||
export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeModel> extends UmbControllerBase {
|
||||
#init;
|
||||
#initResolver?: (value: unknown) => void;
|
||||
#initRejector?: () => void;
|
||||
|
||||
#containerId?: string | null;
|
||||
#childType?: UmbPropertyContainerTypes = 'Group';
|
||||
@@ -21,31 +27,55 @@ export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeMode
|
||||
|
||||
// State containing the all containers defined in the data:
|
||||
#childContainers = new UmbArrayState<UmbPropertyTypeContainerModel>([], (x) => x.id);
|
||||
readonly containers = this.#childContainers.asObservable();
|
||||
get containers() {
|
||||
this.#startLegacy();
|
||||
return this.#childContainers.asObservable();
|
||||
}
|
||||
|
||||
// State containing the merged containers (only one pr. name):
|
||||
#mergedChildContainers = new UmbArrayState<UmbPropertyTypeContainerModel>([], (x) => x.id);
|
||||
readonly mergedContainers = this.#mergedChildContainers.asObservable();
|
||||
#legacyMergedChildContainers = new UmbArrayState<UmbPropertyTypeContainerModel>([], (x) => x.id);
|
||||
get mergedContainers() {
|
||||
this.#startLegacy();
|
||||
return this.#legacyMergedChildContainers.asObservable();
|
||||
}
|
||||
|
||||
#childContainersMerged = new UmbArrayState<UmbPropertyTypeContainerMergedModel>([], (x) => x.path);
|
||||
public readonly childContainers = this.#childContainersMerged.asObservable();
|
||||
|
||||
// Owner containers are containers owned by the owner Content Type (The specific one up for editing)
|
||||
#ownerChildContainers: UmbPropertyTypeContainerModel[] = [];
|
||||
|
||||
#hasProperties = new UmbArrayState<{ id: string | null; has: boolean }>([], (x) => x.id);
|
||||
readonly hasProperties = this.#hasProperties.asObservablePart((x) => x.some((y) => y.has));
|
||||
#hasProperties = new UmbBooleanState(false);
|
||||
readonly hasProperties = this.#hasProperties.asObservable();
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host);
|
||||
this.#init = new Promise((resolve) => {
|
||||
this.#init = new Promise((resolve, reject) => {
|
||||
this.#initResolver = resolve;
|
||||
this.#initRejector = reject;
|
||||
});
|
||||
|
||||
this.#mergedChildContainers.sortBy((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0));
|
||||
this.observe(this.containers, this.#performContainerMerge, null);
|
||||
this.#legacyMergedChildContainers.sortBy((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0));
|
||||
}
|
||||
|
||||
// TODO: Implement UmbDeprecated and Obsolete this from v.17 [NL]
|
||||
#legacyMergeLogic = false;
|
||||
#startLegacy() {
|
||||
if (this.#legacyMergeLogic) return;
|
||||
console.log(
|
||||
"Pst. we will be deprecating 'mergedContainers' and 'containers' in v.17.0, feel free to use them until v.18.0. But please use 'childContainers'",
|
||||
);
|
||||
this.#legacyMergeLogic = true;
|
||||
this.#legacyObserveContainers();
|
||||
this.observe(this.containers, this.#legacyPerformContainerMerge, null);
|
||||
}
|
||||
|
||||
public setStructureManager(structure: UmbContentTypeStructureManager<T> | undefined) {
|
||||
if (this.#structure === structure || !structure) return;
|
||||
if (this.#structure && !structure) {
|
||||
this.#initRejector?.();
|
||||
this.#initResolver = undefined;
|
||||
this.#initRejector = undefined;
|
||||
throw new Error(
|
||||
'Structure manager is already set, the helpers are not designed to be re-setup with new managers',
|
||||
);
|
||||
@@ -53,7 +83,9 @@ export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeMode
|
||||
this.#structure = structure;
|
||||
this.#initResolver?.(undefined);
|
||||
this.#initResolver = undefined;
|
||||
this.#observeContainers();
|
||||
this.#initRejector = undefined;
|
||||
this.#observeContainer();
|
||||
this.#legacyObserveContainers();
|
||||
}
|
||||
|
||||
public getStructureManager() {
|
||||
@@ -72,7 +104,8 @@ export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeMode
|
||||
public setContainerId(value: string | null | undefined) {
|
||||
if (this.#containerId === value) return;
|
||||
this.#containerId = value;
|
||||
this.#observeContainers();
|
||||
this.#observeContainer();
|
||||
this.#legacyObserveContainers();
|
||||
}
|
||||
public getContainerId() {
|
||||
return this.#containerId;
|
||||
@@ -81,23 +114,75 @@ export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeMode
|
||||
public setContainerChildType(value?: UmbPropertyContainerTypes) {
|
||||
if (this.#childType === value) return;
|
||||
this.#childType = value;
|
||||
this.#observeContainers();
|
||||
this.#observeContainer();
|
||||
this.#legacyObserveContainers();
|
||||
}
|
||||
public getContainerChildType() {
|
||||
return this.#childType;
|
||||
}
|
||||
|
||||
#observeContainer() {
|
||||
// Determine the observable, leaving it undefined if not ready, in this way the observation will be determined if not everything is ready. [NL]
|
||||
const childObservable =
|
||||
this.#containerId !== undefined && this.#childType
|
||||
? this.#structure?.mergedContainersOfParentIdAndType(this.#containerId, this.#childType)
|
||||
: undefined;
|
||||
|
||||
this.observe(
|
||||
childObservable,
|
||||
(childContainers) => {
|
||||
this.#childContainersMerged.setValue(childContainers ?? []);
|
||||
},
|
||||
'observeChildContainers',
|
||||
);
|
||||
|
||||
if (this.#containerId === null) {
|
||||
this.removeUmbControllerByAlias('observeParentContainer');
|
||||
// Observe root properties:
|
||||
this.observe(
|
||||
this.#structure?.hasPropertyStructuresOfRoot(),
|
||||
(has) => {
|
||||
this.#hasProperties.setValue(has ?? false);
|
||||
},
|
||||
'observeProperties',
|
||||
);
|
||||
} else {
|
||||
// Observe properties of the parent container and matching containers (therefor getting the merged container of the parent id): [NL]
|
||||
const parentObservable =
|
||||
this.#containerId !== undefined && this.#childType
|
||||
? this.#structure?.mergedContainersOfId(this.#containerId)
|
||||
: undefined;
|
||||
|
||||
this.observe(
|
||||
parentObservable,
|
||||
(parentContainer) => {
|
||||
this.observe(
|
||||
parentContainer ? this.#structure?.hasPropertyStructuresOfGroupIds(parentContainer.ids ?? []) : undefined,
|
||||
(has) => {
|
||||
this.#hasProperties.setValue(has ?? false);
|
||||
},
|
||||
'observeProperties',
|
||||
);
|
||||
},
|
||||
'observeParentContainer',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// LEGACY properties:
|
||||
#containerName?: string;
|
||||
#containerType?: UmbPropertyContainerTypes;
|
||||
#parentName?: string | null;
|
||||
#parentType?: UmbPropertyContainerTypes;
|
||||
|
||||
#observeContainers() {
|
||||
// LEGACY method:
|
||||
#legacyObserveContainers() {
|
||||
if (!this.#legacyMergeLogic) return;
|
||||
if (!this.#structure || this.#containerId === undefined) return;
|
||||
|
||||
if (this.#containerId === null) {
|
||||
this.#observeHasPropertiesOf(null);
|
||||
this.#observeRootContainers();
|
||||
//this.#observeHasPropertiesOf(null);
|
||||
this.#legacyObserveRootContainers();
|
||||
this.removeUmbControllerByAlias('_observeContainers');
|
||||
} else {
|
||||
this.observe(
|
||||
@@ -133,8 +218,7 @@ export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeMode
|
||||
this.removeUmbControllerByAlias('_observeContainers');
|
||||
this.#containerName = undefined;
|
||||
this.#containerType = undefined;
|
||||
// TODO: reset has Properties.
|
||||
this.#hasProperties.setValue([]);
|
||||
//this.#hasProperties.setValue([]);
|
||||
}
|
||||
},
|
||||
'_observeMainContainer',
|
||||
@@ -142,6 +226,7 @@ export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeMode
|
||||
}
|
||||
}
|
||||
|
||||
// LEGACY method:
|
||||
#observeSimilarContainers() {
|
||||
if (this.#containerName === undefined || !this.#containerType || this.#parentName === undefined) return;
|
||||
this.observe(
|
||||
@@ -152,13 +237,13 @@ export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeMode
|
||||
this.#parentType,
|
||||
),
|
||||
(containers) => {
|
||||
this.#hasProperties.setValue([]);
|
||||
//this.#hasProperties.setValue([]);
|
||||
this.#childContainers.setValue([]);
|
||||
this.#containerObservers.forEach((x) => x.destroy());
|
||||
this.#containerObservers = [];
|
||||
|
||||
containers.forEach((container) => {
|
||||
this.#observeHasPropertiesOf(container.id);
|
||||
//this.#observeHasPropertiesOf(container.id);
|
||||
|
||||
this.#containerObservers.push(
|
||||
this.observe(
|
||||
@@ -184,7 +269,8 @@ export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeMode
|
||||
);
|
||||
}
|
||||
|
||||
#observeRootContainers() {
|
||||
// LEGACY method:
|
||||
#legacyObserveRootContainers() {
|
||||
if (!this.#structure || !this.#childType || this.#containerId === undefined) return;
|
||||
|
||||
this.observe(
|
||||
@@ -201,6 +287,7 @@ export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeMode
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
#observeHasPropertiesOf(groupId?: string | null) {
|
||||
if (!this.#structure || groupId === undefined) return;
|
||||
|
||||
@@ -212,8 +299,10 @@ export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeMode
|
||||
'_observePropertyStructureOfGroup' + groupId,
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
#filterNonOwnerContainers(containers: Array<UmbPropertyTypeContainerModel>) {
|
||||
// LEGACY method:
|
||||
#legacyFilterNonOwnerContainers(containers: Array<UmbPropertyTypeContainerModel>) {
|
||||
return this.#ownerChildContainers.length > 0
|
||||
? containers.filter(
|
||||
(anyCon) =>
|
||||
@@ -226,22 +315,23 @@ export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeMode
|
||||
: containers;
|
||||
}
|
||||
|
||||
#performContainerMerge = (containers: Array<UmbPropertyTypeContainerModel>) => {
|
||||
// LEGACY method:
|
||||
#legacyPerformContainerMerge = (containers: Array<UmbPropertyTypeContainerModel>) => {
|
||||
// Remove containers that matches with a owner container:
|
||||
let merged = this.#filterNonOwnerContainers(containers);
|
||||
let merged = this.#legacyFilterNonOwnerContainers(containers);
|
||||
// Remove containers of same name and type:
|
||||
// This only works cause we are dealing with a single level of containers in this Helper, if we had more levels we would need to be more clever about the parent as well. [NL]
|
||||
merged = merged.filter((x, i, cons) => i === cons.findIndex((y) => y.name === x.name && y.type === x.type));
|
||||
this.#mergedChildContainers.setValue(merged);
|
||||
this.#legacyMergedChildContainers.setValue(merged);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if the container is an owner container.
|
||||
* @param containerId
|
||||
*/
|
||||
isOwnerChildContainer(containerId?: string) {
|
||||
isOwnerChildContainer(containerId?: string): boolean | undefined {
|
||||
if (!this.#structure || !containerId) return;
|
||||
return this.#ownerChildContainers.some((x) => x.id === containerId);
|
||||
return this.#structure.isOwnerContainer(containerId);
|
||||
}
|
||||
|
||||
getContentTypeOfContainer(containerId?: string) {
|
||||
|
||||
@@ -47,7 +47,7 @@ export class UmbContentTypePropertyStructureHelper<T extends UmbContentTypeModel
|
||||
this.#structure = structure;
|
||||
this.#initResolver?.(undefined);
|
||||
this.#initResolver = undefined;
|
||||
this.#observeContainers();
|
||||
this.#observeContainer();
|
||||
}
|
||||
|
||||
public getStructureManager() {
|
||||
@@ -57,13 +57,13 @@ export class UmbContentTypePropertyStructureHelper<T extends UmbContentTypeModel
|
||||
public setContainerId(value?: string | null) {
|
||||
if (this.#containerId === value) return;
|
||||
this.#containerId = value;
|
||||
this.#observeContainers();
|
||||
this.#observeContainer();
|
||||
}
|
||||
public getContainerId() {
|
||||
return this.#containerId;
|
||||
}
|
||||
|
||||
#observeContainers() {
|
||||
#observeContainer() {
|
||||
this.observe(
|
||||
this.#containerId ? this.#structure?.mergedContainersOfId(this.#containerId) : undefined,
|
||||
(container) => {
|
||||
|
||||
@@ -497,19 +497,38 @@ export class UmbContentTypeStructureManager<
|
||||
|
||||
makeEmptyContainerName(
|
||||
containerId: string,
|
||||
containerType: UmbPropertyContainerTypes,
|
||||
parentId: string | null = null,
|
||||
legacyContainerType?: UmbPropertyContainerTypes,
|
||||
legacyParentId?: string | null,
|
||||
): string {
|
||||
return (
|
||||
this.makeContainerNameUniqueForOwnerContentType(containerId, 'Unnamed', containerType, parentId) ?? 'Unnamed'
|
||||
this.makeContainerNameUniqueForOwnerContentType(containerId, 'Unnamed', legacyContainerType, legacyParentId) ??
|
||||
'Unnamed'
|
||||
);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {string} containerId - The id of the container to make unique
|
||||
* @param {string} newName - The new name to make unique
|
||||
* @param {never} _legacyContainerType - do not use, has no effect. Is deprecated and will be removed in v.17
|
||||
* @param {never} _legacyParentId - do not use, has no effect. Is deprecated and will be removed in v.17
|
||||
* @returns
|
||||
*/
|
||||
makeContainerNameUniqueForOwnerContentType(
|
||||
containerId: string,
|
||||
newName: string,
|
||||
containerType: UmbPropertyContainerTypes,
|
||||
parentId: string | null = null,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_legacyContainerType?: UmbPropertyContainerTypes,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_legacyParentId?: string | null,
|
||||
) {
|
||||
const container = this.getOwnerContainerById(containerId);
|
||||
if (!container) {
|
||||
console.warn(`Container with id ${containerId} not found in owner content type.`);
|
||||
return null;
|
||||
}
|
||||
const containerType = container.type;
|
||||
const parentId = container.parent?.id ?? null;
|
||||
|
||||
const ownerRootContainers = this.getOwnerContainers(containerType, parentId); //getRootContainers() can't differentiates between compositions and locals
|
||||
if (!ownerRootContainers) {
|
||||
return null;
|
||||
@@ -749,6 +768,26 @@ export class UmbContentTypeStructureManager<
|
||||
});
|
||||
}
|
||||
|
||||
hasPropertyStructuresOfGroupIds(groupIds: Array<string>) {
|
||||
return this.#contentTypes.asObservablePart((docTypes) => {
|
||||
return docTypes.some((docType) => {
|
||||
return docType.properties?.some((property) => {
|
||||
return property.container?.id && groupIds.includes(property.container.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
hasPropertyStructuresOfRoot() {
|
||||
return this.#contentTypes.asObservablePart((docTypes) => {
|
||||
return docTypes.some((docType) => {
|
||||
return docType.properties?.some((property) => {
|
||||
return !property.container;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
rootContainers(containerType: UmbPropertyContainerTypes) {
|
||||
return createObservablePart(this.#contentTypeContainers, (data) => {
|
||||
return data.filter((x) => x.parent === null && x.type === containerType);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { UmbContentTypeModel, UmbPropertyTypeContainerModel } from '../../../types.js';
|
||||
import type { UmbContentTypeModel, UmbPropertyTypeContainerMergedModel } from '../../../types.js';
|
||||
import type { UmbContentTypeContainerStructureHelper } from '../../../structure/index.js';
|
||||
import { css, customElement, html, nothing, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
@@ -11,16 +11,16 @@ import './content-type-design-editor-properties.element.js';
|
||||
@customElement('umb-content-type-design-editor-group')
|
||||
export class UmbContentTypeWorkspaceViewEditGroupElement extends UmbLitElement {
|
||||
@property({ attribute: false })
|
||||
public set group(value: UmbPropertyTypeContainerModel | undefined) {
|
||||
public set group(value: UmbPropertyTypeContainerMergedModel | undefined) {
|
||||
if (value === this._group) return;
|
||||
this._group = value;
|
||||
this._groupId = value?.id;
|
||||
this._groupId = value?.ownerId ?? value?.ids[0];
|
||||
this.#checkInherited();
|
||||
}
|
||||
public get group(): UmbPropertyTypeContainerModel | undefined {
|
||||
public get group(): UmbPropertyTypeContainerMergedModel | undefined {
|
||||
return this._group;
|
||||
}
|
||||
private _group?: UmbPropertyTypeContainerModel | undefined;
|
||||
private _group?: UmbPropertyTypeContainerMergedModel | undefined;
|
||||
|
||||
@property({ attribute: false })
|
||||
public set groupStructureHelper(value: UmbContentTypeContainerStructureHelper<UmbContentTypeModel> | undefined) {
|
||||
@@ -45,7 +45,8 @@ export class UmbContentTypeWorkspaceViewEditGroupElement extends UmbLitElement {
|
||||
@state()
|
||||
private _hasOwnerContainer?: boolean;
|
||||
|
||||
@state()
|
||||
// attrbute is used by Sorter Controller in parent scope.
|
||||
@property({ type: Boolean, reflect: true, attribute: 'inherited' })
|
||||
private _inherited?: boolean;
|
||||
|
||||
@state()
|
||||
@@ -54,48 +55,46 @@ export class UmbContentTypeWorkspaceViewEditGroupElement extends UmbLitElement {
|
||||
#checkInherited() {
|
||||
if (this.groupStructureHelper && this.group) {
|
||||
// Check is this container matches with any other group. If so it is inherited aka. merged with others. [NL]
|
||||
if (this.group.name) {
|
||||
// We can first match with something if we have a name [NL]
|
||||
this.observe(
|
||||
this.groupStructureHelper.containersByNameAndType(this.group.name, 'Group'),
|
||||
(containers) => {
|
||||
const ownerContainer = containers.find((con) => this.groupStructureHelper!.isOwnerChildContainer(con.id));
|
||||
const hasAOwnerContainer = !!ownerContainer;
|
||||
const pureOwnerContainer = hasAOwnerContainer && containers.length === 1;
|
||||
|
||||
this._hasOwnerContainer = hasAOwnerContainer;
|
||||
this._inherited = !pureOwnerContainer;
|
||||
this._inheritedFrom = containers
|
||||
.filter((con) => con.id !== ownerContainer?.id)
|
||||
.map((con) => this.groupStructureHelper!.getContentTypeOfContainer(con.id))
|
||||
.filter((contentType) => contentType !== undefined) as Array<UmbContentTypeModel>;
|
||||
},
|
||||
'observeGroupContainers',
|
||||
);
|
||||
} else {
|
||||
// We use name match to determine inheritance, so no name cannot inherit.
|
||||
this._inherited = false;
|
||||
if (this.group.ownerId) {
|
||||
this._hasOwnerContainer = true;
|
||||
this.removeUmbControllerByAlias('observeGroupContainers');
|
||||
}
|
||||
|
||||
const notOwnerContainerIds = this.group.ids.filter((id) => id !== this.group!.ownerId);
|
||||
|
||||
if (notOwnerContainerIds.length > 0) {
|
||||
this._inheritedFrom = notOwnerContainerIds
|
||||
.map((id) => this.groupStructureHelper!.getContentTypeOfContainer(id))
|
||||
.filter((contentType) => contentType !== undefined) as Array<UmbContentTypeModel>;
|
||||
this._inherited = true;
|
||||
} else {
|
||||
this._inheritedFrom = undefined;
|
||||
this._inherited = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#singleValueUpdate(propertyName: string, value: string | number | boolean | null | undefined) {
|
||||
if (!this._groupStructureHelper || !this.group) return;
|
||||
if (!this._groupStructureHelper || !this._group) return;
|
||||
|
||||
const ownerId = this._group.ownerId;
|
||||
if (!ownerId) return;
|
||||
|
||||
const partialObject = {} as any;
|
||||
partialObject[propertyName] = value;
|
||||
|
||||
this._groupStructureHelper.partialUpdateContainer(this.group.id, partialObject);
|
||||
this._groupStructureHelper.partialUpdateContainer(ownerId, partialObject);
|
||||
}
|
||||
|
||||
#renameGroup(e: InputEvent) {
|
||||
if (!this.groupStructureHelper || !this._group) return;
|
||||
const ownerId = this._group.ownerId;
|
||||
if (!ownerId) return;
|
||||
let newName = (e.target as HTMLInputElement).value;
|
||||
// TODO: This does not seem right, the detection of a unique name requires better awareness on the level of the change. [NL]
|
||||
// This seem to use check for root containers.
|
||||
const changedName = this.groupStructureHelper
|
||||
.getStructureManager()!
|
||||
.makeContainerNameUniqueForOwnerContentType(this._group.id, newName, 'Group', this._group.parent?.id ?? null);
|
||||
.makeContainerNameUniqueForOwnerContentType(ownerId, newName);
|
||||
if (changedName) {
|
||||
newName = changedName;
|
||||
}
|
||||
@@ -105,11 +104,11 @@ export class UmbContentTypeWorkspaceViewEditGroupElement extends UmbLitElement {
|
||||
|
||||
#blurGroup(e: InputEvent) {
|
||||
if (!this.groupStructureHelper || !this._group) return;
|
||||
const ownerId = this._group.ownerId;
|
||||
if (!ownerId) return;
|
||||
const newName = (e.target as HTMLInputElement).value;
|
||||
if (newName === '') {
|
||||
const changedName = this.groupStructureHelper
|
||||
.getStructureManager()!
|
||||
.makeEmptyContainerName(this._group.id, 'Group', this._group.parent?.id ?? null);
|
||||
const changedName = this.groupStructureHelper.getStructureManager()!.makeEmptyContainerName(ownerId);
|
||||
this.#singleValueUpdate('name', changedName);
|
||||
(e.target as HTMLInputElement).value = changedName;
|
||||
}
|
||||
@@ -119,21 +118,22 @@ export class UmbContentTypeWorkspaceViewEditGroupElement extends UmbLitElement {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
if (!this.groupStructureHelper || !this._group) return;
|
||||
if (this._group.ownerId === undefined) return;
|
||||
|
||||
// TODO: Do proper localization here: [NL]
|
||||
await umbConfirmModal(this, {
|
||||
headline: `${this.localize.term('actions_delete')} group`,
|
||||
content: html`<umb-localize key="contentTypeEditor_confirmDeleteGroupMessage" .args=${[
|
||||
this._group.name ?? this._group.id,
|
||||
this._group.name ?? this._group.ownerId,
|
||||
]}>
|
||||
Are you sure you want to delete the group <strong>${this._group.name ?? this._group.id}</strong>
|
||||
Are you sure you want to delete the group <strong>${this._group.name ?? this._group.ownerId}</strong>
|
||||
</umb-localize>
|
||||
</div>`,
|
||||
confirmLabel: this.localize.term('actions_delete'),
|
||||
color: 'danger',
|
||||
});
|
||||
|
||||
this.groupStructureHelper.removeContainer(this._group.id);
|
||||
this.groupStructureHelper.removeContainer(this._group.ownerId);
|
||||
}
|
||||
|
||||
override render() {
|
||||
@@ -277,6 +277,10 @@ export class UmbContentTypeWorkspaceViewEditGroupElement extends UmbLitElement {
|
||||
padding: var(--uui-size-space-4) var(--uui-size-space-5);
|
||||
}
|
||||
|
||||
:host([inherited]) div[slot='header'] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
div[slot='header'] > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -456,6 +456,10 @@ export class UmbContentTypeDesignEditorPropertyElement extends UmbLitElement {
|
||||
height: min-content;
|
||||
}
|
||||
|
||||
#header i {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
#editor {
|
||||
position: relative;
|
||||
--uui-button-background-color: var(--uui-color-background);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { UMB_CONTENT_TYPE_WORKSPACE_CONTEXT } from '../../content-type-workspace.context-token.js';
|
||||
import type { UmbContentTypeModel, UmbPropertyTypeContainerModel } from '../../../types.js';
|
||||
import type {
|
||||
UmbContentTypeModel,
|
||||
UmbPropertyTypeContainerMergedModel,
|
||||
UmbPropertyTypeContainerModel,
|
||||
} from '../../../types.js';
|
||||
import { UmbContentTypeContainerStructureHelper } from '../../../structure/index.js';
|
||||
import { UMB_CONTENT_TYPE_DESIGN_EDITOR_CONTEXT } from './content-type-design-editor.context-token.js';
|
||||
import type { UmbContentTypeWorkspaceViewEditGroupElement } from './content-type-design-editor-group.element.js';
|
||||
@@ -13,95 +17,122 @@ import type { UmbSorterConfig } from '@umbraco-cms/backoffice/sorter';
|
||||
import './content-type-design-editor-properties.element.js';
|
||||
import './content-type-design-editor-group.element.js';
|
||||
|
||||
const SORTER_CONFIG: UmbSorterConfig<UmbPropertyTypeContainerModel, UmbContentTypeWorkspaceViewEditGroupElement> = {
|
||||
getUniqueOfElement: (element) => element.group?.id,
|
||||
getUniqueOfModel: (modelEntry) => modelEntry.id,
|
||||
// TODO: Make specific to the current owner document. [NL]
|
||||
identifier: 'content-type-container-sorter',
|
||||
itemSelector: 'umb-content-type-design-editor-group',
|
||||
handleSelector: '.drag-handle',
|
||||
containerSelector: '.container-list',
|
||||
};
|
||||
const SORTER_CONFIG: UmbSorterConfig<UmbPropertyTypeContainerMergedModel, UmbContentTypeWorkspaceViewEditGroupElement> =
|
||||
{
|
||||
getUniqueOfElement: (element) => element.group?.key,
|
||||
getUniqueOfModel: (modelEntry) => modelEntry.key,
|
||||
// TODO: Make specific to the current owner document. [NL]
|
||||
identifier: 'content-type-container-sorter',
|
||||
itemSelector: 'umb-content-type-design-editor-group',
|
||||
handleSelector: '.drag-handle',
|
||||
disabledItemSelector: '[inherited]', // Inherited attribute is set by the umb-content-type-design-editor-group.
|
||||
containerSelector: '.container-list',
|
||||
};
|
||||
|
||||
@customElement('umb-content-type-design-editor-tab')
|
||||
export class UmbContentTypeDesignEditorTabElement extends UmbLitElement {
|
||||
#sorter = new UmbSorterController<UmbPropertyTypeContainerModel, UmbContentTypeWorkspaceViewEditGroupElement>(this, {
|
||||
...SORTER_CONFIG,
|
||||
onChange: ({ model }) => {
|
||||
this._groups = model;
|
||||
},
|
||||
onEnd: ({ item }) => {
|
||||
/*if (this._inherited === undefined) {
|
||||
#sorter = new UmbSorterController<UmbPropertyTypeContainerMergedModel, UmbContentTypeWorkspaceViewEditGroupElement>(
|
||||
this,
|
||||
{
|
||||
...SORTER_CONFIG,
|
||||
onChange: ({ model }) => {
|
||||
this._groups = model;
|
||||
},
|
||||
onEnd: ({ item }) => {
|
||||
/*if (this._inherited === undefined) {
|
||||
throw new Error('OwnerTabId is not set, we have not made a local duplicated of this container.');
|
||||
return;
|
||||
}*/
|
||||
/**
|
||||
* Explanation: If the item is the first in list, we compare it to the item behind it to set a sortOrder.
|
||||
* If it's not the first in list, we will compare to the item in before it, and check the following item to see if it caused overlapping sortOrder, then update
|
||||
* the overlap if true, which may cause another overlap, so we loop through them till no more overlaps...
|
||||
*/
|
||||
const model = this._groups;
|
||||
const newIndex = model.findIndex((entry) => entry.id === item.id);
|
||||
/**
|
||||
* Explanation: If the item is the first in list, we compare it to the item behind it to set a sortOrder.
|
||||
* If it's not the first in list, we will compare to the item in before it, and check the following item to see if it caused overlapping sortOrder, then update
|
||||
* the overlap if true, which may cause another overlap, so we loop through them till no more overlaps...
|
||||
*/
|
||||
const model = this._groups;
|
||||
const newIndex = model.findIndex((entry) => entry.key === item.key);
|
||||
|
||||
// Doesn't exist in model
|
||||
if (newIndex === -1) return;
|
||||
// Doesn't exist in model
|
||||
if (newIndex === -1) return;
|
||||
|
||||
// As origin we set prev sort order to -1, so if no other then our item will become 0
|
||||
let prevSortOrder = -1;
|
||||
// As origin we set prev sort order to -1, so if no other then our item will become 0
|
||||
let prevSortOrder = -1;
|
||||
|
||||
// Not first in list
|
||||
if (newIndex > 0 && model.length > 0) {
|
||||
prevSortOrder = model[newIndex - 1].sortOrder;
|
||||
}
|
||||
// Not first in list
|
||||
if (newIndex > 0 && model.length > 0) {
|
||||
prevSortOrder = model[newIndex - 1].sortOrder;
|
||||
}
|
||||
|
||||
// increase the prevSortOrder and use it for the moved item,
|
||||
this.#groupStructureHelper.partialUpdateContainer(item.id, {
|
||||
sortOrder: ++prevSortOrder,
|
||||
});
|
||||
const ownerId = item.ownerId;
|
||||
|
||||
// Adjust everyone right after, meaning until there is a gap between the sortOrders:
|
||||
let i = newIndex + 1;
|
||||
let entry: UmbPropertyTypeContainerModel | undefined;
|
||||
// As long as there is an item with the index & the sortOrder is less or equal to the prevSortOrder, we will update the sortOrder:
|
||||
while ((entry = model[i]) !== undefined && entry.sortOrder <= prevSortOrder) {
|
||||
// Increase the prevSortOrder and use it for the item:
|
||||
this.#groupStructureHelper.partialUpdateContainer(entry.id, {
|
||||
if (ownerId === undefined) {
|
||||
// This may be possible later, but for now this is not possible. [NL]
|
||||
throw new Error(
|
||||
'OwnerId is not set for the given container, we cannot move containers that are not owned by the current Document.',
|
||||
);
|
||||
}
|
||||
|
||||
// increase the prevSortOrder and use it for the moved item,
|
||||
this.#groupStructureHelper.partialUpdateContainer(ownerId, {
|
||||
sortOrder: ++prevSortOrder,
|
||||
});
|
||||
|
||||
i++;
|
||||
}
|
||||
// Adjust everyone right after, meaning until there is a gap between the sortOrders:
|
||||
let i = newIndex + 1;
|
||||
let entry: UmbPropertyTypeContainerMergedModel | undefined;
|
||||
// As long as there is an item with the index & the sortOrder is less or equal to the prevSortOrder, we will update the sortOrder:
|
||||
while ((entry = model[i]) !== undefined && entry.sortOrder <= prevSortOrder) {
|
||||
// Only updated owned containers:
|
||||
if (entry.ownerId) {
|
||||
// Increase the prevSortOrder and use it for the item:
|
||||
this.#groupStructureHelper.partialUpdateContainer(entry.ownerId, {
|
||||
sortOrder: ++prevSortOrder,
|
||||
});
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
},
|
||||
onRequestDrop: async ({ unique }) => {
|
||||
const context = await this.getContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT);
|
||||
if (!context) {
|
||||
throw new Error('Could not get Workspace Context');
|
||||
}
|
||||
return context.structure.getMergedContainerById(unique) as UmbPropertyTypeContainerMergedModel | undefined;
|
||||
},
|
||||
requestExternalRemove: async ({ item }) => {
|
||||
const context = await this.getContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT);
|
||||
if (!context) {
|
||||
throw new Error('Could not get Workspace Context');
|
||||
}
|
||||
return await context.structure.removeContainer(null, item.ownerId, { preventRemovingProperties: true }).then(
|
||||
() => true,
|
||||
() => false,
|
||||
);
|
||||
},
|
||||
requestExternalInsert: async ({ item }) => {
|
||||
const context = await this.getContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT);
|
||||
if (!context) {
|
||||
throw new Error('Could not get Workspace Context');
|
||||
}
|
||||
if (item.ownerId === undefined) {
|
||||
// This may be possible later, but for now this is not possible. [NL]
|
||||
throw new Error('OwnerId is not set, we cannot move containers that are not owned by the current Document.');
|
||||
}
|
||||
const parent = this.#containerId ? { id: this.#containerId } : null;
|
||||
const containerModelItem: UmbPropertyTypeContainerModel = {
|
||||
id: item.ownerId,
|
||||
name: item.name,
|
||||
sortOrder: item.sortOrder,
|
||||
type: item.type,
|
||||
parent,
|
||||
};
|
||||
return await context.structure.insertContainer(null, containerModelItem).then(
|
||||
() => true,
|
||||
() => false,
|
||||
);
|
||||
},
|
||||
},
|
||||
onRequestDrop: async ({ unique }) => {
|
||||
const context = await this.getContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT);
|
||||
if (!context) {
|
||||
throw new Error('Could not get Workspace Context');
|
||||
}
|
||||
return context.structure.getOwnerContainerById(unique);
|
||||
},
|
||||
requestExternalRemove: async ({ item }) => {
|
||||
const context = await this.getContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT);
|
||||
if (!context) {
|
||||
throw new Error('Could not get Workspace Context');
|
||||
}
|
||||
return await context.structure.removeContainer(null, item.id, { preventRemovingProperties: true }).then(
|
||||
() => true,
|
||||
() => false,
|
||||
);
|
||||
},
|
||||
requestExternalInsert: async ({ item }) => {
|
||||
const context = await this.getContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT);
|
||||
if (!context) {
|
||||
throw new Error('Could not get Workspace Context');
|
||||
}
|
||||
const parent = this.#containerId ? { id: this.#containerId } : null;
|
||||
const updatedItem = { ...item, parent };
|
||||
return await context.structure.insertContainer(null, updatedItem).then(
|
||||
() => true,
|
||||
() => false,
|
||||
);
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
#workspaceModal?: UmbModalRouteRegistrationController<
|
||||
typeof UMB_WORKSPACE_MODAL.DATA,
|
||||
@@ -122,7 +153,7 @@ export class UmbContentTypeDesignEditorTabElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
@state()
|
||||
private _groups: Array<UmbPropertyTypeContainerModel> = [];
|
||||
private _groups: Array<UmbPropertyTypeContainerMergedModel> = [];
|
||||
|
||||
@state()
|
||||
private _hasProperties = false;
|
||||
@@ -170,7 +201,7 @@ export class UmbContentTypeDesignEditorTabElement extends UmbLitElement {
|
||||
});
|
||||
|
||||
this.observe(
|
||||
this.#groupStructureHelper.mergedContainers,
|
||||
this.#groupStructureHelper.childContainers,
|
||||
(groups) => {
|
||||
this._groups = groups;
|
||||
this.#sorter.setModel(this._groups);
|
||||
@@ -212,14 +243,14 @@ export class UmbContentTypeDesignEditorTabElement extends UmbLitElement {
|
||||
<div class="container-list" ?sort-mode-active=${this._sortModeActive}>
|
||||
${repeat(
|
||||
this._groups,
|
||||
(group) => group.id,
|
||||
(group) => group.ids[0],
|
||||
(group) => html`
|
||||
<umb-content-type-design-editor-group
|
||||
?sort-mode-active=${this._sortModeActive}
|
||||
.editContentTypePath=${this._editContentTypePath}
|
||||
.group=${group}
|
||||
.groupStructureHelper=${this.#groupStructureHelper as any}
|
||||
data-umb-group-id=${group.id}
|
||||
data-umb-group-id=${group.ownerId ?? group.ids[0]}
|
||||
data-mark="group:${group.name}">
|
||||
</umb-content-type-design-editor-group>
|
||||
`,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { UMB_CONTENT_TYPE_WORKSPACE_CONTEXT } from '../../content-type-workspace
|
||||
import type {
|
||||
UmbContentTypeCompositionModel,
|
||||
UmbContentTypeModel,
|
||||
UmbPropertyTypeContainerModel,
|
||||
UmbPropertyTypeContainerMergedModel,
|
||||
} from '../../../types.js';
|
||||
import {
|
||||
UmbContentTypeContainerStructureHelper,
|
||||
@@ -33,9 +33,9 @@ import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
|
||||
@customElement('umb-content-type-design-editor')
|
||||
export class UmbContentTypeDesignEditorElement extends UmbLitElement implements UmbWorkspaceViewElement {
|
||||
#sorter = new UmbSorterController<UmbPropertyTypeContainerModel, UUITabElement>(this, {
|
||||
getUniqueOfElement: (element) => element.getAttribute('data-umb-tab-id'),
|
||||
getUniqueOfModel: (tab) => tab.id,
|
||||
#sorter = new UmbSorterController<UmbPropertyTypeContainerMergedModel, UUITabElement>(this, {
|
||||
getUniqueOfElement: (element) => element.getAttribute('data-umb-tab-key'),
|
||||
getUniqueOfModel: (tab) => tab.key,
|
||||
identifier: 'content-type-tabs-sorter',
|
||||
itemSelector: 'uui-tab',
|
||||
containerSelector: 'uui-tab-group',
|
||||
@@ -51,7 +51,7 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
|
||||
* the overlap if true, which may cause another overlap, so we loop through them till no more overlaps...
|
||||
*/
|
||||
const model = this._tabs ?? [];
|
||||
const newIndex = model.findIndex((entry) => entry.id === item.id);
|
||||
const newIndex = model.findIndex((entry) => entry.key === item.key);
|
||||
|
||||
// Doesn't exist in model
|
||||
if (newIndex === -1) return;
|
||||
@@ -64,20 +64,32 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
|
||||
prevSortOrder = model[newIndex - 1].sortOrder;
|
||||
}
|
||||
|
||||
const ownerId = item.ownerId;
|
||||
|
||||
if (ownerId === undefined) {
|
||||
// This may be possible later, but for now this is not possible. [NL]
|
||||
throw new Error(
|
||||
'OwnerId is not set for the given container, we cannot move containers that are not owned by the current Document.',
|
||||
);
|
||||
}
|
||||
|
||||
// increase the prevSortOrder and use it for the moved item,
|
||||
this.#tabsStructureHelper.partialUpdateContainer(item.id, {
|
||||
this.#tabsStructureHelper.partialUpdateContainer(ownerId, {
|
||||
sortOrder: ++prevSortOrder,
|
||||
});
|
||||
|
||||
// Adjust everyone right after, until there is a gap between the sortOrders: [NL]
|
||||
let i = newIndex + 1;
|
||||
let entry: UmbPropertyTypeContainerModel | undefined;
|
||||
let entry: UmbPropertyTypeContainerMergedModel | undefined;
|
||||
// As long as there is an item with the index & the sortOrder is less or equal to the prevSortOrder, we will update the sortOrder:
|
||||
while ((entry = model[i]) !== undefined && entry.sortOrder <= prevSortOrder) {
|
||||
// Increase the prevSortOrder and use it for the item:
|
||||
this.#tabsStructureHelper.partialUpdateContainer(entry.id, {
|
||||
sortOrder: ++prevSortOrder,
|
||||
});
|
||||
// Only updated owned containers:
|
||||
if (entry.ownerId) {
|
||||
// Increase the prevSortOrder and use it for the item:
|
||||
this.#tabsStructureHelper.partialUpdateContainer(entry.ownerId, {
|
||||
sortOrder: ++prevSortOrder,
|
||||
});
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
@@ -105,7 +117,7 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
|
||||
private _routes: UmbRoute[] = [];
|
||||
|
||||
@state()
|
||||
private _tabs?: Array<UmbPropertyTypeContainerModel>;
|
||||
private _tabs?: Array<UmbPropertyTypeContainerMergedModel>;
|
||||
|
||||
@state()
|
||||
private _routerPath?: string;
|
||||
@@ -136,7 +148,7 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
|
||||
|
||||
this.#tabsStructureHelper.setContainerChildType('Tab');
|
||||
this.#tabsStructureHelper.setIsRoot(true);
|
||||
this.observe(this.#tabsStructureHelper.mergedContainers, (tabs) => {
|
||||
this.observe(this.#tabsStructureHelper.childContainers, (tabs) => {
|
||||
this._tabs = tabs;
|
||||
this.#sorter.setModel(tabs);
|
||||
this.#createRoutes();
|
||||
@@ -180,7 +192,7 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
|
||||
if (this._tabs.length > 0) {
|
||||
this._tabs?.forEach((tab) => {
|
||||
const tabName = tab.name && tab.name !== '' ? tab.name : '-';
|
||||
if (tab.id === this.#processingTabId) {
|
||||
if (tab.ownerId && tab.ownerId === this.#processingTabId) {
|
||||
activeTabName = tabName;
|
||||
}
|
||||
routes.push({
|
||||
@@ -188,7 +200,7 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
|
||||
component: () => import('./content-type-design-editor-tab.element.js'),
|
||||
setup: (component) => {
|
||||
this.#currentTabComponent = component as UmbContentTypeDesignEditorTabElement;
|
||||
this.#currentTabComponent.containerId = tab.id;
|
||||
this.#currentTabComponent.containerId = tab.ownerId ?? tab.ids[0];
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -265,8 +277,8 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
|
||||
}
|
||||
}
|
||||
|
||||
async #requestDeleteTab(tab: UmbPropertyTypeContainerModel | undefined) {
|
||||
if (!tab) return;
|
||||
async #requestDeleteTab(tab: UmbPropertyTypeContainerMergedModel | undefined) {
|
||||
if (!tab || !tab.ownerId) return;
|
||||
// TODO: Localize this:
|
||||
const tabName = tab.name === '' ? 'Unnamed' : tab.name;
|
||||
// TODO: Localize this:
|
||||
@@ -288,10 +300,9 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
|
||||
|
||||
await umbConfirmModal(this, modalData);
|
||||
|
||||
this.#deleteTab(tab?.id);
|
||||
this.#deleteTab(tab.ownerId);
|
||||
}
|
||||
#deleteTab(tabId?: string) {
|
||||
if (!tabId) return;
|
||||
#deleteTab(tabId: string) {
|
||||
this.#workspaceContext?.structure.removeContainer(null, tabId);
|
||||
if (this.#processingTabId === tabId) {
|
||||
this.#processingTabId = undefined;
|
||||
@@ -332,12 +343,13 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
|
||||
}, 100);
|
||||
}
|
||||
|
||||
async #tabNameChanged(event: InputEvent, tab: UmbPropertyTypeContainerModel) {
|
||||
this.#processingTabId = tab.id;
|
||||
async #tabNameChanged(event: InputEvent, tab: UmbPropertyTypeContainerMergedModel) {
|
||||
if (!this.#workspaceContext || !tab.ownerId) return;
|
||||
this.#processingTabId = tab.ownerId;
|
||||
let newName = (event.target as HTMLInputElement).value;
|
||||
|
||||
const changedName = this.#workspaceContext?.structure.makeContainerNameUniqueForOwnerContentType(
|
||||
tab.id,
|
||||
const changedName = this.#workspaceContext.structure.makeContainerNameUniqueForOwnerContentType(
|
||||
tab.ownerId,
|
||||
newName,
|
||||
'Tab',
|
||||
);
|
||||
@@ -349,20 +361,20 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
|
||||
(event.target as HTMLInputElement).value = newName;
|
||||
}
|
||||
|
||||
this.#tabsStructureHelper.partialUpdateContainer(tab.id!, {
|
||||
this.#tabsStructureHelper.partialUpdateContainer(tab.ownerId, {
|
||||
name: newName,
|
||||
});
|
||||
}
|
||||
|
||||
async #tabNameBlur(event: FocusEvent, tab: UmbPropertyTypeContainerModel) {
|
||||
if (!this.#processingTabId) return;
|
||||
async #tabNameBlur(event: FocusEvent, tab: UmbPropertyTypeContainerMergedModel) {
|
||||
if (!this.#processingTabId || !tab.ownerId) return;
|
||||
const newName = (event.target as HTMLInputElement | undefined)?.value;
|
||||
if (newName === '') {
|
||||
const changedName = this.#workspaceContext!.structure.makeEmptyContainerName(this.#processingTabId, 'Tab');
|
||||
|
||||
(event.target as HTMLInputElement).value = changedName;
|
||||
|
||||
this.#tabsStructureHelper.partialUpdateContainer(tab.id!, {
|
||||
this.#tabsStructureHelper.partialUpdateContainer(tab.ownerId, {
|
||||
name: changedName,
|
||||
});
|
||||
}
|
||||
@@ -506,7 +518,7 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
|
||||
${this.renderRootTab()}
|
||||
${repeat(
|
||||
this._tabs,
|
||||
(tab) => tab.id,
|
||||
(tab) => tab.ownerId ?? tab.ids[0],
|
||||
(tab) => this.renderTab(tab),
|
||||
)}
|
||||
</uui-tab-group>
|
||||
@@ -535,16 +547,17 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
|
||||
`;
|
||||
}
|
||||
|
||||
renderTab(tab: UmbPropertyTypeContainerModel) {
|
||||
renderTab(tab: UmbPropertyTypeContainerMergedModel) {
|
||||
const path = this._routerPath + '/tab/' + encodeFolderName(tab.name && tab.name !== '' ? tab.name : '-');
|
||||
const tabActive = path === this._activePath;
|
||||
const ownedTab = this.#tabsStructureHelper.isOwnerChildContainer(tab.id!) ?? false;
|
||||
const ownedTab = tab.ownerId ? true : false;
|
||||
|
||||
return html`<uui-tab
|
||||
label=${tab.name && tab.name !== '' ? tab.name : 'Unnamed'}
|
||||
.active=${tabActive}
|
||||
href=${path}
|
||||
data-umb-tab-id=${ifDefined(tab.id)}
|
||||
data-umb-tab-id=${ifDefined(tab.ownerId ?? tab.ids[0])}
|
||||
data-umb-tab-key=${ifDefined(tab.key)}
|
||||
data-mark="tab:${tab.name}"
|
||||
?sortable=${ownedTab}
|
||||
@dragover=${(event: DragEvent) => this.#onDragOver(event, path)}>
|
||||
@@ -552,14 +565,15 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
|
||||
</uui-tab>`;
|
||||
}
|
||||
|
||||
renderTabInner(tab: UmbPropertyTypeContainerModel, tabActive: boolean, ownedTab: boolean) {
|
||||
renderTabInner(tab: UmbPropertyTypeContainerMergedModel, tabActive: boolean, ownedTab: boolean) {
|
||||
// TODO: Localize this:
|
||||
const hasTabName = tab.name && tab.name !== '';
|
||||
const tabName = hasTabName ? tab.name : 'Unnamed';
|
||||
const tabId = tab.ownerId ?? tab.ids[0];
|
||||
if (this._sortModeActive) {
|
||||
return html`<div class="tab">
|
||||
${ownedTab
|
||||
? html`<uui-icon name="icon-grip" class="drag-${tab.id}"> </uui-icon>${tabName}
|
||||
? html`<uui-icon name="icon-grip" class="drag-${tabId}"> </uui-icon>${tabName}
|
||||
<uui-input
|
||||
label="sort order"
|
||||
type="number"
|
||||
@@ -599,13 +613,13 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
|
||||
}
|
||||
}
|
||||
|
||||
#changeOrderNumber(tab: UmbPropertyTypeContainerModel, e: UUIInputEvent) {
|
||||
if (!e.target.value || !tab.id) return;
|
||||
#changeOrderNumber(tab: UmbPropertyTypeContainerMergedModel, e: UUIInputEvent) {
|
||||
if (!e.target.value || !tab.ownerId) return;
|
||||
const sortOrder = Number(e.target.value);
|
||||
this.#tabsStructureHelper.partialUpdateContainer(tab.id, { sortOrder });
|
||||
this.#tabsStructureHelper.partialUpdateContainer(tab.ownerId, { sortOrder });
|
||||
}
|
||||
|
||||
renderDeleteFor(tab: UmbPropertyTypeContainerModel) {
|
||||
renderDeleteFor(tab: UmbPropertyTypeContainerMergedModel) {
|
||||
return html`<uui-button
|
||||
label=${this.localize.term('actions_remove')}
|
||||
class="trash"
|
||||
|
||||
@@ -3,7 +3,7 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type {
|
||||
UmbContentTypeModel,
|
||||
UmbContentTypeStructureManager,
|
||||
UmbPropertyTypeContainerModel,
|
||||
UmbPropertyTypeContainerMergedModel,
|
||||
} from '@umbraco-cms/backoffice/content-type';
|
||||
import {
|
||||
UmbContentTypeContainerStructureHelper,
|
||||
@@ -28,7 +28,7 @@ export class UmbContentWorkspaceViewEditTabElement extends UmbLitElement {
|
||||
#groupStructureHelper = new UmbContentTypeContainerStructureHelper<UmbContentTypeModel>(this);
|
||||
|
||||
@state()
|
||||
private _groups: Array<UmbPropertyTypeContainerModel> = [];
|
||||
private _groups: Array<UmbPropertyTypeContainerMergedModel> = [];
|
||||
|
||||
@state()
|
||||
private _hasProperties = false;
|
||||
@@ -42,7 +42,7 @@ export class UmbContentWorkspaceViewEditTabElement extends UmbLitElement {
|
||||
workspaceContext?.structure as unknown as UmbContentTypeStructureManager<UmbContentTypeModel>,
|
||||
);
|
||||
});
|
||||
this.observe(this.#groupStructureHelper.mergedContainers, (groups) => {
|
||||
this.observe(this.#groupStructureHelper.childContainers, (groups) => {
|
||||
this._groups = groups;
|
||||
});
|
||||
this.observe(this.#groupStructureHelper.hasProperties, (hasProperties) => {
|
||||
@@ -63,12 +63,12 @@ export class UmbContentWorkspaceViewEditTabElement extends UmbLitElement {
|
||||
: ''}
|
||||
${repeat(
|
||||
this._groups,
|
||||
(group) => group.id,
|
||||
(group) => group.key,
|
||||
(group) =>
|
||||
html`<uui-box .headline=${this.localize.string(group.name) ?? ''}>
|
||||
<umb-content-workspace-view-edit-properties
|
||||
data-mark="property-group:${group.name}"
|
||||
.containerId=${group.id}></umb-content-workspace-view-edit-properties>
|
||||
.containerId=${group.ids[0]}></umb-content-workspace-view-edit-properties>
|
||||
</uui-box>`,
|
||||
)}
|
||||
`;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type {
|
||||
UmbContentTypeModel,
|
||||
UmbContentTypeStructureManager,
|
||||
UmbPropertyTypeContainerModel,
|
||||
UmbPropertyTypeContainerMergedModel,
|
||||
} from '@umbraco-cms/backoffice/content-type';
|
||||
import {
|
||||
UmbContentTypeContainerStructureHelper,
|
||||
@@ -34,7 +34,7 @@ export class UmbContentWorkspaceViewEditElement extends UmbLitElement implements
|
||||
private _routes: UmbRoute[] = [];
|
||||
|
||||
@state()
|
||||
private _tabs?: Array<UmbPropertyTypeContainerModel>;
|
||||
private _tabs?: Array<UmbPropertyTypeContainerMergedModel>;
|
||||
|
||||
@state()
|
||||
private _routerPath?: string;
|
||||
@@ -64,7 +64,7 @@ export class UmbContentWorkspaceViewEditElement extends UmbLitElement implements
|
||||
this._tabsStructureHelper.setIsRoot(true);
|
||||
this._tabsStructureHelper.setContainerChildType('Tab');
|
||||
this.observe(
|
||||
this._tabsStructureHelper.mergedContainers,
|
||||
this._tabsStructureHelper.childContainers,
|
||||
(tabs) => {
|
||||
this._tabs = tabs;
|
||||
this.#createRoutes();
|
||||
@@ -117,7 +117,7 @@ export class UmbContentWorkspaceViewEditElement extends UmbLitElement implements
|
||||
path,
|
||||
component: () => import('./content-editor-tab.element.js'),
|
||||
setup: (component) => {
|
||||
(component as UmbContentWorkspaceViewEditTabElement).containerId = tab.id;
|
||||
(component as UmbContentWorkspaceViewEditTabElement).containerId = tab.ownerId ?? tab.ids[0];
|
||||
},
|
||||
});
|
||||
this.#createViewContext(path);
|
||||
|
||||
@@ -35,7 +35,7 @@ export function setStoredPath(path: string): void {
|
||||
* Redirect the user to the stored path or the base path if not available.
|
||||
* If the basePath matches the start of the stored path, the browser will replace the state instead of redirecting.
|
||||
* @param {string} basePath - The base path to redirect to if no stored path is available.
|
||||
* @param {boolean} [force] - If true, will redirect using Location
|
||||
* @param {boolean} force - If true, will redirect using Location
|
||||
*/
|
||||
export function redirectToStoredPath(basePath: string, force = false): void {
|
||||
const url = retrieveStoredPath();
|
||||
|
||||
@@ -213,8 +213,9 @@ export class UmbWorkspaceEditorElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
#renderRoutes() {
|
||||
if (!this._routes || this._routes.length === 0 || !this._workspaceViews || this._workspaceViews.length === 0)
|
||||
if (!this._routes || this._routes.length === 0 || !this._workspaceViews || this._workspaceViews.length === 0) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<umb-router-slot
|
||||
inherit-addendum
|
||||
|
||||
Reference in New Issue
Block a user