Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
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
9 changes: 9 additions & 0 deletions src/Umbraco.Web.UI.Client/examples/tree/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Tree Example

This example demonstrates how to register a tree

The example includes:

- Tree Registration
- A Dashboard to show how to render a tree anywhere in the backoffice
- How to use the tree in the sidebar menu
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { EXAMPLE_TREE_ALIAS } from '../tree/constants.js';
import { html, customElement, LitElement, css } from '@umbraco-cms/backoffice/external/lit';
import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';

@customElement('example-dashboard-with-tree')
export class ExampleDashboardWithTree extends UmbElementMixin(LitElement) {
override render() {
return html`<uui-box><umb-tree alias=${EXAMPLE_TREE_ALIAS}></umb-tree></uui-box>`;
}

static override styles = [
css`
:host {
display: block;
padding: var(--uui-size-layout-1);
}
`,
];
}

export { ExampleDashboardWithTree as element };

declare global {
interface HTMLElementTagNameMap {
'example-dashboard-with-tree': ExampleDashboardWithTree;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'dashboard',
kind: 'default',
name: 'Example Dashboard With Tree',
alias: 'Example.Dashboard.WithTree',
element: () => import('./dashboard-with-tree.element.js'),
weight: 3000,
meta: {
label: 'Tree Example',
pathname: 'tree-example',
},
},
];
5 changes: 5 additions & 0 deletions src/Umbraco.Web.UI.Client/examples/tree/entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const EXAMPLE_ENTITY_TYPE = 'example';
export const EXAMPLE_ROOT_ENTITY_TYPE = 'example-root';

export type ExampleEntityType = typeof EXAMPLE_ENTITY_TYPE;
export type ExampleRootEntityType = typeof EXAMPLE_ROOT_ENTITY_TYPE;
5 changes: 5 additions & 0 deletions src/Umbraco.Web.UI.Client/examples/tree/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { manifests as dashboardManifests } from './dashboard-with-tree/manifests.js';
import { manifests as menuItemManifests } from './menu-item-with-tree/manifests.js';
import { manifests as treeManifests } from './tree/manifests.js';

export const manifests: Array<UmbExtensionManifest> = [...dashboardManifests, ...menuItemManifests, ...treeManifests];
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { EXAMPLE_TREE_ALIAS } from '../tree/constants.js';
import { UMB_CONTENT_MENU_ALIAS } from '@umbraco-cms/backoffice/document';

export const manifests: Array<UmbExtensionManifest> = [
{
type: 'menuItem',
kind: 'tree',
alias: 'Example.MenuItem.Tree',
name: 'Example Tree Menu Item',
weight: 1000,
meta: {
label: 'Example Tree',
menus: [UMB_CONTENT_MENU_ALIAS],
treeAlias: EXAMPLE_TREE_ALIAS,
},
},
];
1 change: 1 addition & 0 deletions src/Umbraco.Web.UI.Client/examples/tree/tree/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const EXAMPLE_TREE_ALIAS = 'Example.Tree';
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './repository/constants.js';
export * from './store/constants.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './tree.local.data-source.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import type { ExampleTreeItemModel } from '../../types.js';
import { EXAMPLE_ENTITY_TYPE, EXAMPLE_ROOT_ENTITY_TYPE } from '../../../entity.js';
import type {
UmbTreeAncestorsOfRequestArgs,
UmbTreeChildrenOfRequestArgs,
UmbTreeDataSource,
UmbTreeRootItemsRequestArgs,
} from '@umbraco-cms/backoffice/tree';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';

const EXAMPLE_TREE_DATA: Array<ExampleTreeItemModel> = [
{
entityType: EXAMPLE_ENTITY_TYPE,
hasChildren: false,
isFolder: false,
name: 'Item 1',
parent: { unique: null, entityType: EXAMPLE_ROOT_ENTITY_TYPE },
unique: 'ab7b6e03-5f4d-4a6b-9f4c-21292d462e08',
icon: 'icon-newspaper',
},
{
entityType: EXAMPLE_ENTITY_TYPE,
hasChildren: true,
isFolder: false,
name: 'Item 2',
parent: { unique: null, entityType: EXAMPLE_ROOT_ENTITY_TYPE },
unique: '74a5b2d9-3564-45b8-a3ee-98fc7ec0c1fb',
icon: 'icon-newspaper',
},
{
entityType: EXAMPLE_ENTITY_TYPE,
hasChildren: false,
isFolder: false,
name: 'Item 3',
parent: { unique: null, entityType: EXAMPLE_ROOT_ENTITY_TYPE },
unique: '1b8ed2ac-b4bb-4384-972e-2cf18f40586a',
icon: 'icon-newspaper',
},
{
entityType: EXAMPLE_ENTITY_TYPE,
hasChildren: false,
isFolder: false,
name: 'Item 2.1',
parent: { unique: '74a5b2d9-3564-45b8-a3ee-98fc7ec0c1fb', entityType: EXAMPLE_ENTITY_TYPE },
unique: '62dbd672-b198-4fc8-8b42-5d21dfbd3788',
icon: 'icon-newspaper',
},
{
entityType: EXAMPLE_ENTITY_TYPE,
hasChildren: true,
isFolder: false,
name: 'Item 2.2',
parent: { unique: '74a5b2d9-3564-45b8-a3ee-98fc7ec0c1fb', entityType: EXAMPLE_ENTITY_TYPE },
unique: 'deaa3f8c-e40b-4eb7-8268-34014504152e',
icon: 'icon-newspaper',
},
{
entityType: EXAMPLE_ENTITY_TYPE,
hasChildren: false,
isFolder: false,
name: 'Item 2.2.1',
parent: { unique: 'deaa3f8c-e40b-4eb7-8268-34014504152e', entityType: EXAMPLE_ENTITY_TYPE },
unique: 'd4cf5fd2-1f84-4f3b-b63b-5f29a38e72d1',
icon: 'icon-newspaper',
},
];

export class ExampleTreeLocalDataSource extends UmbControllerBase implements UmbTreeDataSource<ExampleTreeItemModel> {
async getRootItems(args: UmbTreeRootItemsRequestArgs) {
// TODO: handle skip, take, foldersOnly.
console.log(args);
const rootItems: Array<ExampleTreeItemModel> = EXAMPLE_TREE_DATA.filter((item) => item.parent.unique === null);
return { data: { items: rootItems, total: rootItems.length } };
}

async getChildrenOf(args: UmbTreeChildrenOfRequestArgs) {
// TODO: handle skip, take, foldersOnly.
const children = EXAMPLE_TREE_DATA.filter(
(item) => item.parent.unique === args.parent.unique && item.parent.entityType === args.parent.entityType,
);

return { data: { items: children, total: children.length } };
}

async getAncestorsOf(args: UmbTreeAncestorsOfRequestArgs) {
const ancestors = findAncestors(args.treeItem.unique, args.treeItem.entityType);
return { data: ancestors };
}
}

// Helper function to find ancestors recursively
const findAncestors = (unique: string, entityType: string): Array<ExampleTreeItemModel> => {
const item = EXAMPLE_TREE_DATA.find((i) => i.unique === unique && i.entityType === entityType);

if (!item || !item.parent || item.parent.unique === null) {

Check warning on line 95 in src/Umbraco.Web.UI.Client/examples/tree/tree/data/local-data-source/tree.local.data-source.ts

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

❌ New issue: Complex Conditional

findAncestors has 1 complex conditionals with 2 branches, threshold = 2. A complex conditional is an expression inside a branch (e.g. if, for, while) which consists of multiple, logical operators such as AND/OR. The more logical operators in an expression, the more severe the code smell.
return [];
}

const parent = EXAMPLE_TREE_DATA.find(
(i) => i.unique === item.parent.unique && i.entityType === item.parent.entityType,
);

if (!parent) {
return [];
}

return [parent, ...findAncestors(parent.unique, parent.entityType)];
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { manifests as repositoryManifests } from './repository/manifests.js';
import { manifests as storeManifests } from './store/manifests.js';

export const manifests: Array<UmbExtensionManifest> = [...repositoryManifests, ...storeManifests];
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const EXAMPLE_TREE_REPOSITORY_ALIAS = 'Example.Repository.Tree';
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { EXAMPLE_TREE_REPOSITORY_ALIAS } from './constants.js';

export const manifests: Array<UmbExtensionManifest> = [
{
type: 'repository',
alias: EXAMPLE_TREE_REPOSITORY_ALIAS,
name: 'Example Tree Repository',
api: () => import('./tree.repository.js'),
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { ExampleTreeItemModel, ExampleTreeRootModel } from '../../types.js';
import { EXAMPLE_ROOT_ENTITY_TYPE } from '../../../entity.js';
import { EXAMPLE_TREE_STORE_CONTEXT } from '../store/index.js';
import { ExampleTreeLocalDataSource } from '../local-data-source/index.js';
import { UmbTreeRepositoryBase, type UmbTreeRepository } from '@umbraco-cms/backoffice/tree';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';

export class ExampleTreeRepository
extends UmbTreeRepositoryBase<ExampleTreeItemModel, ExampleTreeRootModel>
implements UmbTreeRepository, UmbApi
{
constructor(host: UmbControllerHost) {
super(host, ExampleTreeLocalDataSource, EXAMPLE_TREE_STORE_CONTEXT);
}

async requestTreeRoot() {
const root: ExampleTreeRootModel = {
unique: null,
entityType: EXAMPLE_ROOT_ENTITY_TYPE,
name: 'Example Tree',
hasChildren: true,
isFolder: true,
};

return { data: root };
}
}

export { ExampleTreeRepository as api };
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const EXAMPLE_TREE_STORE_ALIAS = 'Example.Store.Tree';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './tree.store.context-token.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { EXAMPLE_TREE_STORE_ALIAS } from './constants.js';

export const manifests: Array<UmbExtensionManifest> = [
{
type: 'treeStore',
alias: EXAMPLE_TREE_STORE_ALIAS,
name: 'Example Tree Store',
api: () => import('./tree.store.js'),
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { ExampleTreeStore } from './tree.store.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';

export const EXAMPLE_TREE_STORE_CONTEXT = new UmbContextToken<ExampleTreeStore>('ExampleTreeStore');
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { EXAMPLE_TREE_STORE_CONTEXT } from './tree.store.context-token.js';
import { UmbUniqueTreeStore } from '@umbraco-cms/backoffice/tree';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';

export class ExampleTreeStore extends UmbUniqueTreeStore {
constructor(host: UmbControllerHost) {
super(host, EXAMPLE_TREE_STORE_CONTEXT.toString());
}
}

export { ExampleTreeStore as api };
24 changes: 24 additions & 0 deletions src/Umbraco.Web.UI.Client/examples/tree/tree/manifests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { EXAMPLE_ENTITY_TYPE, EXAMPLE_ROOT_ENTITY_TYPE } from '../entity.js';
import { EXAMPLE_TREE_ALIAS } from './constants.js';
import { EXAMPLE_TREE_REPOSITORY_ALIAS } from './data/constants.js';
import { manifests as dataManifests } from './data/manifests.js';

export const manifests: Array<UmbExtensionManifest> = [
{
type: 'tree',
kind: 'default',
alias: EXAMPLE_TREE_ALIAS,
name: 'Example Tree',
meta: {
repositoryAlias: EXAMPLE_TREE_REPOSITORY_ALIAS,
},
},
{
type: 'treeItem',
kind: 'default',
alias: 'Example.TreeItem',
name: 'Example Tree Item',
forEntityTypes: [EXAMPLE_ROOT_ENTITY_TYPE, EXAMPLE_ENTITY_TYPE],
},
...dataManifests,
];
10 changes: 10 additions & 0 deletions src/Umbraco.Web.UI.Client/examples/tree/tree/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { ExampleEntityType, ExampleRootEntityType } from '../entity.js';
import type { UmbTreeItemModel, UmbTreeRootModel } from '@umbraco-cms/backoffice/tree';

export interface ExampleTreeItemModel extends UmbTreeItemModel {
entityType: ExampleEntityType;
}

export interface ExampleTreeRootModel extends UmbTreeRootModel {
entityType: ExampleRootEntityType;
}
Loading