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:
Niels Lyngsø
2025-08-29 20:05:26 +02:00
committed by GitHub
parent 7f720ddf7c
commit 28b90bcfea
14 changed files with 407 additions and 224 deletions

View File

@@ -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}
`;

View File

@@ -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>
`;
}

View File

@@ -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];
},
});
});

View File

@@ -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) {

View File

@@ -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) => {

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);

View File

@@ -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>
`,

View File

@@ -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"

View File

@@ -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>`,
)}
`;

View File

@@ -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);

View File

@@ -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();

View File

@@ -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