Skip to content

Commit 9a18dd5

Browse files
authored
Content Type Designer: Fix moving a group to an inherited tab (#20138)
move group to inherited tab
1 parent 7ecc6ec commit 9a18dd5

File tree

7 files changed

+80
-36
lines changed

7 files changed

+80
-36
lines changed

src/Umbraco.Web.UI.Client/src/packages/content/content-type/structure/content-type-container-structure-helper.class.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeMode
3939
return this.#legacyMergedChildContainers.asObservable();
4040
}
4141

42-
#childContainersMerged = new UmbArrayState<UmbPropertyTypeContainerMergedModel>([], (x) => x.path);
42+
#childContainersMerged = new UmbArrayState<UmbPropertyTypeContainerMergedModel>([], (x) => x.key);
4343
public readonly childContainers = this.#childContainersMerged.asObservable();
4444

4545
// Owner containers are containers owned by the owner Content Type (The specific one up for editing)

src/Umbraco.Web.UI.Client/src/packages/content/content-type/structure/content-type-structure-manager.class.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,8 @@ export class UmbContentTypeStructureManager<
198198
this.#ownerContentTypeUnique = unique;
199199
if (!unique) {
200200
this.#initRejection?.(`Content Type structure manager could not load: ${unique}`);
201+
this.#initResolver = undefined;
202+
this.#initRejection = undefined;
201203
return Promise.reject(
202204
new Error('The unique identifier is missing. A valid unique identifier is required to load the content type.'),
203205
);
@@ -207,6 +209,8 @@ export class UmbContentTypeStructureManager<
207209
const result = await this.observe(observable).asPromise();
208210
if (!result) {
209211
this.#initRejection?.(`Content Type structure manager could not load: ${unique}`);
212+
this.#initResolver = undefined;
213+
this.#initRejection = undefined;
210214
return {
211215
error: new UmbError(`Content Type structure manager could not load: ${unique}`),
212216
asObservable: () => observable,
@@ -219,10 +223,14 @@ export class UmbContentTypeStructureManager<
219223
}).catch(() => {
220224
const msg = `Content Type structure manager could not load: ${unique}. Not all Content Types loaded successfully.`;
221225
this.#initRejection?.(msg);
226+
this.#initResolver = undefined;
227+
this.#initRejection = undefined;
222228
return Promise.reject(new UmbError(msg));
223229
});
224230

225231
this.#initResolver?.(result);
232+
this.#initResolver = undefined;
233+
this.#initRejection = undefined;
226234
return { data: result, asObservable: () => this.ownerContentType };
227235
}
228236

@@ -234,6 +242,8 @@ export class UmbContentTypeStructureManager<
234242
const { data } = repsonse;
235243
if (!data) {
236244
this.#initRejection?.(`Content Type structure manager could not create scaffold`);
245+
this.#initResolver = undefined;
246+
this.#initRejection = undefined;
237247
return { error: repsonse.error };
238248
}
239249

@@ -244,6 +254,8 @@ export class UmbContentTypeStructureManager<
244254
// Make a entry in the repo manager:
245255
this.#repoManager!.addEntry(data);
246256
this.#initResolver?.(data);
257+
this.#initResolver = undefined;
258+
this.#initRejection = undefined;
247259
return repsonse;
248260
}
249261

@@ -997,16 +1009,21 @@ export class UmbContentTypeStructureManager<
9971009
*/
9981010

9991011
/**
1000-
*
1001-
* Find merged containers that match the provided container ids.
1002-
* Notice if you can provide one or more ids matching the same container and it will still only return return the matching container once.
1003-
* @param containerIds - An array of container ids to find merged containers for.
1004-
* @param id
1012+
* Find a merged container that match the provided container id.
1013+
* @param {string} id - The id to find the merged container of.
10051014
* @returns {UmbPropertyTypeContainerMergedModel | undefined} - The merged containers that match the provided container ids.
10061015
*/
10071016
getMergedContainerById(id: string): UmbPropertyTypeContainerMergedModel | undefined {
10081017
return this.#mergedContainers.find((x) => x.ids.includes(id));
10091018
}
1019+
/**
1020+
* Find a merged container that match the provided merged-container key.
1021+
* @param {string} key - The key to find the merged container of.
1022+
* @returns {UmbPropertyTypeContainerMergedModel | undefined} - The merged containers that match the provided merged-container key.
1023+
*/
1024+
getMergedContainerByKey(key: string): UmbPropertyTypeContainerMergedModel | undefined {
1025+
return this.#mergedContainers.find((x) => x.key === key);
1026+
}
10101027

10111028
/**
10121029
*

src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/views/design/content-type-design-editor-group.element.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export class UmbContentTypeWorkspaceViewEditGroupElement extends UmbLitElement {
4242
@state()
4343
private _groupId?: string;
4444

45-
@state()
45+
@property({ type: Boolean, reflect: true, attribute: 'has-owner-container' })
4646
private _hasOwnerContainer?: boolean;
4747

4848
// attrbute is used by Sorter Controller in parent scope.
@@ -273,12 +273,11 @@ export class UmbContentTypeWorkspaceViewEditGroupElement extends UmbLitElement {
273273
display: flex;
274274
align-items: center;
275275
justify-content: space-between;
276-
cursor: grab;
277276
padding: var(--uui-size-space-4) var(--uui-size-space-5);
278277
}
279278
280-
:host([inherited]) div[slot='header'] {
281-
cursor: default;
279+
:host([has-owner-container]) div[slot='header'] {
280+
cursor: grab;
282281
}
283282
284283
div[slot='header'] > div {

src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/views/design/content-type-design-editor-tab.element.ts

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import type {
55
UmbPropertyTypeContainerModel,
66
} from '../../../types.js';
77
import { UmbContentTypeContainerStructureHelper } from '../../../structure/index.js';
8-
import { UMB_CONTENT_TYPE_DESIGN_EDITOR_CONTEXT } from './content-type-design-editor.context-token.js';
98
import type { UmbContentTypeWorkspaceViewEditGroupElement } from './content-type-design-editor-group.element.js';
109
import { css, customElement, html, nothing, property, repeat, state } from '@umbraco-cms/backoffice/external/lit';
1110
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
11+
import { UMB_CONTENT_TYPE_DESIGN_EDITOR_CONTEXT } from './content-type-design-editor.context-token.js';
1212
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
1313
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
1414
import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace';
@@ -19,13 +19,13 @@ import './content-type-design-editor-group.element.js';
1919

2020
const SORTER_CONFIG: UmbSorterConfig<UmbPropertyTypeContainerMergedModel, UmbContentTypeWorkspaceViewEditGroupElement> =
2121
{
22-
getUniqueOfElement: (element) => element.group?.key,
23-
getUniqueOfModel: (modelEntry) => modelEntry.key,
22+
getUniqueOfElement: (element) => element.group?.ownerId ?? element.group?.ids[0],
23+
getUniqueOfModel: (modelEntry) => modelEntry.ownerId ?? modelEntry.ids[0],
2424
// TODO: Make specific to the current owner document. [NL]
2525
identifier: 'content-type-container-sorter',
2626
itemSelector: 'umb-content-type-design-editor-group',
2727
handleSelector: '.drag-handle',
28-
disabledItemSelector: '[inherited]', // Inherited attribute is set by the umb-content-type-design-editor-group.
28+
disabledItemSelector: ':not([has-owner-container])', // Inherited attribute is set by the umb-content-type-design-editor-group.
2929
containerSelector: '.container-list',
3030
};
3131

@@ -38,18 +38,34 @@ export class UmbContentTypeDesignEditorTabElement extends UmbLitElement {
3838
onChange: ({ model }) => {
3939
this._groups = model;
4040
},
41+
onContainerChange: ({ item }) => {
42+
if (this.#containerId === undefined) {
43+
throw new Error('ContainerId is not set');
44+
}
45+
if (item.ownerId === undefined) {
46+
// This may be possible later, but for now this is not possible. [NL]
47+
throw new Error(
48+
'OwnerId is not set for the given container, we cannot move containers that are not owned by the current Document.',
49+
);
50+
}
51+
this.#groupStructureHelper.partialUpdateContainer(item.ownerId, {
52+
parent: this.#containerId ? { id: this.#containerId } : null,
53+
});
54+
},
4155
onEnd: ({ item }) => {
42-
/*if (this._inherited === undefined) {
43-
throw new Error('OwnerTabId is not set, we have not made a local duplicated of this container.');
44-
return;
45-
}*/
56+
if (item.ownerId === undefined) {
57+
// This may be possible later, but for now this is not possible. [NL]
58+
throw new Error(
59+
'OwnerId is not set for the given container, we cannot move containers that are not owned by the current Document.',
60+
);
61+
}
4662
/**
4763
* Explanation: If the item is the first in list, we compare it to the item behind it to set a sortOrder.
4864
* 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
4965
* the overlap if true, which may cause another overlap, so we loop through them till no more overlaps...
5066
*/
5167
const model = this._groups;
52-
const newIndex = model.findIndex((entry) => entry.key === item.key);
68+
const newIndex = model.findIndex((entry) => entry.ownerId === item.ownerId);
5369

5470
// Doesn't exist in model
5571
if (newIndex === -1) return;
@@ -87,20 +103,22 @@ export class UmbContentTypeDesignEditorTabElement extends UmbLitElement {
87103
this.#groupStructureHelper.partialUpdateContainer(entry.ownerId, {
88104
sortOrder: ++prevSortOrder,
89105
});
90-
91-
i++;
92106
}
107+
i++;
93108
}
94109
},
95110
onRequestDrop: async ({ unique }) => {
96-
const context = await this.getContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT);
111+
const context = this.#contentTypeWorkspaceContext;
97112
if (!context) {
98113
throw new Error('Could not get Workspace Context');
99114
}
100-
return context.structure.getMergedContainerById(unique) as UmbPropertyTypeContainerMergedModel | undefined;
115+
const result = context.structure.getMergedContainerById(unique) as
116+
| UmbPropertyTypeContainerMergedModel
117+
| undefined;
118+
return result;
101119
},
102120
requestExternalRemove: async ({ item }) => {
103-
const context = await this.getContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT);
121+
const context = this.#contentTypeWorkspaceContext;
104122
if (!context) {
105123
throw new Error('Could not get Workspace Context');
106124
}
@@ -110,7 +128,7 @@ export class UmbContentTypeDesignEditorTabElement extends UmbLitElement {
110128
);
111129
},
112130
requestExternalInsert: async ({ item }) => {
113-
const context = await this.getContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT);
131+
const context = this.#contentTypeWorkspaceContext;
114132
if (!context) {
115133
throw new Error('Could not get Workspace Context');
116134
}
@@ -165,11 +183,13 @@ export class UmbContentTypeDesignEditorTabElement extends UmbLitElement {
165183
private _editContentTypePath?: string;
166184

167185
#groupStructureHelper = new UmbContentTypeContainerStructureHelper<UmbContentTypeModel>(this);
186+
#contentTypeWorkspaceContext?: typeof UMB_CONTENT_TYPE_WORKSPACE_CONTEXT.TYPE;
168187

169188
constructor() {
170189
super();
171190

172191
this.consumeContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT, (context) => {
192+
this.#contentTypeWorkspaceContext = context;
173193
this.#groupStructureHelper.setStructureManager(context?.structure);
174194

175195
const entityType = context?.getEntityType();
@@ -190,11 +210,6 @@ export class UmbContentTypeDesignEditorTabElement extends UmbLitElement {
190210
context?.isSorting,
191211
(isSorting) => {
192212
this._sortModeActive = isSorting;
193-
if (isSorting) {
194-
this.#sorter.enable();
195-
} else {
196-
this.#sorter.disable();
197-
}
198213
},
199214
'_observeIsSorting',
200215
);
@@ -294,10 +309,20 @@ export class UmbContentTypeDesignEditorTabElement extends UmbLitElement {
294309
align-content: start;
295310
}
296311
312+
/* Ensure the container-list has some height when its empty so groups can be dropped into it.*/
313+
.container-list {
314+
margin-top: calc(var(--uui-size-layout-1) * -1);
315+
padding-top: var(--uui-size-layout-1);
316+
}
317+
297318
.container-list #convert-to-tab {
298319
margin-bottom: var(--uui-size-layout-1);
299320
display: flex;
300321
}
322+
323+
.container-list[sort-mode-active] {
324+
min-height: 100px;
325+
}
301326
`,
302327
];
303328
}

src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/views/design/content-type-design-editor.element.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -753,7 +753,7 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
753753
transition: opacity 100ms;
754754
}
755755
756-
uui-tab:not(:hover, :focus) .trash {
756+
uui-tab:not(:hover, :focus, :focus-within) .trash {
757757
opacity: 0;
758758
transition: opacity 100ms;
759759
}

src/Umbraco.Web.UI.Client/src/packages/core/router/modal-registration/modal-route-registration.controller.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,12 @@ export class UmbModalRouteRegistrationController<
103103

104104
this.#init = this.consumeContext(UMB_ROUTE_CONTEXT, (_routeContext) => {
105105
this.#routeContext = _routeContext;
106-
this.#registerModal().catch(() => undefined);
107-
}).asPromise({ preventTimeout: true });
106+
if (this.#routeContext) {
107+
this.#registerModal().catch(() => undefined);
108+
}
109+
})
110+
.asPromise({ preventTimeout: true })
111+
.catch(() => undefined);
108112
}
109113

110114
/**
@@ -176,7 +180,7 @@ export class UmbModalRouteRegistrationController<
176180
if (oldValue === value) return;
177181

178182
this.#uniquePaths.set(identifier, value);
179-
this.#registerModal().catch(() => undefined);
183+
this.#registerModal();
180184
}
181185
getUniquePathValue(identifier: string): string | undefined {
182186
return this.#uniquePaths.get(identifier);
@@ -234,7 +238,7 @@ export class UmbModalRouteRegistrationController<
234238
override hostConnected() {
235239
super.hostConnected();
236240
if (!this.#modalRegistrationContext) {
237-
this.#registerModal().catch(() => undefined);
241+
this.#registerModal();
238242
}
239243
}
240244
override hostDisconnected(): void {

src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,6 @@ export type UmbSorterConfig<T, ElementType extends HTMLElement = HTMLElement> =
247247
Partial<Pick<INTERNAL_UmbSorterConfig<T, ElementType>, 'ignorerSelector' | 'containerSelector' | 'identifier'>>;
248248

249249
/**
250-
251250
* @class UmbSorterController
252251
* @implements {UmbControllerInterface}
253252
* @description This controller can make user able to sort items.

0 commit comments

Comments
 (0)