Skip to content

Menu expansion to paginated items #19883

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 51 commits into
base: v16/feature/menu-expansion
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
f1f4233
Added user start node restrictions to sibling endpoints.
AndyButland Jul 31, 2025
b6dc933
Further integration tests.
AndyButland Jul 31, 2025
376e462
Tidy up.
AndyButland Jul 31, 2025
e994315
Apply suggestions from code review
AndyButland Jul 31, 2025
dde922c
Revert previous update.
AndyButland Jul 31, 2025
9550282
Retrieves item counts before and after the target for sibling endpoin…
AndyButland Aug 1, 2025
5fb84c0
Applied previous update correctly.
AndyButland Aug 1, 2025
cb5bb47
Merge branch 'v16/improvements/add-data-type-filter-to-siblings-endpo…
AndyButland Aug 1, 2025
eba61e5
Removed blank line.
AndyButland Aug 1, 2025
7d4585b
Fix build and test asserts following merge.
AndyButland Aug 1, 2025
e6abd2a
add getItem method
madsrasmussen Aug 4, 2025
81f263d
Merge remote-tracking branch 'origin/v16/improvements/add-totals-to-s…
madsrasmussen Aug 4, 2025
92554de
Update OpenApi.json.
AndyButland Aug 4, 2025
f8ff4ec
Merge branch 'v16/improvements/add-totals-to-sibling-endpoints' into …
madsrasmussen Aug 4, 2025
02989f2
generate new server types
madsrasmussen Aug 4, 2025
61f7da6
Merge branch 'v16/feature/menu-expansion' into v16/feature/menu-expan…
madsrasmussen Aug 4, 2025
1f52b7c
add target pagination type
madsrasmussen Aug 4, 2025
6c9d458
return totalBefore and totalAfter
madsrasmussen Aug 4, 2025
94e2f6d
call siblings endpoint for documents
madsrasmussen Aug 4, 2025
6c7a7ce
add method to load children with target
madsrasmussen Aug 4, 2025
e776b24
rename to item
madsrasmussen Aug 4, 2025
9340123
wip target pagination manager
madsrasmussen Aug 4, 2025
833240c
add button to load prev tree items
madsrasmussen Aug 5, 2025
e2ec6c5
render prev and nexts buttons for tree items
madsrasmussen Aug 5, 2025
90c7151
Update tree-load-prev-button.element.ts
madsrasmussen Aug 5, 2025
a851783
add util to append to unique array
madsrasmussen Aug 5, 2025
9409579
add state method to prepend data
madsrasmussen Aug 5, 2025
e1e0867
implement methods to load next and prev items
madsrasmussen Aug 5, 2025
fc072b9
add methods to interface
madsrasmussen Aug 5, 2025
4cf23d4
Update tree-item-element-base.ts
madsrasmussen Aug 5, 2025
9da6903
Update tree-item-context-base.ts
madsrasmussen Aug 5, 2025
b93ae4d
remove unused
madsrasmussen Aug 5, 2025
1221876
align methods
madsrasmussen Aug 5, 2025
3bdf028
update types
madsrasmussen Aug 5, 2025
72e75c9
add jsdocs
madsrasmussen Aug 5, 2025
90609d6
add deprecation notice
madsrasmussen Aug 5, 2025
a7eac16
fix jsdocs
madsrasmussen Aug 5, 2025
4556e0c
fix import
madsrasmussen Aug 5, 2025
306b06d
Update tree-data-source.interface.ts
madsrasmussen Aug 5, 2025
88218cd
remove duplicate type
madsrasmussen Aug 5, 2025
9786ca0
clean up
madsrasmussen Aug 5, 2025
cde5928
fix page calculations
madsrasmussen Aug 6, 2025
32c3237
Merge branch 'v16/feature/menu-expansion' into v16/feature/menu-expan…
madsrasmussen Aug 6, 2025
5ccfef9
Merge branch 'v16/feature/menu-expansion' into v16/feature/menu-expan…
madsrasmussen Aug 6, 2025
11c850c
clean up
madsrasmussen Aug 6, 2025
10de1e8
Merge branch 'v16/feature/menu-expansion' into v16/feature/menu-expan…
madsrasmussen Aug 7, 2025
c0f8b0b
Merge branch 'v16/feature/menu-expansion' into v16/feature/menu-expan…
madsrasmussen Aug 8, 2025
9e8fe4c
Merge branch 'v16/feature/menu-expansion' into v16/feature/menu-expan…
madsrasmussen Aug 8, 2025
b8d5424
return correct data from base
madsrasmussen Aug 8, 2025
5cbe1a2
recalculate after
madsrasmussen Aug 8, 2025
b517d48
Merge branch 'v16/feature/menu-expansion' into v16/feature/menu-expan…
madsrasmussen Aug 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { partialUpdateFrozenArray } from '../utils/partial-update-frozen-array.function.js';
import { prependToUniqueArray } from '../utils/prepend-to-unique-array.function.js';
import { pushAtToUniqueArray } from '../utils/push-at-to-unique-array.function.js';
import { pushToUniqueArray } from '../utils/push-to-unique-array.function.js';
import { UmbDeepState } from './deep-state.js';
Expand Down Expand Up @@ -271,6 +272,36 @@ export class UmbArrayState<T, U = unknown> extends UmbDeepState<T[]> {
return this;
}

/**
* @function prepend
* @param {T[]} entries - A array of new data to be added in this Subject.
* @returns {UmbArrayState<T>} Reference to it self.
* @description - Prepend some new data to this Subject, if it compares to existing data it will replace it.
* @example <caption>Example prepend some data.</caption>
* const data = [
* { key: 1, value: 'foo'},
* { key: 2, value: 'bar'}
* ];
* const myState = new UmbArrayState(data);
* myState.prepend([
* { key: 0, value: 'another-bla'},
* { key: 1, value: 'replaced-foo'},
* ]);
*/
prepend(entries: T[]) {
if (this.getUniqueMethod) {
const next = [...this.getValue()];
entries.forEach((entry) => {
prependToUniqueArray(next, entry, this.getUniqueMethod!);
});
this.setValue(next);
} else {
this.setValue([...entries, ...this.getValue()]);
}

return this;
}

override destroy() {
super.destroy();
this.#sortMethod = undefined;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* @function prependToUniqueArray
* @param {T[]} data - An array of objects.
* @param {T} entry - The object to insert or replace with.
* @param {getUniqueMethod: (entry: T) => unknown} [getUniqueMethod] - Method to get the unique value of an entry.
* @description - Prepend or replaces an item of an Array.
* @returns {T[]} - The new array with the entry prepended or replaced.
* @example <caption>Example prepend new entry for a Array. Where the key is unique and the item will be updated if matched with existing.</caption>
* const entry = {key: 'myKey', value: 'myValue'};
* const newDataSet = prependToUniqueArray([], entry, x => x.key === key);
* myState.setValue(newDataSet);
*/
export function prependToUniqueArray<T>(data: T[], entry: T, getUniqueMethod: (entry: T) => unknown): T[] {
const unique = getUniqueMethod(entry);
const indexToReplace = data.findIndex((x) => getUniqueMethod(x) === unique);
if (indexToReplace !== -1) {
data[indexToReplace] = entry;
} else {
data.unshift(entry);
}
return data;
}
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ export type CreateUserGroupRequestModel = {
mediaStartNode?: ReferenceByIdModel | null;
mediaRootAccess: boolean;
fallbackPermissions: Array<string>;
permissions: Array<DocumentPermissionPresentationModel | DocumentPropertyValuePermissionPresentationModel | UnknownTypePermissionPresentationModel>;
permissions: Array<DocumentPermissionPresentationModel | DocumentPropertyValuePermissionPresentationModel | DocumentTypePermissionPresentationModel | UnknownTypePermissionPresentationModel>;
id?: string | null;
};

Expand Down Expand Up @@ -467,7 +467,7 @@ export type CurrentUserResponseModel = {
hasAccessToAllLanguages: boolean;
hasAccessToSensitiveData: boolean;
fallbackPermissions: Array<string>;
permissions: Array<DocumentPermissionPresentationModel | DocumentPropertyValuePermissionPresentationModel | UnknownTypePermissionPresentationModel>;
permissions: Array<DocumentPermissionPresentationModel | DocumentPropertyValuePermissionPresentationModel | DocumentTypePermissionPresentationModel | UnknownTypePermissionPresentationModel>;
allowedSections: Array<string>;
isAdmin: boolean;
};
Expand Down Expand Up @@ -773,6 +773,12 @@ export type DocumentTypeItemResponseModel = {
description?: string | null;
};

export type DocumentTypePermissionPresentationModel = {
$type: string;
verbs: Array<string>;
documentTypeAlias: string;
};

export type DocumentTypePropertyTypeContainerResponseModel = {
id: string;
parent?: ReferenceByIdModel | null;
Expand Down Expand Up @@ -2382,6 +2388,48 @@ export type StylesheetResponseModel = {
content: string;
};

export type SubsetDataTypeTreeItemResponseModel = {
totalBefore: number;
totalAfter: number;
items: Array<DataTypeTreeItemResponseModel>;
};

export type SubsetDocumentBlueprintTreeItemResponseModel = {
totalBefore: number;
totalAfter: number;
items: Array<DocumentBlueprintTreeItemResponseModel>;
};

export type SubsetDocumentTreeItemResponseModel = {
totalBefore: number;
totalAfter: number;
items: Array<DocumentTreeItemResponseModel>;
};

export type SubsetDocumentTypeTreeItemResponseModel = {
totalBefore: number;
totalAfter: number;
items: Array<DocumentTypeTreeItemResponseModel>;
};

export type SubsetMediaTreeItemResponseModel = {
totalBefore: number;
totalAfter: number;
items: Array<MediaTreeItemResponseModel>;
};

export type SubsetMediaTypeTreeItemResponseModel = {
totalBefore: number;
totalAfter: number;
items: Array<MediaTypeTreeItemResponseModel>;
};

export type SubsetNamedEntityTreeItemResponseModel = {
totalBefore: number;
totalAfter: number;
items: Array<NamedEntityTreeItemResponseModel>;
};

export type TagResponseModel = {
id: string;
text?: string | null;
Expand Down Expand Up @@ -2777,7 +2825,7 @@ export type UpdateUserGroupRequestModel = {
mediaStartNode?: ReferenceByIdModel | null;
mediaRootAccess: boolean;
fallbackPermissions: Array<string>;
permissions: Array<DocumentPermissionPresentationModel | DocumentPropertyValuePermissionPresentationModel | UnknownTypePermissionPresentationModel>;
permissions: Array<DocumentPermissionPresentationModel | DocumentPropertyValuePermissionPresentationModel | DocumentTypePermissionPresentationModel | UnknownTypePermissionPresentationModel>;
};

export type UpdateUserGroupsOnUserRequestModel = {
Expand Down Expand Up @@ -2884,7 +2932,7 @@ export type UserGroupResponseModel = {
mediaStartNode?: ReferenceByIdModel | null;
mediaRootAccess: boolean;
fallbackPermissions: Array<string>;
permissions: Array<DocumentPermissionPresentationModel | DocumentPropertyValuePermissionPresentationModel | UnknownTypePermissionPresentationModel>;
permissions: Array<DocumentPermissionPresentationModel | DocumentPropertyValuePermissionPresentationModel | DocumentTypePermissionPresentationModel | UnknownTypePermissionPresentationModel>;
id: string;
isDeletable: boolean;
aliasCanBeChanged: boolean;
Expand Down Expand Up @@ -3771,7 +3819,7 @@ export type GetTreeDataTypeSiblingsResponses = {
/**
* OK
*/
200: Array<DataTypeTreeItemResponseModel>;
200: SubsetDataTypeTreeItemResponseModel;
};

export type GetTreeDataTypeSiblingsResponse = GetTreeDataTypeSiblingsResponses[keyof GetTreeDataTypeSiblingsResponses];
Expand Down Expand Up @@ -4709,7 +4757,7 @@ export type GetTreeDocumentBlueprintSiblingsResponses = {
/**
* OK
*/
200: Array<DocumentBlueprintTreeItemResponseModel>;
200: SubsetDocumentBlueprintTreeItemResponseModel;
};

export type GetTreeDocumentBlueprintSiblingsResponse = GetTreeDocumentBlueprintSiblingsResponses[keyof GetTreeDocumentBlueprintSiblingsResponses];
Expand Down Expand Up @@ -5550,7 +5598,7 @@ export type GetTreeDocumentTypeSiblingsResponses = {
/**
* OK
*/
200: Array<DocumentTypeTreeItemResponseModel>;
200: SubsetDocumentTypeTreeItemResponseModel;
};

export type GetTreeDocumentTypeSiblingsResponse = GetTreeDocumentTypeSiblingsResponses[keyof GetTreeDocumentTypeSiblingsResponses];
Expand Down Expand Up @@ -7156,6 +7204,7 @@ export type GetTreeDocumentSiblingsData = {
target?: string;
before?: number;
after?: number;
dataTypeId?: string;
};
url: '/umbraco/management/api/v1/tree/document/siblings';
};
Expand All @@ -7175,7 +7224,7 @@ export type GetTreeDocumentSiblingsResponses = {
/**
* OK
*/
200: Array<DocumentTreeItemResponseModel>;
200: SubsetDocumentTreeItemResponseModel;
};

export type GetTreeDocumentSiblingsResponse = GetTreeDocumentSiblingsResponses[keyof GetTreeDocumentSiblingsResponses];
Expand Down Expand Up @@ -9070,7 +9119,7 @@ export type GetTreeMediaTypeSiblingsResponses = {
/**
* OK
*/
200: Array<MediaTypeTreeItemResponseModel>;
200: SubsetMediaTypeTreeItemResponseModel;
};

export type GetTreeMediaTypeSiblingsResponse = GetTreeMediaTypeSiblingsResponses[keyof GetTreeMediaTypeSiblingsResponses];
Expand Down Expand Up @@ -10016,6 +10065,7 @@ export type GetTreeMediaSiblingsData = {
target?: string;
before?: number;
after?: number;
dataTypeId?: string;
};
url: '/umbraco/management/api/v1/tree/media/siblings';
};
Expand All @@ -10035,7 +10085,7 @@ export type GetTreeMediaSiblingsResponses = {
/**
* OK
*/
200: Array<MediaTreeItemResponseModel>;
200: SubsetMediaTreeItemResponseModel;
};

export type GetTreeMediaSiblingsResponse = GetTreeMediaSiblingsResponses[keyof GetTreeMediaSiblingsResponses];
Expand Down Expand Up @@ -14170,7 +14220,7 @@ export type GetTreeTemplateSiblingsResponses = {
/**
* OK
*/
200: Array<NamedEntityTreeItemResponseModel>;
200: SubsetNamedEntityTreeItemResponseModel;
};

export type GetTreeTemplateSiblingsResponse = GetTreeTemplateSiblingsResponses[keyof GetTreeTemplateSiblingsResponses];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@ import type { UmbDataSourceErrorResponse, UmbDataSourceResponse } from './data-s
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';

export interface UmbPagedModel<T> {
total: number;
items: Array<T>;
total: number;
}

export interface UmbTargetPagedModel<T> extends UmbPagedModel<T> {
// TODO: v18: make mandatory
totalAfter?: number;
totalBefore?: number;
}

// eslint-disable-next-line @typescript-eslint/no-empty-object-type
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './tree-load-more-button.element.js';
export * from './tree-load-prev-button.element.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { css, customElement, html } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';

@customElement('umb-tree-load-prev-button')
export class UmbTreeLoadPrevButtonElement extends UmbLitElement {
override render() {
return html`<uui-button
data-mark="tree:load-prev"
id="load-prev"
look="secondary"
label=${this.localize.term('actions_loadMore')}></uui-button>`;
}

static override readonly styles = css`
:host {
position: relative;
display: block;
padding-left: var(--uui-size-space-3);
margin-right: var(--uui-size-space-2);
margin-left: calc(var(--uui-menu-item-indent, 0) * var(--uui-size-4));
}

uui-button {
width: 100%;
height: var(--uui-size---uui-size-layout-3);
--uui-box-border-radius: calc(var(--uui-border-radius) * 2);
}
`;
}

declare global {
interface HTMLElementTagNameMap {
'umb-tree-load-prev-button': UmbTreeLoadPrevButtonElement;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {
UmbTreeChildrenOfRequestArgs,
UmbTreeRootItemsRequestArgs,
} from './types.js';
import type { UmbPagedModel, UmbDataSourceResponse } from '@umbraco-cms/backoffice/repository';
import type { UmbDataSourceResponse, UmbTargetPagedModel } from '@umbraco-cms/backoffice/repository';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';

/**
Expand Down Expand Up @@ -32,18 +32,18 @@ export interface UmbTreeDataSource<
* @returns {*} {Promise<UmbDataSourceResponse<UmbPagedModel<TreeItemType>>>}
* @memberof UmbTreeDataSource
*/
getRootItems(args: TreeRootItemsRequestArgsType): Promise<UmbDataSourceResponse<UmbPagedModel<TreeItemType>>>;
getRootItems(args: TreeRootItemsRequestArgsType): Promise<UmbDataSourceResponse<UmbTargetPagedModel<TreeItemType>>>;

/**
* Gets the children of the given parent item.
* @returns {*} {Promise<UmbDataSourceResponse<UmbPagedModel<TreeItemType>>}
* @returns {Promise<UmbDataSourceResponse<UmbPagedModel<TreeItemType>>>}
* @memberof UmbTreeDataSource
*/
getChildrenOf(args: TreeChildrenOfRequestArgsType): Promise<UmbDataSourceResponse<UmbPagedModel<TreeItemType>>>;
getChildrenOf(args: TreeChildrenOfRequestArgsType): Promise<UmbDataSourceResponse<UmbTargetPagedModel<TreeItemType>>>;

/**
* Gets the ancestors of the given item.
* @returns {*} {Promise<UmbDataSourceResponse<Array<TreeItemType>>}
* @returns {Promise<UmbDataSourceResponse<Array<TreeItemType>>>}
* @memberof UmbTreeDataSource
*/
getAncestorsOf(args: TreeAncestorsOfRequestArgsType): Promise<UmbDataSourceResponse<Array<TreeItemType>>>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import type {
UmbTreeRootItemsRequestArgs,
} from './types.js';
import type {
UmbPagedModel,
UmbRepositoryResponse,
UmbRepositoryResponseWithAsObservable,
UmbTargetPagedModel,
} from '@umbraco-cms/backoffice/repository';
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
Expand Down Expand Up @@ -39,7 +39,7 @@ export interface UmbTreeRepository<
*/
requestTreeRootItems: (
args: TreeRootItemsRequestArgsType,
) => Promise<UmbRepositoryResponseWithAsObservable<UmbPagedModel<TreeItemType>, TreeItemType[]>>;
) => Promise<UmbRepositoryResponseWithAsObservable<UmbTargetPagedModel<TreeItemType>, TreeItemType[]>>;

/**
* Requests the children of the given parent item.
Expand All @@ -48,7 +48,7 @@ export interface UmbTreeRepository<
*/
requestTreeItemsOf: (
args: TreeChildrenOfRequestArgsType,
) => Promise<UmbRepositoryResponseWithAsObservable<UmbPagedModel<TreeItemType>, TreeItemType[]>>;
) => Promise<UmbRepositoryResponseWithAsObservable<UmbTargetPagedModel<TreeItemType>, TreeItemType[]>>;

/**
* Requests the ancestors of the given item.
Expand Down
Loading