Merge branch 'main' into feature/remove-parent-from-detail-model
This commit is contained in:
4
src/Umbraco.Web.UI.Client/package-lock.json
generated
4
src/Umbraco.Web.UI.Client/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@umbraco-cms/backoffice",
|
||||
"version": "14.0.0--preview008",
|
||||
"version": "14.0.0--beta001",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@umbraco-cms/backoffice",
|
||||
"version": "14.0.0--preview008",
|
||||
"version": "14.0.0--beta001",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@openid/appauth": "^1.3.1",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@umbraco-cms/backoffice",
|
||||
"license": "MIT",
|
||||
"version": "14.0.0--preview008",
|
||||
"version": "14.0.0--beta001",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": null,
|
||||
|
||||
@@ -244,9 +244,8 @@ export abstract class UmbBaseExtensionInitializer<
|
||||
const newPermission = await this._conditionsAreGood();
|
||||
// Only set new permission if we are still positive, otherwise it means that we have been destroyed in the mean time.
|
||||
if (newPermission === false || this._isConditionsPositive === false) {
|
||||
console.warn(
|
||||
'If this happens then please inform Niels Lyngsø on CMS Team. We are still investigating wether this is a situation we should handle. Ref. No.: 1.',
|
||||
);
|
||||
// Then we need to revert the above work:
|
||||
this._conditionsAreBad();
|
||||
return;
|
||||
}
|
||||
// We update the oldValue as this point, cause in this way we are sure its the value at this point, when doing async code someone else might have changed the state in the mean time.
|
||||
@@ -259,9 +258,6 @@ export abstract class UmbBaseExtensionInitializer<
|
||||
|
||||
// Only continue if we are still negative, otherwise it means that something changed in the mean time.
|
||||
if (this._isConditionsPositive === true) {
|
||||
console.warn(
|
||||
'If this happens then please inform Niels Lyngsø on CMS Team. We are still investigating wether this is a situation we should handle. Ref. No.: 2.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
// We update the oldValue as this point, cause in this way we are sure its the value at this point, when doing async code someone else might have changed the state in the mean time.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { pagedResult } from '../paged-result.js';
|
||||
import type { UmbEntityMockDbBase } from './entity-base.js';
|
||||
import { UmbId } from '@umbraco-cms/backoffice/id';
|
||||
import type { EntityTreeItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
@@ -11,18 +12,18 @@ export class UmbMockEntityTreeManager<T extends Omit<EntityTreeItemResponseModel
|
||||
this.#treeItemMapper = treeItemMapper;
|
||||
}
|
||||
|
||||
getRoot() {
|
||||
getRoot({ skip = 0, take = 100 }: { skip?: number; take?: number } = {}) {
|
||||
const items = this.#db.getAll().filter((item) => item.parent === null);
|
||||
const treeItems = items.map((item) => this.#treeItemMapper(item));
|
||||
const total = items.length;
|
||||
return { items: treeItems, total };
|
||||
const paged = pagedResult(items, skip, take);
|
||||
const treeItems = paged.items.map((item) => this.#treeItemMapper(item));
|
||||
return { items: treeItems, total: paged.total };
|
||||
}
|
||||
|
||||
getChildrenOf(parentId: string) {
|
||||
getChildrenOf({ parentId, skip = 0, take = 100 }: { parentId: string; skip?: number; take?: number }) {
|
||||
const items = this.#db.getAll().filter((item) => item.parent?.id === parentId);
|
||||
const treeItems = items.map((item) => this.#treeItemMapper(item));
|
||||
const total = items.length;
|
||||
return { items: treeItems, total };
|
||||
const paged = pagedResult(items, skip, take);
|
||||
const treeItems = paged.items.map((item) => this.#treeItemMapper(item));
|
||||
return { items: treeItems, total: paged.total };
|
||||
}
|
||||
|
||||
move(ids: Array<string>, destinationId: string) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { UmbMockDBBase } from '../mock-db-base.js';
|
||||
import { createFileSystemTreeItem } from '../../utils.js';
|
||||
import { pagedResult } from '../paged-result.js';
|
||||
import type { FileSystemTreeItemPresentationModel } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
|
||||
export class UmbMockFileSystemTreeManager<T extends Omit<FileSystemTreeItemPresentationModel, 'type'>> {
|
||||
@@ -9,20 +10,23 @@ export class UmbMockFileSystemTreeManager<T extends Omit<FileSystemTreeItemPrese
|
||||
this.#db = mockDb;
|
||||
}
|
||||
|
||||
getRoot(): { items: Array<Omit<FileSystemTreeItemPresentationModel, 'type'>>; total: number } {
|
||||
getRoot({ skip = 0, take = 100 }: { skip?: number; take?: number } = {}): {
|
||||
items: Array<Omit<FileSystemTreeItemPresentationModel, 'type'>>;
|
||||
total: number;
|
||||
} {
|
||||
const items = this.#db.getAll().filter((item) => item.parent === null);
|
||||
const treeItems = items.map((item) => createFileSystemTreeItem(item));
|
||||
const total = items.length;
|
||||
return { items: treeItems, total };
|
||||
const paged = pagedResult(items, skip, take);
|
||||
const treeItems = paged.items.map((item) => createFileSystemTreeItem(item));
|
||||
return { items: treeItems, total: paged.total };
|
||||
}
|
||||
|
||||
getChildrenOf(parentPath: string): {
|
||||
getChildrenOf({ parentPath, skip = 0, take = 100 }: { parentPath: string; skip?: number; take?: number }): {
|
||||
items: Array<Omit<FileSystemTreeItemPresentationModel, 'type'>>;
|
||||
total: number;
|
||||
} {
|
||||
const items = this.#db.getAll().filter((item) => item.parent?.path === parentPath);
|
||||
const treeItems = items.map((item) => createFileSystemTreeItem(item));
|
||||
const total = items.length;
|
||||
return { items: treeItems, total };
|
||||
const paged = pagedResult(items, skip, take);
|
||||
const treeItems = paged.items.map((item) => createFileSystemTreeItem(item));
|
||||
return { items: treeItems, total: paged.total };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,18 @@ import { umbracoPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
export const treeHandlers = [
|
||||
rest.get(umbracoPath(`/tree${UMB_SLUG}/root`), (req, res, ctx) => {
|
||||
const response = umbDataTypeMockDb.tree.getRoot();
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbDataTypeMockDb.tree.getRoot({ skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
|
||||
rest.get(umbracoPath(`/tree${UMB_SLUG}/children`), (req, res, ctx) => {
|
||||
const parentId = req.url.searchParams.get('parentId');
|
||||
if (!parentId) return;
|
||||
const response = umbDataTypeMockDb.tree.getChildrenOf(parentId);
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbDataTypeMockDb.tree.getChildrenOf({ parentId, skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -5,14 +5,18 @@ import { umbracoPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
export const treeHandlers = [
|
||||
rest.get(umbracoPath(`/tree${UMB_SLUG}/root`), (req, res, ctx) => {
|
||||
const response = umbDictionaryMockDb.tree.getRoot();
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbDictionaryMockDb.tree.getRoot({ skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
|
||||
rest.get(umbracoPath(`/tree${UMB_SLUG}/children`), (req, res, ctx) => {
|
||||
const parentId = req.url.searchParams.get('parentId');
|
||||
if (!parentId) return;
|
||||
const response = umbDictionaryMockDb.tree.getChildrenOf(parentId);
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbDictionaryMockDb.tree.getChildrenOf({ parentId, skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -5,14 +5,18 @@ import { umbracoPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
export const treeHandlers = [
|
||||
rest.get(umbracoPath(`/tree${UMB_SLUG}/root`), (req, res, ctx) => {
|
||||
const response = umbDocumentTypeMockDb.tree.getRoot();
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbDocumentTypeMockDb.tree.getRoot({ skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
|
||||
rest.get(umbracoPath(`/tree${UMB_SLUG}/children`), (req, res, ctx) => {
|
||||
const parentId = req.url.searchParams.get('parentId');
|
||||
if (!parentId) return;
|
||||
const response = umbDocumentTypeMockDb.tree.getChildrenOf(parentId);
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbDocumentTypeMockDb.tree.getChildrenOf({ parentId, skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -5,14 +5,18 @@ import { umbracoPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
export const recycleBinHandlers = [
|
||||
rest.get(umbracoPath(`/recycle-bin${UMB_SLUG}/root`), (req, res, ctx) => {
|
||||
const response = umbDocumentMockDb.recycleBin.tree.getRoot();
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbDocumentMockDb.recycleBin.tree.getRoot({ skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
|
||||
rest.get(umbracoPath(`/recycle-bin${UMB_SLUG}/children`), (req, res, ctx) => {
|
||||
const parentId = req.url.searchParams.get('parentId');
|
||||
if (!parentId) return;
|
||||
const response = umbDocumentMockDb.recycleBin.tree.getChildrenOf(parentId);
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbDocumentMockDb.recycleBin.tree.getChildrenOf({ parentId, skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
|
||||
|
||||
@@ -5,14 +5,18 @@ import { umbracoPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
export const treeHandlers = [
|
||||
rest.get(umbracoPath(`/tree${UMB_SLUG}/root`), (req, res, ctx) => {
|
||||
const response = umbDocumentMockDb.tree.getRoot();
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbDocumentMockDb.tree.getRoot({ skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
|
||||
rest.get(umbracoPath(`/tree${UMB_SLUG}/children`), (req, res, ctx) => {
|
||||
const parentId = req.url.searchParams.get('parentId');
|
||||
if (!parentId) return;
|
||||
const response = umbDocumentMockDb.tree.getChildrenOf(parentId);
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbDocumentMockDb.tree.getChildrenOf({ parentId, skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -5,14 +5,18 @@ import { umbracoPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
export const treeHandlers = [
|
||||
rest.get(umbracoPath(`/tree${UMB_SLUG}/root`), (req, res, ctx) => {
|
||||
const response = umbMediaTypeMockDb.tree.getRoot();
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbMediaTypeMockDb.tree.getRoot({ skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
|
||||
rest.get(umbracoPath(`/tree${UMB_SLUG}/children`), (req, res, ctx) => {
|
||||
const parentId = req.url.searchParams.get('parentId');
|
||||
if (!parentId) return;
|
||||
const response = umbMediaTypeMockDb.tree.getChildrenOf(parentId);
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbMediaTypeMockDb.tree.getChildrenOf({ parentId, skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -5,14 +5,18 @@ import { umbracoPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
export const recycleBinHandlers = [
|
||||
rest.get(umbracoPath(`/recycle-bin${UMB_SLUG}/root`), (req, res, ctx) => {
|
||||
const response = umbMediaMockDb.recycleBin.tree.getRoot();
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbMediaMockDb.recycleBin.tree.getRoot({ skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
|
||||
rest.get(umbracoPath(`/recycle-bin${UMB_SLUG}/children`), (req, res, ctx) => {
|
||||
const parentId = req.url.searchParams.get('parentId');
|
||||
if (!parentId) return;
|
||||
const response = umbMediaMockDb.recycleBin.tree.getChildrenOf(parentId);
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbMediaMockDb.recycleBin.tree.getChildrenOf({ parentId, skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
|
||||
|
||||
@@ -5,14 +5,18 @@ import { umbracoPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
export const treeHandlers = [
|
||||
rest.get(umbracoPath(`/tree${UMB_SLUG}/root`), (req, res, ctx) => {
|
||||
const response = umbMediaMockDb.tree.getRoot();
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbMediaMockDb.tree.getRoot({ skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
|
||||
rest.get(umbracoPath(`/tree${UMB_SLUG}/children`), (req, res, ctx) => {
|
||||
const parentId = req.url.searchParams.get('parentId');
|
||||
if (!parentId) return;
|
||||
const response = umbMediaMockDb.tree.getChildrenOf(parentId);
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbMediaMockDb.tree.getChildrenOf({ parentId, skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -5,14 +5,18 @@ import { umbracoPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
export const treeHandlers = [
|
||||
rest.get(umbracoPath(`/tree${UMB_SLUG}/root`), (req, res, ctx) => {
|
||||
const response = umbMemberTypeMockDb.tree.getRoot();
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbMemberTypeMockDb.tree.getRoot({ skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
|
||||
rest.get(umbracoPath(`/tree${UMB_SLUG}/children`), (req, res, ctx) => {
|
||||
const parentId = req.url.searchParams.get('parentId');
|
||||
if (!parentId) return;
|
||||
const response = umbMemberTypeMockDb.tree.getChildrenOf(parentId);
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbMemberTypeMockDb.tree.getChildrenOf({ parentId, skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -5,14 +5,18 @@ import { umbracoPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
export const treeHandlers = [
|
||||
rest.get(umbracoPath(`/tree${UMB_SLUG}/root`), (req, res, ctx) => {
|
||||
const response = umbPartialViewMockDB.tree.getRoot();
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbPartialViewMockDB.tree.getRoot({ skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
|
||||
rest.get(umbracoPath(`/tree${UMB_SLUG}/children`), (req, res, ctx) => {
|
||||
const parentPath = req.url.searchParams.get('parentPath');
|
||||
if (!parentPath) return res(ctx.status(400));
|
||||
const response = umbPartialViewMockDB.tree.getChildrenOf(parentPath);
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbPartialViewMockDB.tree.getChildrenOf({ parentPath, skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -5,14 +5,18 @@ import { umbracoPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
export const treeHandlers = [
|
||||
rest.get(umbracoPath(`/tree${UMB_SLUG}/root`), (req, res, ctx) => {
|
||||
const response = umbScriptMockDb.tree.getRoot();
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbScriptMockDb.tree.getRoot({ skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
|
||||
rest.get(umbracoPath(`/tree${UMB_SLUG}/children`), (req, res, ctx) => {
|
||||
const parentPath = req.url.searchParams.get('parentPath');
|
||||
if (!parentPath) return res(ctx.status(400));
|
||||
const response = umbScriptMockDb.tree.getChildrenOf(parentPath);
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbScriptMockDb.tree.getChildrenOf({ parentPath, skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -5,14 +5,18 @@ import { umbracoPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
export const treeHandlers = [
|
||||
rest.get(umbracoPath(`/tree${UMB_SLUG}/root`), (req, res, ctx) => {
|
||||
const response = umbStaticFileMockDb.tree.getRoot();
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbStaticFileMockDb.tree.getRoot({ skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
|
||||
rest.get(umbracoPath(`/tree${UMB_SLUG}/children`), (req, res, ctx) => {
|
||||
const parentPath = req.url.searchParams.get('parentPath');
|
||||
if (!parentPath) return res(ctx.status(400));
|
||||
const response = umbStaticFileMockDb.tree.getChildrenOf(parentPath);
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbStaticFileMockDb.tree.getChildrenOf({ parentPath, skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -5,14 +5,18 @@ import { umbracoPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
export const treeHandlers = [
|
||||
rest.get(umbracoPath(`/tree${UMB_SLUG}/root`), (req, res, ctx) => {
|
||||
const response = umbStylesheetMockDb.tree.getRoot();
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbStylesheetMockDb.tree.getRoot({ skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
|
||||
rest.get(umbracoPath(`/tree${UMB_SLUG}/children`), (req, res, ctx) => {
|
||||
const parentPath = req.url.searchParams.get('parentPath');
|
||||
if (!parentPath) return res(ctx.status(400));
|
||||
const response = umbStylesheetMockDb.tree.getChildrenOf(parentPath);
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbStylesheetMockDb.tree.getChildrenOf({ parentPath, skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -5,14 +5,18 @@ import { umbracoPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
export const treeHandlers = [
|
||||
rest.get(umbracoPath(`/tree${UMB_SLUG}/root`), (req, res, ctx) => {
|
||||
const response = umbTemplateMockDb.tree.getRoot();
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbTemplateMockDb.tree.getRoot({ skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
|
||||
rest.get(umbracoPath(`/tree${UMB_SLUG}/children`), (req, res, ctx) => {
|
||||
const parentId = req.url.searchParams.get('parentId');
|
||||
if (!parentId) return;
|
||||
const response = umbTemplateMockDb.tree.getChildrenOf(parentId);
|
||||
const skip = Number(req.url.searchParams.get('skip'));
|
||||
const take = Number(req.url.searchParams.get('take'));
|
||||
const response = umbTemplateMockDb.tree.getChildrenOf({ parentId, skip, take });
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -119,7 +119,8 @@ export class UmbBlockGridAreaTypeWorkspaceContext
|
||||
context.setValue(appendToFrozenArray(context.getValue() ?? [], this.#data.getValue(), (x) => x?.key));
|
||||
});
|
||||
|
||||
this.saveComplete(this.#data.value);
|
||||
this.setIsNew(false);
|
||||
this.workspaceComplete(this.#data.value);
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
|
||||
@@ -14,6 +14,11 @@ export const manifest: ManifestPropertyEditorUi = {
|
||||
group: 'richContent',
|
||||
settings: {
|
||||
properties: [
|
||||
{
|
||||
alias: 'blockGroups',
|
||||
label: '',
|
||||
propertyEditorUiAlias: 'Umb.PropertyEditorUi.BlockTypeGroupConfiguration',
|
||||
},
|
||||
{
|
||||
alias: 'useLiveEditing',
|
||||
label: 'Live editing mode',
|
||||
|
||||
@@ -7,7 +7,6 @@ export const manifest: ManifestPropertyEditorUi = {
|
||||
js: () => import('./property-editor-ui-block-grid-type-configuration.element.js'),
|
||||
meta: {
|
||||
label: 'Block Grid Block Configuration',
|
||||
propertyEditorSchemaAlias: 'Umbraco.BlockGrid.BlockConfiguration',
|
||||
icon: 'icon-autofill',
|
||||
group: 'blocks',
|
||||
},
|
||||
|
||||
@@ -7,7 +7,6 @@ export const manifest: ManifestPropertyEditorUi = {
|
||||
js: () => import('./property-editor-ui-block-list-type-configuration.element.js'),
|
||||
meta: {
|
||||
label: 'Block List Type Configuration',
|
||||
propertyEditorSchemaAlias: '',
|
||||
icon: 'icon-autofill',
|
||||
group: 'common',
|
||||
},
|
||||
|
||||
@@ -7,7 +7,6 @@ export const manifest: ManifestPropertyEditorUi = {
|
||||
js: () => import('./property-editor-ui-block-type-group-configuration.element.js'),
|
||||
meta: {
|
||||
label: 'Block Grid Group Configuration',
|
||||
propertyEditorSchemaAlias: 'Umbraco.BlockGrid.GroupConfiguration',
|
||||
icon: 'icon-autofill',
|
||||
group: 'blocks',
|
||||
},
|
||||
|
||||
@@ -121,7 +121,8 @@ export class UmbBlockTypeWorkspaceContext<BlockTypeData extends UmbBlockTypeWith
|
||||
);
|
||||
});
|
||||
|
||||
this.saveComplete(this.#data.value);
|
||||
this.setIsNew(false);
|
||||
this.workspaceComplete(this.#data.value);
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
|
||||
@@ -295,7 +295,8 @@ export class UmbBlockWorkspaceContext<
|
||||
}
|
||||
}
|
||||
|
||||
this.saveComplete(layoutData);
|
||||
this.setIsNew(false);
|
||||
this.workspaceComplete(layoutData);
|
||||
}
|
||||
|
||||
#modalRejected = () => {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
@customElement('umb-collection-pagination')
|
||||
export class UmbCollectionPaginationElement extends UmbLitElement {
|
||||
@state()
|
||||
_totalPages = 0;
|
||||
_totalPages = 1;
|
||||
|
||||
@state()
|
||||
_currentPage = 1;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { UMB_DEFAULT_COLLECTION_CONTEXT, UmbDefaultCollectionContext } from './collection-default.context.js';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
@@ -35,6 +36,11 @@ export class UmbCollectionDefaultElement extends UmbLitElement {
|
||||
});
|
||||
}
|
||||
|
||||
protected firstUpdated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
|
||||
super.firstUpdated(_changedProperties);
|
||||
this.#collectionContext?.requestCollection();
|
||||
}
|
||||
|
||||
#observeCollectionRoutes() {
|
||||
if (!this.#collectionContext) return;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { css, html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
|
||||
@@ -17,25 +17,27 @@ export class UmbInputNumberRangeElement extends FormControlMixin(UmbLitElement)
|
||||
|
||||
@state()
|
||||
private _minValue?: number;
|
||||
@property()
|
||||
public get minValue() {
|
||||
return this._minValue;
|
||||
}
|
||||
|
||||
@property({ type: Number })
|
||||
public set minValue(value: number | undefined) {
|
||||
this._minValue = value;
|
||||
this.updateValue();
|
||||
}
|
||||
public get minValue() {
|
||||
return this._minValue;
|
||||
}
|
||||
|
||||
@state()
|
||||
private _maxValue?: number;
|
||||
@property()
|
||||
public get maxValue() {
|
||||
return this._maxValue;
|
||||
}
|
||||
|
||||
@property({ type: Number })
|
||||
public set maxValue(value: number | undefined) {
|
||||
this._maxValue = value;
|
||||
this.updateValue();
|
||||
}
|
||||
public get maxValue() {
|
||||
return this._maxValue;
|
||||
}
|
||||
|
||||
private updateValue() {
|
||||
const newValue = this._minValue || this._maxValue ? (this._minValue || '') + ',' + (this._maxValue || '') : '';
|
||||
|
||||
@@ -1,32 +1,21 @@
|
||||
import type { UmbMultipleColorPickerItemInputElement } from './multiple-color-picker-item-input.element.js';
|
||||
import type { UmbSwatchDetails } from '@umbraco-cms/backoffice/models';
|
||||
import {
|
||||
css,
|
||||
customElement,
|
||||
html,
|
||||
ifDefined,
|
||||
nothing,
|
||||
repeat,
|
||||
customElement,
|
||||
property,
|
||||
state,
|
||||
ifDefined,
|
||||
} from '@umbraco-cms/backoffice/external/lit';
|
||||
import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { type UmbInputEvent, UmbChangeEvent, type UmbDeleteEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { type UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property';
|
||||
|
||||
const SORTER_CONFIG: UmbSorterConfig<UmbSwatchDetails, UmbMultipleColorPickerItemInputElement> = {
|
||||
getUniqueOfElement: (element) => {
|
||||
return element.value.toString();
|
||||
},
|
||||
getUniqueOfModel: (modelEntry) => {
|
||||
return modelEntry.value;
|
||||
},
|
||||
identifier: 'Umb.SorterIdentifier.ColorEditor',
|
||||
itemSelector: 'umb-multiple-color-picker-item-input',
|
||||
containerSelector: '#sorter-wrapper',
|
||||
};
|
||||
import type { UmbInputEvent, UmbDeleteEvent } from '@umbraco-cms/backoffice/event';
|
||||
import type { UmbSwatchDetails } from '@umbraco-cms/backoffice/models';
|
||||
|
||||
/**
|
||||
* @element umb-multiple-color-picker-input
|
||||
@@ -34,7 +23,15 @@ const SORTER_CONFIG: UmbSorterConfig<UmbSwatchDetails, UmbMultipleColorPickerIte
|
||||
@customElement('umb-multiple-color-picker-input')
|
||||
export class UmbMultipleColorPickerInputElement extends FormControlMixin(UmbLitElement) {
|
||||
#sorter = new UmbSorterController(this, {
|
||||
...SORTER_CONFIG,
|
||||
getUniqueOfElement: (element: UmbMultipleColorPickerItemInputElement) => {
|
||||
return element.value.toString();
|
||||
},
|
||||
getUniqueOfModel: (modelEntry: UmbSwatchDetails) => {
|
||||
return modelEntry.value;
|
||||
},
|
||||
identifier: 'Umb.SorterIdentifier.ColorEditor',
|
||||
itemSelector: 'umb-multiple-color-picker-item-input',
|
||||
containerSelector: '#sorter-wrapper',
|
||||
onChange: ({ model }) => {
|
||||
const oldValue = this._items;
|
||||
this._items = model;
|
||||
@@ -194,7 +191,6 @@ export class UmbMultipleColorPickerInputElement extends FormControlMixin(UmbLitE
|
||||
?showLabels=${this.showLabels}
|
||||
value=${item.value}
|
||||
label=${ifDefined(item.label)}
|
||||
name="item-${index}"
|
||||
@change=${(event: UmbChangeEvent) => this.#onChange(event, index)}
|
||||
@delete="${(event: UmbDeleteEvent) => this.#deleteItem(event, index)}"
|
||||
?disabled=${this.disabled}
|
||||
|
||||
@@ -1,37 +1,26 @@
|
||||
import type { UmbInputMultipleTextStringItemElement } from './input-multiple-text-string-item.element.js';
|
||||
import { css, html, nothing, repeat, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui';
|
||||
import type { UmbInputEvent, UmbDeleteEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import type { UmbSorterConfig } from '@umbraco-cms/backoffice/sorter';
|
||||
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
|
||||
export type MultipleTextStringValue = Array<MultipleTextStringValueItem>;
|
||||
|
||||
export interface MultipleTextStringValueItem {
|
||||
value: string;
|
||||
}
|
||||
|
||||
const SORTER_CONFIG: UmbSorterConfig<MultipleTextStringValueItem> = {
|
||||
getUniqueOfElement: (element) => {
|
||||
return element.getAttribute('data-sort-entry-id');
|
||||
},
|
||||
getUniqueOfModel: (modelEntry) => {
|
||||
return modelEntry.value;
|
||||
},
|
||||
identifier: 'Umb.SorterIdentifier.ColorEditor',
|
||||
itemSelector: 'umb-input-multiple-text-string-item',
|
||||
containerSelector: '#sorter-wrapper',
|
||||
};
|
||||
import type { UmbInputEvent, UmbDeleteEvent } from '@umbraco-cms/backoffice/event';
|
||||
|
||||
/**
|
||||
* @element umb-input-multiple-text-string
|
||||
*/
|
||||
@customElement('umb-input-multiple-text-string')
|
||||
export class UmbInputMultipleTextStringElement extends FormControlMixin(UmbLitElement) {
|
||||
#prevalueSorter = new UmbSorterController(this, {
|
||||
...SORTER_CONFIG,
|
||||
#sorter = new UmbSorterController(this, {
|
||||
getUniqueOfElement: (element) => {
|
||||
return element.getAttribute('data-sort-entry-id');
|
||||
},
|
||||
getUniqueOfModel: (modelEntry: string) => {
|
||||
return modelEntry;
|
||||
},
|
||||
identifier: 'Umb.SorterIdentifier.ColorEditor',
|
||||
itemSelector: 'umb-input-multiple-text-string-item',
|
||||
containerSelector: '#sorter-wrapper',
|
||||
onChange: ({ model }) => {
|
||||
const oldValue = this._items;
|
||||
this._items = model;
|
||||
@@ -120,17 +109,17 @@ export class UmbInputMultipleTextStringElement extends FormControlMixin(UmbLitEl
|
||||
}
|
||||
|
||||
@state()
|
||||
private _items: MultipleTextStringValue = [];
|
||||
private _items: Array<string> = [];
|
||||
|
||||
@property({ type: Array })
|
||||
public get items(): MultipleTextStringValue {
|
||||
public get items(): Array<string> {
|
||||
return this._items;
|
||||
}
|
||||
public set items(items: MultipleTextStringValue) {
|
||||
public set items(items: Array<string>) {
|
||||
// TODO: when we have a way to overwrite the missing value validator we can remove this
|
||||
this.value = items?.length > 0 ? 'some value' : '';
|
||||
this._items = items ?? [];
|
||||
this.#prevalueSorter.setModel(this.items);
|
||||
this.#sorter.setModel(this.items);
|
||||
}
|
||||
|
||||
// TODO: Some inputs might not have a value that is either FormDataEntryValue or FormData.
|
||||
@@ -146,7 +135,7 @@ export class UmbInputMultipleTextStringElement extends FormControlMixin(UmbLitEl
|
||||
*/
|
||||
|
||||
#onAdd() {
|
||||
this._items = [...this._items, { value: '' }];
|
||||
this._items = [...this._items, ''];
|
||||
this.pristine = false;
|
||||
this.dispatchEvent(new UmbChangeEvent());
|
||||
this.#focusNewItem();
|
||||
@@ -156,7 +145,7 @@ export class UmbInputMultipleTextStringElement extends FormControlMixin(UmbLitEl
|
||||
event.stopPropagation();
|
||||
const target = event.currentTarget as UmbInputMultipleTextStringItemElement;
|
||||
const value = target.value as string;
|
||||
this._items = this._items.map((item, index) => (index === currentIndex ? { value } : item));
|
||||
this._items = this._items.map((item, index) => (index === currentIndex ? value : item));
|
||||
this.dispatchEvent(new UmbChangeEvent());
|
||||
}
|
||||
|
||||
@@ -192,9 +181,9 @@ export class UmbInputMultipleTextStringElement extends FormControlMixin(UmbLitEl
|
||||
(item, index) => index,
|
||||
(item, index) =>
|
||||
html` <umb-input-multiple-text-string-item
|
||||
value=${item.value}
|
||||
value=${item}
|
||||
name="item-${index}"
|
||||
data-sort-entry-id=${item.value}
|
||||
data-sort-entry-id=${item}
|
||||
@input=${(event: UmbInputEvent) => this.#onInput(event, index)}
|
||||
@delete="${(event: UmbDeleteEvent) => this.#deleteItem(event, index)}"
|
||||
?disabled=${this.disabled}
|
||||
|
||||
@@ -18,6 +18,7 @@ export class UmbDeleteEntityAction<
|
||||
color: 'danger',
|
||||
confirmLabel: 'Delete',
|
||||
});
|
||||
|
||||
await this.repository?.delete(this.unique);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ export class UmbExtensionCollectionElement extends UmbCollectionDefaultElement {
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: make this a utility function, please check that we do not already have on for this: [NL]
|
||||
// credit: https://stackoverflow.com/a/7225450/12787 [LK]
|
||||
#camelCaseToWords(input: string) {
|
||||
const result = input.replace(/([A-Z])/g, ' $1');
|
||||
@@ -35,7 +36,6 @@ export class UmbExtensionCollectionElement extends UmbCollectionDefaultElement {
|
||||
|
||||
#onChange(event: UUISelectEvent) {
|
||||
const extensionType = event.target.value;
|
||||
console.log('onChange', extensionType);
|
||||
this.#collectionContext?.setFilter({ type: extensionType });
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { umbExtensionsRegistry } from './registry.js';
|
||||
import { html, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import type { ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api';
|
||||
import { UmbExtensionElementInitializer, createExtensionApi } from '@umbraco-cms/backoffice/extension-api';
|
||||
|
||||
// TODO: Eslint: allow abstract element class to end with "ElementBase" instead of "Element"
|
||||
// eslint-disable-next-line local-rules/enforce-element-suffix-on-element-class-name
|
||||
export abstract class UmbExtensionInitializerElementBase<
|
||||
ManifestType extends ManifestWithDynamicConditions,
|
||||
> extends UmbLitElement {
|
||||
_alias?: string;
|
||||
@property({ type: String, reflect: true })
|
||||
get alias() {
|
||||
return this._alias;
|
||||
}
|
||||
set alias(newVal) {
|
||||
this._alias = newVal;
|
||||
this.#observeManifest();
|
||||
}
|
||||
|
||||
@property({ type: Object, attribute: false })
|
||||
get props() {
|
||||
return this.#props;
|
||||
}
|
||||
set props(newVal: Record<string, unknown> | undefined) {
|
||||
// TODO, compare changes since last time. only reset the ones that changed. This might be better done by the controller is self:
|
||||
this.#props = newVal;
|
||||
if (this.#extensionElementController) {
|
||||
this.#extensionElementController.properties = newVal;
|
||||
}
|
||||
}
|
||||
#props?: Record<string, unknown> = {};
|
||||
|
||||
#extensionElementController?: UmbExtensionElementInitializer<ManifestType>;
|
||||
|
||||
@state()
|
||||
_element: HTMLElement | undefined;
|
||||
|
||||
abstract getExtensionType(): string;
|
||||
abstract getDefaultElementName(): string;
|
||||
|
||||
#observeManifest() {
|
||||
if (!this._alias) return;
|
||||
this.observe(
|
||||
umbExtensionsRegistry.byTypeAndAlias(this.getExtensionType(), this._alias),
|
||||
async (m) => {
|
||||
if (!m) return;
|
||||
const manifest = m as unknown as ManifestType;
|
||||
this.createApi(manifest);
|
||||
this.createElement(manifest);
|
||||
},
|
||||
'umbObserveTreeManifest',
|
||||
);
|
||||
}
|
||||
|
||||
protected async createApi(manifest?: ManifestType) {
|
||||
if (!manifest) throw new Error('No manifest');
|
||||
const api = (await createExtensionApi(manifest, [this])) as unknown as any;
|
||||
if (!api) throw new Error('No api');
|
||||
api.setManifest(manifest);
|
||||
}
|
||||
|
||||
protected async createElement(manifest?: ManifestType) {
|
||||
if (!manifest) throw new Error('No manifest');
|
||||
|
||||
const extController = new UmbExtensionElementInitializer<ManifestType>(
|
||||
this,
|
||||
umbExtensionsRegistry,
|
||||
manifest.alias,
|
||||
this.#extensionChanged,
|
||||
this.getDefaultElementName(),
|
||||
);
|
||||
|
||||
extController.properties = this.#props;
|
||||
|
||||
this.#extensionElementController = extController;
|
||||
}
|
||||
|
||||
#extensionChanged = (isPermitted: boolean, controller: UmbExtensionElementInitializer<ManifestType>) => {
|
||||
this._element = isPermitted ? controller.component : undefined;
|
||||
this.requestUpdate('_element');
|
||||
};
|
||||
|
||||
render() {
|
||||
return html`${this._element}`;
|
||||
}
|
||||
}
|
||||
@@ -2,3 +2,5 @@ export * from './conditions/index.js';
|
||||
export * from './interfaces/index.js';
|
||||
export * from './models/index.js';
|
||||
export * from './registry.js';
|
||||
|
||||
export { UmbExtensionInitializerElementBase } from './extension-initializer-element-base.js';
|
||||
|
||||
@@ -6,5 +6,4 @@ export * from './property-editor-ui-element.interface.js';
|
||||
export * from './section-element.interface.js';
|
||||
export * from './section-sidebar-app-element.interface.js';
|
||||
export * from './section-view-element.interface.js';
|
||||
export * from './tree-item-element.interface.js';
|
||||
export * from './workspace-view-element.interface.js';
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import type { UmbTreeItemModelBase } from '@umbraco-cms/backoffice/tree';
|
||||
|
||||
export interface UmbTreeItemElement extends HTMLElement {
|
||||
item?: UmbTreeItemModelBase;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { UmbTreeItemElement } from '../interfaces/index.js';
|
||||
import type { ManifestElement } from '@umbraco-cms/backoffice/extension-api';
|
||||
import type { UmbTreeItemContext, UmbTreeItemModelBase } from '../../index.js';
|
||||
import type { ManifestElementAndApi } from '@umbraco-cms/backoffice/extension-api';
|
||||
|
||||
export interface ManifestTreeItem extends ManifestElement<UmbTreeItemElement> {
|
||||
export interface ManifestTreeItem extends ManifestElementAndApi<HTMLElement, UmbTreeItemContext<UmbTreeItemModelBase>> {
|
||||
type: 'treeItem';
|
||||
meta: MetaTreeItem;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ManifestBase } from '@umbraco-cms/backoffice/extension-api';
|
||||
import type { ManifestElementAndApi } from '@umbraco-cms/backoffice/extension-api';
|
||||
|
||||
export interface ManifestTree extends ManifestBase {
|
||||
export interface ManifestTree extends ManifestElementAndApi {
|
||||
type: 'tree';
|
||||
meta: MetaTree;
|
||||
}
|
||||
|
||||
@@ -13,9 +13,11 @@ export class UmbConfirmModalController extends UmbControllerBase {
|
||||
data: args,
|
||||
});
|
||||
|
||||
await modalContext.onSubmit().catch(() => {
|
||||
const p = modalContext.onSubmit();
|
||||
p.catch(() => {
|
||||
this.destroy();
|
||||
});
|
||||
await p;
|
||||
|
||||
// This is a one time off, so we can destroy our selfs.
|
||||
this.destroy();
|
||||
|
||||
@@ -98,6 +98,7 @@ export class UmbLinkPickerModalElement extends UmbModalBaseElement<UmbLinkPicker
|
||||
const linkType = (entityType as UmbLinkPickerLinkType) ?? 'external';
|
||||
|
||||
this._selectedKey = selectedKey;
|
||||
if (this._selectedKey === undefined) return;
|
||||
this._selectionConfiguration.selection = [this._selectedKey];
|
||||
this.#partialUpdateLink({ type: linkType, unique: selectedKey, url: selectedKey });
|
||||
this.requestUpdate();
|
||||
@@ -187,10 +188,12 @@ export class UmbLinkPickerModalElement extends UmbModalBaseElement<UmbLinkPicker
|
||||
placeholder=${this.localize.term('placeholders_search')}
|
||||
label=${this.localize.term('placeholders_search')}></uui-input>
|
||||
<umb-tree
|
||||
?hide-tree-root=${true}
|
||||
alias=${UMB_DOCUMENT_TREE_ALIAS}
|
||||
@selection-change=${(event: CustomEvent) => this.#handleSelectionChange(event, 'document')}
|
||||
.selectionConfiguration=${this._selectionConfiguration}></umb-tree>
|
||||
.props=${{
|
||||
hideTreeRoot: true,
|
||||
selectionConfiguration: this._selectionConfiguration,
|
||||
}}
|
||||
@selection-change=${(event: CustomEvent) => this.#handleSelectionChange(event, 'document')}></umb-tree>
|
||||
</div>
|
||||
<hr />
|
||||
<uui-symbol-expand
|
||||
@@ -200,10 +203,12 @@ export class UmbLinkPickerModalElement extends UmbModalBaseElement<UmbLinkPicker
|
||||
<uui-label for="media-expand">${this.localize.term('defaultdialogs_linkToMedia')}</uui-label>
|
||||
<div style="${styleMap({ display: !this.mediaExpanded ? 'block' : 'none' })}">
|
||||
<umb-tree
|
||||
?hide-tree-root=${true}
|
||||
alias="Umb.Tree.Media"
|
||||
@selection-change=${(event: CustomEvent) => this.#handleSelectionChange(event, 'media')}
|
||||
.selectionConfiguration=${this._selectionConfiguration}></umb-tree>
|
||||
.props=${{
|
||||
hideTreeRoot: true,
|
||||
selectionConfiguration: this._selectionConfiguration,
|
||||
}}
|
||||
@selection-change=${(event: CustomEvent) => this.#handleSelectionChange(event, 'media')}></umb-tree>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -43,12 +43,6 @@ const modals: Array<ManifestModal> = [
|
||||
name: 'Embedded Media Modal',
|
||||
js: () => import('./embedded-media/embedded-media-modal.element.js'),
|
||||
},
|
||||
{
|
||||
type: 'modal',
|
||||
alias: 'Umb.Modal.TreePicker',
|
||||
name: 'Tree Picker Modal',
|
||||
js: () => import('./tree-picker/tree-picker-modal.element.js'),
|
||||
},
|
||||
];
|
||||
|
||||
export const manifests = [...modals];
|
||||
|
||||
@@ -6,7 +6,7 @@ import { UMB_DOCUMENT_TYPE_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/doc
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type { UUIBooleanInputEvent, UUIInputEvent, UUISelectEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, html, nothing, customElement, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, html, nothing, customElement, state, ifDefined } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UmbPropertySettingsModalValue, UmbPropertySettingsModalData } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
|
||||
import { generateAlias } from '@umbraco-cms/backoffice/utils';
|
||||
@@ -234,6 +234,7 @@ export class UmbPropertySettingsModalElement extends UmbModalBaseElement<
|
||||
<uui-input
|
||||
id="name-input"
|
||||
name="name"
|
||||
label="property name (TODO: Localize)"
|
||||
@input=${this.#onNameChange}
|
||||
.value=${this.value.name}
|
||||
placeholder="Enter a name...">
|
||||
@@ -259,7 +260,7 @@ export class UmbPropertySettingsModalElement extends UmbModalBaseElement<
|
||||
.value=${this.value.description}></uui-textarea>
|
||||
</div>
|
||||
<umb-data-type-flow-input
|
||||
.value=${this.value.dataType?.unique}
|
||||
.value=${ifDefined(this.value.dataType?.unique)}
|
||||
@change=${this.#onDataTypeIdChange}></umb-data-type-flow-input>
|
||||
<hr />
|
||||
<div class="container">
|
||||
|
||||
@@ -14,11 +14,6 @@ export const manifest: ManifestPropertyEditorSchema = {
|
||||
description: 'Define Blocks based on Element Types.',
|
||||
propertyEditorUiAlias: 'Umb.PropertyEditorUi.BlockGridTypeConfiguration',
|
||||
},
|
||||
{
|
||||
alias: 'blockGroups',
|
||||
label: '',
|
||||
propertyEditorUiAlias: 'Umb.PropertyEditorUi.BlockTypeGroupConfiguration',
|
||||
},
|
||||
{
|
||||
alias: 'validationLimit',
|
||||
label: 'Amount',
|
||||
|
||||
@@ -7,5 +7,19 @@ export const manifest: ManifestPropertyEditorSchema = {
|
||||
alias: 'Umbraco.DropDown.Flexible',
|
||||
meta: {
|
||||
defaultPropertyEditorUiAlias: 'Umb.PropertyEditorUi.Dropdown',
|
||||
settings: {
|
||||
properties: [
|
||||
{
|
||||
alias: 'multiple',
|
||||
label: 'Enable multiple choice',
|
||||
propertyEditorUiAlias: 'Umb.PropertyEditorUi.Toggle',
|
||||
},
|
||||
{
|
||||
alias: 'items',
|
||||
label: 'Add options',
|
||||
propertyEditorUiAlias: 'Umb.PropertyEditorUi.MultipleTextString',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -6,5 +6,15 @@ export const manifest: ManifestPropertyEditorSchema = {
|
||||
alias: 'Umbraco.RadioButtonList',
|
||||
meta: {
|
||||
defaultPropertyEditorUiAlias: 'Umb.PropertyEditorUi.RadioButtonList',
|
||||
settings: {
|
||||
properties: [
|
||||
{
|
||||
alias: 'items',
|
||||
label: 'Add option',
|
||||
description: 'Add, remove or sort options for the list.',
|
||||
propertyEditorUiAlias: 'Umb.PropertyEditorUi.MultipleTextString',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -12,13 +12,20 @@ export const manifest: ManifestPropertyEditorSchema = {
|
||||
alias: 'mediaParentId',
|
||||
label: 'Image Upload Folder',
|
||||
description: 'Choose the upload location of pasted images',
|
||||
propertyEditorUiAlias: 'Umb.PropertyEditorUi.TreePicker',
|
||||
propertyEditorUiAlias: 'Umb.PropertyEditorUi.MediaPicker',
|
||||
config: [{ alias: 'validationLimit', value: { min: 0, max: 1 } }],
|
||||
},
|
||||
{
|
||||
alias: 'ignoreUserStartNodes',
|
||||
label: 'Ignore User Start Nodes',
|
||||
propertyEditorUiAlias: 'Umb.PropertyEditorUi.Toggle',
|
||||
},
|
||||
{
|
||||
alias: 'blocks',
|
||||
label: 'Available Blocks',
|
||||
description: 'Define the available blocks.',
|
||||
propertyEditorUiAlias: 'Umb.PropertyEditorUi.BlockRteTypeConfiguration',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -6,6 +6,6 @@ export const manifest: ManifestPropertyEditorSchema = {
|
||||
name: 'Date/Time',
|
||||
alias: 'Umbraco.TrueFalse',
|
||||
meta: {
|
||||
defaultPropertyEditorUiAlias: 'Umb.PropertyEditorUi.TrueFalse',
|
||||
defaultPropertyEditorUiAlias: 'Umb.PropertyEditorUi.Toggle',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -7,7 +7,6 @@ export const manifest: ManifestPropertyEditorUi = {
|
||||
element: () => import('./property-editor-ui-collection-view-bulk-action-permissions.element.js'),
|
||||
meta: {
|
||||
label: 'Collection View Bulk Action Permissions',
|
||||
propertyEditorSchemaAlias: '',
|
||||
icon: 'icon-autofill',
|
||||
group: 'lists',
|
||||
},
|
||||
|
||||
@@ -7,7 +7,6 @@ export const manifest: ManifestPropertyEditorUi = {
|
||||
element: () => import('./property-editor-ui-collection-view-column-configuration.element.js'),
|
||||
meta: {
|
||||
label: 'Collection View Column Configuration',
|
||||
propertyEditorSchemaAlias: '',
|
||||
icon: 'icon-autofill',
|
||||
group: 'lists',
|
||||
},
|
||||
|
||||
@@ -7,7 +7,6 @@ export const manifest: ManifestPropertyEditorUi = {
|
||||
element: () => import('./property-editor-ui-collection-view-layout-configuration.element.js'),
|
||||
meta: {
|
||||
label: 'Collection View Layout Configuration',
|
||||
propertyEditorSchemaAlias: '',
|
||||
icon: 'icon-autofill',
|
||||
group: 'lists',
|
||||
},
|
||||
|
||||
@@ -26,7 +26,7 @@ export class UmbPropertyEditorUICollectionViewLayoutConfigurationElement
|
||||
implements UmbPropertyEditorUiElement
|
||||
{
|
||||
@property({ type: Array })
|
||||
value: Array<LayoutConfig> = [];
|
||||
value?: Array<LayoutConfig>;
|
||||
|
||||
@property({ type: Object, attribute: false })
|
||||
public config?: UmbPropertyEditorConfigCollection;
|
||||
@@ -41,47 +41,47 @@ export class UmbPropertyEditorUICollectionViewLayoutConfigurationElement
|
||||
}
|
||||
|
||||
#onAdd() {
|
||||
this.value = [...this.value, { isSystem: false, icon: 'icon-stop', selected: true }];
|
||||
this.value = [...(this.value ?? []), { isSystem: false, icon: 'icon-stop', selected: true }];
|
||||
this.dispatchEvent(new UmbPropertyValueChangeEvent());
|
||||
}
|
||||
|
||||
#onRemove(unique: number) {
|
||||
const values = [...this.value];
|
||||
const values = [...(this.value ?? [])];
|
||||
values.splice(unique, 1);
|
||||
this.value = values;
|
||||
this.dispatchEvent(new UmbPropertyValueChangeEvent());
|
||||
}
|
||||
|
||||
#onChangePath(e: UUIInputEvent, index: number) {
|
||||
const values = [...this.value];
|
||||
const values = [...(this.value ?? [])];
|
||||
values[index] = { ...values[index], path: e.target.value as string };
|
||||
this.value = values;
|
||||
this.dispatchEvent(new UmbPropertyValueChangeEvent());
|
||||
}
|
||||
|
||||
#onChangeName(e: UUIInputEvent, index: number) {
|
||||
const values = [...this.value];
|
||||
const values = [...(this.value ?? [])];
|
||||
values[index] = { ...values[index], name: e.target.value as string };
|
||||
this.value = values;
|
||||
this.dispatchEvent(new UmbPropertyValueChangeEvent());
|
||||
}
|
||||
|
||||
#onChangeSelected(e: UUIBooleanInputEvent, index: number) {
|
||||
const values = [...this.value];
|
||||
const values = [...(this.value ?? [])];
|
||||
values[index] = { ...values[index], selected: e.target.checked };
|
||||
this.value = values;
|
||||
this.dispatchEvent(new UmbPropertyValueChangeEvent());
|
||||
}
|
||||
|
||||
async #onIconChange(index: number) {
|
||||
const icon = this.#iconReader(this.value[index].icon ?? '');
|
||||
const icon = this.#iconReader((this.value ? this.value[index].icon : undefined) ?? '');
|
||||
|
||||
// TODO: send icon data to modal
|
||||
const modalContext = this._modalContext?.open(UMB_ICON_PICKER_MODAL);
|
||||
const picked = await modalContext?.onSubmit();
|
||||
if (!picked) return;
|
||||
|
||||
const values = [...this.value];
|
||||
const values = [...(this.value ?? [])];
|
||||
values[index] = { ...values[index], icon: `${picked.icon} color-${picked.color}` };
|
||||
this.value = values;
|
||||
this.dispatchEvent(new UmbPropertyValueChangeEvent());
|
||||
@@ -89,16 +89,18 @@ export class UmbPropertyEditorUICollectionViewLayoutConfigurationElement
|
||||
|
||||
render() {
|
||||
return html`<div id="layout-wrapper">
|
||||
${repeat(
|
||||
this.value,
|
||||
(layout, index) => '' + layout.name + layout.icon,
|
||||
(layout, index) =>
|
||||
html` <div class="layout-item">
|
||||
<uui-icon name="icon-navigation"></uui-icon> ${layout.isSystem
|
||||
? this.renderSystemFieldRow(layout, index)
|
||||
: this.renderCustomFieldRow(layout, index)}
|
||||
</div>`,
|
||||
)}
|
||||
${this.value
|
||||
? repeat(
|
||||
this.value,
|
||||
(layout, index) => '' + layout.name + layout.icon,
|
||||
(layout, index) =>
|
||||
html` <div class="layout-item">
|
||||
<uui-icon name="icon-navigation"></uui-icon> ${layout.isSystem
|
||||
? this.renderSystemFieldRow(layout, index)
|
||||
: this.renderCustomFieldRow(layout, index)}
|
||||
</div>`,
|
||||
)
|
||||
: ''}
|
||||
</div>
|
||||
<uui-button
|
||||
id="add"
|
||||
|
||||
@@ -7,7 +7,6 @@ export const manifest: ManifestPropertyEditorUi = {
|
||||
element: () => import('./property-editor-ui-collection-view-order-by.element.js'),
|
||||
meta: {
|
||||
label: 'Collection View Order By',
|
||||
propertyEditorSchemaAlias: '',
|
||||
icon: 'icon-autofill',
|
||||
group: 'lists',
|
||||
},
|
||||
|
||||
@@ -21,10 +21,6 @@ export class UmbPropertyEditorUIColorSwatchesEditorElement extends UmbLitElement
|
||||
@property({ attribute: false })
|
||||
public set config(config: UmbPropertyEditorConfigCollection | undefined) {
|
||||
this._showLabels = config?.getValueByAlias('useLabel') ?? this.#defaultShowLabels;
|
||||
const items = config?.getValueByAlias('items') as typeof this.value;
|
||||
if (items) {
|
||||
this.value = items;
|
||||
}
|
||||
}
|
||||
|
||||
#onChange(event: CustomEvent) {
|
||||
|
||||
@@ -10,19 +10,5 @@ export const manifest: ManifestPropertyEditorUi = {
|
||||
propertyEditorSchemaAlias: 'Umbraco.DropDown.Flexible',
|
||||
icon: 'icon-time',
|
||||
group: 'pickers',
|
||||
settings: {
|
||||
properties: [
|
||||
{
|
||||
alias: 'multiple',
|
||||
label: 'Enable multiple choice',
|
||||
propertyEditorUiAlias: 'Umb.PropertyEditorUi.Toggle',
|
||||
},
|
||||
{
|
||||
alias: 'items',
|
||||
label: 'Add options',
|
||||
propertyEditorUiAlias: 'Umb.PropertyEditorUi.MultipleTextString',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { html, customElement, property, state, ifDefined } from '@umbraco-cms/ba
|
||||
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
|
||||
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import type { MultipleTextStringValue, UmbInputMultipleTextStringElement } from '@umbraco-cms/backoffice/components';
|
||||
import type { UmbInputMultipleTextStringElement } from '@umbraco-cms/backoffice/components';
|
||||
|
||||
/**
|
||||
* @element umb-property-editor-ui-multiple-text-string
|
||||
@@ -12,7 +12,7 @@ import type { MultipleTextStringValue, UmbInputMultipleTextStringElement } from
|
||||
@customElement('umb-property-editor-ui-multiple-text-string')
|
||||
export class UmbPropertyEditorUIMultipleTextStringElement extends UmbLitElement implements UmbPropertyEditorUiElement {
|
||||
@property({ type: Array })
|
||||
public value: MultipleTextStringValue = [];
|
||||
public value: Array<string> = [];
|
||||
|
||||
@property({ attribute: false })
|
||||
public set config(config: UmbPropertyEditorConfigCollection | undefined) {
|
||||
|
||||
@@ -7,7 +7,6 @@ export const manifest: ManifestPropertyEditorUi = {
|
||||
element: () => import('./property-editor-ui-number-range.element.js'),
|
||||
meta: {
|
||||
label: 'Number Range',
|
||||
propertyEditorSchemaAlias: '',
|
||||
icon: 'icon-autofill',
|
||||
group: 'common',
|
||||
},
|
||||
|
||||
@@ -7,7 +7,6 @@ export const manifest: ManifestPropertyEditorUi = {
|
||||
element: () => import('./property-editor-ui-order-direction.element.js'),
|
||||
meta: {
|
||||
label: 'Order Direction',
|
||||
propertyEditorSchemaAlias: '',
|
||||
icon: 'icon-autofill',
|
||||
group: 'common',
|
||||
},
|
||||
|
||||
@@ -7,7 +7,6 @@ export const manifest: ManifestPropertyEditorUi = {
|
||||
element: () => import('./property-editor-ui-overlay-size.element.js'),
|
||||
meta: {
|
||||
label: 'Overlay Size',
|
||||
propertyEditorSchemaAlias: '',
|
||||
icon: 'icon-document',
|
||||
group: '',
|
||||
},
|
||||
|
||||
@@ -10,15 +10,5 @@ export const manifest: ManifestPropertyEditorUi = {
|
||||
propertyEditorSchemaAlias: 'Umbraco.RadioButtonList',
|
||||
icon: 'icon-target',
|
||||
group: 'lists',
|
||||
settings: {
|
||||
properties: [
|
||||
{
|
||||
alias: 'items',
|
||||
label: 'Add option',
|
||||
description: 'Add, remove or sort options for the list.',
|
||||
propertyEditorUiAlias: 'Umb.PropertyEditorUi.MultipleTextString',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -9,6 +9,5 @@ export const manifest: ManifestPropertyEditorUi = {
|
||||
label: 'Tree Picker Source Picker',
|
||||
icon: 'icon-page-add',
|
||||
group: 'pickers',
|
||||
propertyEditorSchemaAlias: '',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -9,6 +9,5 @@ export const manifest: ManifestPropertyEditorUi = {
|
||||
label: 'Tree Picker Source Type Picker',
|
||||
icon: 'icon-page-add',
|
||||
group: 'pickers',
|
||||
propertyEditorSchemaAlias: '',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -110,12 +110,21 @@ export class UmbPropertyContext<ValueType = any> extends UmbControllerBase {
|
||||
public setAlias(alias: string | undefined): void {
|
||||
this.#alias.setValue(alias);
|
||||
}
|
||||
public getAlias(): string | undefined {
|
||||
return this.#alias.getValue();
|
||||
}
|
||||
public setLabel(label: string | undefined): void {
|
||||
this.#label.setValue(label);
|
||||
}
|
||||
public getLabel(): string | undefined {
|
||||
return this.#label.getValue();
|
||||
}
|
||||
public setDescription(description: string | undefined): void {
|
||||
this.#description.setValue(description);
|
||||
}
|
||||
public getDescription(): string | undefined {
|
||||
return this.#description.getValue();
|
||||
}
|
||||
/**
|
||||
* Set the value of this property.
|
||||
* @param value {ValueType} the whole value to be set
|
||||
@@ -136,6 +145,9 @@ export class UmbPropertyContext<ValueType = any> extends UmbControllerBase {
|
||||
public setConfig(config: Array<UmbPropertyEditorConfigProperty> | undefined): void {
|
||||
this.#configValues.setValue(config ?? []);
|
||||
}
|
||||
public getConfig(): Array<UmbPropertyEditorConfigProperty> | undefined {
|
||||
return this.#configValues.getValue();
|
||||
}
|
||||
public setVariantId(variantId: UmbVariantId | undefined): void {
|
||||
this.#variantId.setValue(variantId);
|
||||
}
|
||||
|
||||
@@ -27,9 +27,12 @@ export class UmbPropertyElement extends UmbLitElement {
|
||||
* @default ''
|
||||
*/
|
||||
@property({ type: String })
|
||||
public set label(label: string) {
|
||||
public set label(label: string | undefined) {
|
||||
this.#propertyContext.setLabel(label);
|
||||
}
|
||||
public get label() {
|
||||
return this.#propertyContext.getLabel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Description: render a description underneath the label.
|
||||
@@ -38,9 +41,12 @@ export class UmbPropertyElement extends UmbLitElement {
|
||||
* @default ''
|
||||
*/
|
||||
@property({ type: String })
|
||||
public set description(description: string) {
|
||||
public set description(description: string | undefined) {
|
||||
this.#propertyContext.setDescription(description);
|
||||
}
|
||||
public get description() {
|
||||
return this.#propertyContext.getDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias
|
||||
@@ -53,6 +59,9 @@ export class UmbPropertyElement extends UmbLitElement {
|
||||
public set alias(alias: string) {
|
||||
this.#propertyContext.setAlias(alias);
|
||||
}
|
||||
public get alias() {
|
||||
return this.#propertyContext.getAlias() ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Property Editor UI Alias. Render the Property Editor UI registered for this alias.
|
||||
@@ -62,12 +71,14 @@ export class UmbPropertyElement extends UmbLitElement {
|
||||
* @default ''
|
||||
*/
|
||||
@property({ type: String, attribute: 'property-editor-ui-alias' })
|
||||
public set propertyEditorUiAlias(value: string) {
|
||||
if (this._propertyEditorUiAlias === value) return;
|
||||
public set propertyEditorUiAlias(value: string | undefined) {
|
||||
this._propertyEditorUiAlias = value;
|
||||
this._observePropertyEditorUI();
|
||||
}
|
||||
private _propertyEditorUiAlias = '';
|
||||
public get propertyEditorUiAlias(): string {
|
||||
return this._propertyEditorUiAlias ?? '';
|
||||
}
|
||||
private _propertyEditorUiAlias?: string;
|
||||
|
||||
/**
|
||||
* Config. Configuration to pass to the Property Editor UI. This is also the configuration data stored on the Data Type.
|
||||
@@ -80,6 +91,9 @@ export class UmbPropertyElement extends UmbLitElement {
|
||||
public set config(value: UmbPropertyEditorConfig | undefined) {
|
||||
this.#propertyContext.setConfig(value);
|
||||
}
|
||||
public get config(): UmbPropertyEditorConfig | undefined {
|
||||
return this.#propertyContext.getConfig();
|
||||
}
|
||||
|
||||
@state()
|
||||
private _variantDifference?: string;
|
||||
@@ -130,13 +144,15 @@ export class UmbPropertyElement extends UmbLitElement {
|
||||
};
|
||||
|
||||
private _observePropertyEditorUI(): void {
|
||||
this.observe(
|
||||
umbExtensionsRegistry.byTypeAndAlias('propertyEditorUi', this._propertyEditorUiAlias),
|
||||
(manifest) => {
|
||||
this._gotEditorUI(manifest);
|
||||
},
|
||||
'_observePropertyEditorUI',
|
||||
);
|
||||
if (this._propertyEditorUiAlias) {
|
||||
this.observe(
|
||||
umbExtensionsRegistry.byTypeAndAlias('propertyEditorUi', this._propertyEditorUiAlias),
|
||||
(manifest) => {
|
||||
this._gotEditorUI(manifest);
|
||||
},
|
||||
'_observePropertyEditorUI',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async _gotEditorUI(manifest?: ManifestPropertyEditorUi | null): Promise<void> {
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export type { UmbTreeDataSource } from './tree-data-source.interface.js';
|
||||
export { UmbTreeServerDataSourceBase } from './tree-server-data-source-base.js';
|
||||
@@ -0,0 +1,10 @@
|
||||
export { UmbTreeServerDataSourceBase } from './tree-server-data-source-base.js';
|
||||
export { UmbTreeRepositoryBase } from './tree-repository-base.js';
|
||||
|
||||
export type { UmbTreeDataSource } from './tree-data-source.interface.js';
|
||||
export type { UmbTreeRepository } from './tree-repository.interface.js';
|
||||
export type { UmbTreeStore } from './tree-store.interface.js';
|
||||
|
||||
export type { UmbTreeRootItemsRequestArgs, UmbTreeChildrenOfRequestArgs } from './types.js';
|
||||
|
||||
export { UmbUniqueTreeStore } from './unique-tree-store.js';
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { UmbTreeItemModelBase } from '../types.js';
|
||||
import type { UmbTreeChildrenOfRequestArgs, UmbTreeRootItemsRequestArgs } from './types.js';
|
||||
import type { UmbPagedModel, UmbDataSourceResponse } from '@umbraco-cms/backoffice/repository';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
@@ -21,16 +22,15 @@ export interface UmbTreeDataSourceConstructor<TreeItemType extends UmbTreeItemMo
|
||||
export interface UmbTreeDataSource<TreeItemType extends UmbTreeItemModelBase> {
|
||||
/**
|
||||
* Gets the root items of the tree.
|
||||
* @return {*} {Promise<DataSourceResponse<UmbPagedModel<TreeItemType>>>}
|
||||
* @return {*} {Promise<UmbDataSourceResponse<UmbPagedModel<TreeItemType>>>}
|
||||
* @memberof UmbTreeDataSource
|
||||
*/
|
||||
getRootItems(): Promise<UmbDataSourceResponse<UmbPagedModel<TreeItemType>>>;
|
||||
getRootItems(args: UmbTreeRootItemsRequestArgs): Promise<UmbDataSourceResponse<UmbPagedModel<TreeItemType>>>;
|
||||
|
||||
/**
|
||||
* Gets the children of the given parent item.
|
||||
* @param {(string | null)} parentUnique
|
||||
* @return {*} {Promise<DataSourceResponse<UmbPagedModel<TreeItemType>>}
|
||||
* @return {*} {Promise<UmbDataSourceResponse<UmbPagedModel<TreeItemType>>}
|
||||
* @memberof UmbTreeDataSource
|
||||
*/
|
||||
getChildrenOf(parentUnique: string | null): Promise<UmbDataSourceResponse<UmbPagedModel<TreeItemType>>>;
|
||||
getChildrenOf(args: UmbTreeChildrenOfRequestArgs): Promise<UmbDataSourceResponse<UmbPagedModel<TreeItemType>>>;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { UmbUniqueTreeItemModel, UmbUniqueTreeRootModel } from '../types.js';
|
||||
import type { UmbTreeStore } from './tree-store.interface.js';
|
||||
import type { UmbUniqueTreeItemModel, UmbUniqueTreeRootModel } from './types.js';
|
||||
import type { UmbTreeRepository } from './tree-repository.interface.js';
|
||||
import type { UmbTreeDataSource, UmbTreeDataSourceConstructor } from './data-source/tree-data-source.interface.js';
|
||||
import type { UmbTreeDataSource, UmbTreeDataSourceConstructor } from './tree-data-source.interface.js';
|
||||
import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
|
||||
@@ -61,10 +61,10 @@ export abstract class UmbTreeRepositoryBase<
|
||||
* @return {*}
|
||||
* @memberof UmbTreeRepositoryBase
|
||||
*/
|
||||
async requestRootTreeItems() {
|
||||
async requestRootTreeItems(args: any) {
|
||||
await this._init;
|
||||
|
||||
const { data, error } = await this.#treeSource.getRootItems();
|
||||
const { data, error } = await this.#treeSource.getRootItems(args);
|
||||
|
||||
if (data) {
|
||||
this._treeStore!.appendItems(data.items);
|
||||
@@ -79,17 +79,17 @@ export abstract class UmbTreeRepositoryBase<
|
||||
* @return {*}
|
||||
* @memberof UmbTreeRepositoryBase
|
||||
*/
|
||||
async requestTreeItemsOf(parentUnique: string | null) {
|
||||
if (parentUnique === undefined) throw new Error('Parent unique is missing');
|
||||
async requestTreeItemsOf(args: any) {
|
||||
if (args.parentUnique === undefined) throw new Error('Parent unique is missing');
|
||||
await this._init;
|
||||
|
||||
const { data, error } = await this.#treeSource.getChildrenOf(parentUnique);
|
||||
const { data, error } = await this.#treeSource.getChildrenOf(args);
|
||||
|
||||
if (data) {
|
||||
this._treeStore!.appendItems(data.items);
|
||||
}
|
||||
|
||||
return { data, error, asObservable: () => this._treeStore!.childrenOf(parentUnique) };
|
||||
return { data, error, asObservable: () => this._treeStore!.childrenOf(args.parentUnique) };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { UmbTreeItemModelBase } from './types.js';
|
||||
import type { UmbTreeItemModelBase } from '../types.js';
|
||||
import type { UmbTreeChildrenOfRequestArgs, UmbTreeRootItemsRequestArgs } from './types.js';
|
||||
import type { UmbPagedModel } from '@umbraco-cms/backoffice/repository';
|
||||
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import type { ProblemDetails } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
@@ -27,20 +28,21 @@ export interface UmbTreeRepository<
|
||||
|
||||
/**
|
||||
* Requests the root items of the tree.
|
||||
* @param {UmbTreeRootItemsRequestArgs} args
|
||||
* @memberof UmbTreeRepository
|
||||
*/
|
||||
requestRootTreeItems: () => Promise<{
|
||||
requestRootTreeItems: (args: UmbTreeRootItemsRequestArgs) => Promise<{
|
||||
data?: UmbPagedModel<TreeItemType>;
|
||||
error?: ProblemDetails;
|
||||
asObservable?: () => Observable<TreeItemType[]>;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Requests the items of a item in the tree.
|
||||
* @param {(string | null)} parentUnique
|
||||
* Requests the children of the given parent item.
|
||||
* @param {UmbTreeChildrenOfRequestArgs} args
|
||||
* @memberof UmbTreeRepository
|
||||
*/
|
||||
requestTreeItemsOf: (parentUnique: string | null) => Promise<{
|
||||
requestTreeItemsOf: (args: UmbTreeChildrenOfRequestArgs) => Promise<{
|
||||
data?: UmbPagedModel<TreeItemType>;
|
||||
error?: ProblemDetails;
|
||||
asObservable?: () => Observable<TreeItemType[]>;
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { UmbTreeItemModelBase } from '../types.js';
|
||||
import type { UmbTreeDataSource } from './tree-data-source.interface.js';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { UmbTreeChildrenOfRequestArgs, UmbTreeRootItemsRequestArgs } from './types.js';
|
||||
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { TreeItemPresentationModel } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import type { UmbPagedModel } from '@umbraco-cms/backoffice/repository';
|
||||
|
||||
@@ -9,8 +10,8 @@ export interface UmbTreeServerDataSourceBaseArgs<
|
||||
ServerTreeItemType extends TreeItemPresentationModel,
|
||||
ClientTreeItemType extends UmbTreeItemModelBase,
|
||||
> {
|
||||
getRootItems: () => Promise<UmbPagedModel<ServerTreeItemType>>;
|
||||
getChildrenOf: (parentUnique: string | null) => Promise<UmbPagedModel<ServerTreeItemType>>;
|
||||
getRootItems: (args: UmbTreeRootItemsRequestArgs) => Promise<UmbPagedModel<ServerTreeItemType>>;
|
||||
getChildrenOf: (args: UmbTreeChildrenOfRequestArgs) => Promise<UmbPagedModel<ServerTreeItemType>>;
|
||||
mapper: (item: ServerTreeItemType) => ClientTreeItemType;
|
||||
}
|
||||
|
||||
@@ -44,11 +45,12 @@ export abstract class UmbTreeServerDataSourceBase<
|
||||
|
||||
/**
|
||||
* Fetches the root items for the tree from the server
|
||||
* @param {UmbTreeRootItemsRequestArgs} args
|
||||
* @return {*}
|
||||
* @memberof UmbTreeServerDataSourceBase
|
||||
*/
|
||||
async getRootItems() {
|
||||
const { data, error } = await tryExecuteAndNotify(this.#host, this.#getRootItems());
|
||||
async getRootItems(args: UmbTreeRootItemsRequestArgs) {
|
||||
const { data, error } = await tryExecuteAndNotify(this.#host, this.#getRootItems(args));
|
||||
|
||||
if (data) {
|
||||
const items = data?.items.map((item) => this.#mapper(item));
|
||||
@@ -60,14 +62,14 @@ export abstract class UmbTreeServerDataSourceBase<
|
||||
|
||||
/**
|
||||
* Fetches the children of a given parent unique from the server
|
||||
* @param {(string)} parentUnique
|
||||
* @param {UmbTreeChildrenOfRequestArgs} args
|
||||
* @return {*}
|
||||
* @memberof UmbTreeServerDataSourceBase
|
||||
*/
|
||||
async getChildrenOf(parentUnique: string | null) {
|
||||
if (parentUnique === undefined) throw new Error('Parent unique is missing');
|
||||
async getChildrenOf(args: UmbTreeChildrenOfRequestArgs) {
|
||||
if (args.parentUnique === undefined) throw new Error('Parent unique is missing');
|
||||
|
||||
const { data, error } = await tryExecuteAndNotify(this.#host, this.#getChildrenOf(parentUnique));
|
||||
const { data, error } = await tryExecuteAndNotify(this.#host, this.#getChildrenOf(args));
|
||||
|
||||
if (data) {
|
||||
const items = data?.items.map((item: ServerTreeItemType) => this.#mapper(item));
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { UmbTreeItemModelBase } from './types.js';
|
||||
import type { UmbTreeItemModelBase } from '../types.js';
|
||||
import type { UmbStore } from '@umbraco-cms/backoffice/store';
|
||||
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
|
||||
@@ -0,0 +1,10 @@
|
||||
export interface UmbTreeRootItemsRequestArgs {
|
||||
skip: number;
|
||||
take: number;
|
||||
}
|
||||
|
||||
export interface UmbTreeChildrenOfRequestArgs {
|
||||
parentUnique: string | null;
|
||||
skip: number;
|
||||
take: number;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { UmbUniqueTreeItemModel } from '../types.js';
|
||||
import type { UmbTreeStore } from './tree-store.interface.js';
|
||||
import type { UmbUniqueTreeItemModel } from './types.js';
|
||||
import { UmbStoreBase } from '@umbraco-cms/backoffice/store';
|
||||
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
@@ -1,47 +1,43 @@
|
||||
import { UmbReloadTreeItemChildrenRequestEntityActionEvent } from './reload-tree-item-children/index.js';
|
||||
import type { UmbTreeItemModelBase } from './types.js';
|
||||
import type { UmbTreeRepository } from './tree-repository.interface.js';
|
||||
import { UmbReloadTreeItemChildrenRequestEntityActionEvent } from '../reload-tree-item-children/index.js';
|
||||
import type { UmbTreeItemModelBase } from '../types.js';
|
||||
import type { UmbTreeRepository } from '../data/tree-repository.interface.js';
|
||||
import type { UmbTreeContext } from '../tree-context.interface.js';
|
||||
import { type UmbActionEventContext, UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import type { UmbPagedModel } from '@umbraco-cms/backoffice/repository';
|
||||
import {
|
||||
type ManifestRepository,
|
||||
type ManifestTree,
|
||||
umbExtensionsRegistry,
|
||||
} from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api';
|
||||
import type { ProblemDetails } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbPaginationManager, UmbSelectionManager } from '@umbraco-cms/backoffice/utils';
|
||||
import type { UmbEntityActionEvent } from '@umbraco-cms/backoffice/entity-action';
|
||||
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
|
||||
// TODO: update interface
|
||||
export interface UmbTreeContext<TreeItemType extends UmbTreeItemModelBase> extends UmbControllerBase {
|
||||
selection: UmbSelectionManager;
|
||||
requestChildrenOf: (parentUnique: string | null) => Promise<{
|
||||
data?: UmbPagedModel<TreeItemType>;
|
||||
error?: ProblemDetails;
|
||||
asObservable?: () => Observable<TreeItemType[]>;
|
||||
}>;
|
||||
}
|
||||
|
||||
export class UmbTreeContextBase<TreeItemType extends UmbTreeItemModelBase>
|
||||
extends UmbControllerBase
|
||||
implements UmbTreeContext<TreeItemType>
|
||||
export class UmbDefaultTreeContext<TreeItemType extends UmbTreeItemModelBase>
|
||||
extends UmbContextBase<UmbDefaultTreeContext<TreeItemType>>
|
||||
implements UmbTreeContext
|
||||
{
|
||||
#treeRoot = new UmbObjectState<TreeItemType | undefined>(undefined);
|
||||
treeRoot = this.#treeRoot.asObservable();
|
||||
|
||||
public repository?: UmbTreeRepository<TreeItemType>;
|
||||
public selectableFilter?: (item: TreeItemType) => boolean = () => true;
|
||||
public filter?: (item: TreeItemType) => boolean = () => true;
|
||||
public readonly selection = new UmbSelectionManager(this._host);
|
||||
public readonly pagination = new UmbPaginationManager();
|
||||
|
||||
#treeAlias?: string;
|
||||
#manifest?: ManifestTree;
|
||||
#repository?: UmbTreeRepository<TreeItemType>;
|
||||
#actionEventContext?: UmbActionEventContext;
|
||||
|
||||
#paging = {
|
||||
skip: 0,
|
||||
take: 50,
|
||||
};
|
||||
|
||||
#initResolver?: () => void;
|
||||
#initialized = false;
|
||||
|
||||
@@ -50,9 +46,79 @@ export class UmbTreeContextBase<TreeItemType extends UmbTreeItemModelBase>
|
||||
});
|
||||
|
||||
constructor(host: UmbControllerHostElement) {
|
||||
super(host);
|
||||
this.provideContext('umbTreeContext', this);
|
||||
super(host, UMB_DEFAULT_TREE_CONTEXT);
|
||||
this.pagination.setPageSize(this.#paging.take);
|
||||
this.#consumeContexts();
|
||||
|
||||
// listen for page changes on the pagination manager
|
||||
this.pagination.addEventListener(UmbChangeEvent.TYPE, this.#onPageChange);
|
||||
this.requestTreeRoot();
|
||||
}
|
||||
|
||||
// TODO: find a generic way to do this
|
||||
#checkIfInitialized() {
|
||||
if (this.#repository) {
|
||||
this.#initialized = true;
|
||||
this.#initResolver?.();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the manifest
|
||||
* @param {ManifestCollection} manifest
|
||||
* @memberof UmbCollectionContext
|
||||
*/
|
||||
public setManifest(manifest: ManifestTree | undefined) {
|
||||
if (this.#manifest === manifest) return;
|
||||
this.#manifest = manifest;
|
||||
this.#observeRepository(this.#manifest?.meta.repositoryAlias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the manifest.
|
||||
* @return {ManifestCollection}
|
||||
* @memberof UmbCollectionContext
|
||||
*/
|
||||
public getManifest() {
|
||||
return this.#manifest;
|
||||
}
|
||||
|
||||
public getRepository() {
|
||||
return this.#repository;
|
||||
}
|
||||
|
||||
public async requestTreeRoot() {
|
||||
await this.#init;
|
||||
const { data } = await this.#repository!.requestTreeRoot();
|
||||
|
||||
if (data) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
this.#treeRoot.setValue(data);
|
||||
}
|
||||
}
|
||||
|
||||
public async requestRootItems() {
|
||||
await this.#init;
|
||||
|
||||
const { data, error, asObservable } = await this.#repository!.requestRootTreeItems({
|
||||
skip: this.#paging.skip,
|
||||
take: this.#paging.take,
|
||||
});
|
||||
|
||||
if (data) {
|
||||
this.pagination.setTotalItems(data.total);
|
||||
}
|
||||
|
||||
return { data, error, asObservable };
|
||||
}
|
||||
|
||||
public async rootItems() {
|
||||
await this.#init;
|
||||
return this.#repository!.rootTreeItems();
|
||||
}
|
||||
|
||||
#consumeContexts() {
|
||||
this.consumeContext(UMB_ACTION_EVENT_CONTEXT, (instance) => {
|
||||
this.#actionEventContext = instance;
|
||||
this.#actionEventContext.removeEventListener(
|
||||
@@ -64,76 +130,15 @@ export class UmbTreeContextBase<TreeItemType extends UmbTreeItemModelBase>
|
||||
this.#onReloadRequest as EventListener,
|
||||
);
|
||||
});
|
||||
|
||||
this.requestTreeRoot();
|
||||
}
|
||||
|
||||
// TODO: find a generic way to do this
|
||||
#checkIfInitialized() {
|
||||
if (this.repository) {
|
||||
this.#initialized = true;
|
||||
this.#initResolver?.();
|
||||
}
|
||||
}
|
||||
#onPageChange = (event: UmbChangeEvent) => {
|
||||
const target = event.target as UmbPaginationManager;
|
||||
this.#paging.skip = target.getSkip();
|
||||
this.requestRootItems();
|
||||
};
|
||||
|
||||
public async setTreeAlias(treeAlias?: string) {
|
||||
if (this.#treeAlias === treeAlias) return;
|
||||
this.#treeAlias = treeAlias;
|
||||
|
||||
this.#observeTreeManifest();
|
||||
}
|
||||
|
||||
public getTreeAlias() {
|
||||
return this.#treeAlias;
|
||||
}
|
||||
|
||||
public async requestTreeRoot() {
|
||||
await this.#init;
|
||||
const { data } = await this.repository!.requestTreeRoot();
|
||||
|
||||
if (data) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
this.#treeRoot.setValue(data);
|
||||
}
|
||||
}
|
||||
|
||||
public async requestRootItems() {
|
||||
await this.#init;
|
||||
return this.repository!.requestRootTreeItems();
|
||||
}
|
||||
|
||||
public async requestChildrenOf(parentUnique: string | null) {
|
||||
await this.#init;
|
||||
if (parentUnique === undefined) throw new Error('Parent unique cannot be undefined.');
|
||||
return this.repository!.requestTreeItemsOf(parentUnique);
|
||||
}
|
||||
|
||||
public async rootItems() {
|
||||
await this.#init;
|
||||
return this.repository!.rootTreeItems();
|
||||
}
|
||||
|
||||
public async childrenOf(parentUnique: string | null) {
|
||||
await this.#init;
|
||||
return this.repository!.treeItemsOf(parentUnique);
|
||||
}
|
||||
|
||||
#observeTreeManifest() {
|
||||
if (this.#treeAlias) {
|
||||
this.observe(
|
||||
umbExtensionsRegistry.byTypeAndAlias('tree', this.#treeAlias),
|
||||
async (treeManifest) => {
|
||||
if (!treeManifest) return;
|
||||
this.#observeRepository(treeManifest);
|
||||
},
|
||||
'_observeTreeManifest',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#observeRepository(treeManifest: ManifestTree) {
|
||||
const repositoryAlias = treeManifest.meta.repositoryAlias;
|
||||
#observeRepository(repositoryAlias?: string) {
|
||||
if (!repositoryAlias) throw new Error('Tree must have a repository alias.');
|
||||
|
||||
new UmbExtensionApiInitializer<ManifestRepository<UmbTreeRepository<TreeItemType>>>(
|
||||
@@ -142,7 +147,7 @@ export class UmbTreeContextBase<TreeItemType extends UmbTreeItemModelBase>
|
||||
repositoryAlias,
|
||||
[this._host],
|
||||
(permitted, ctrl) => {
|
||||
this.repository = permitted ? ctrl.api : undefined;
|
||||
this.#repository = permitted ? ctrl.api : undefined;
|
||||
this.#checkIfInitialized();
|
||||
},
|
||||
);
|
||||
@@ -167,3 +172,7 @@ export class UmbTreeContextBase<TreeItemType extends UmbTreeItemModelBase>
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbDefaultTreeContext;
|
||||
|
||||
export const UMB_DEFAULT_TREE_CONTEXT = new UmbContextToken<UmbDefaultTreeContext<any>>('UmbTreeContext');
|
||||
@@ -0,0 +1,168 @@
|
||||
import type { UmbTreeItemModelBase, UmbTreeSelectionConfiguration } from '../types.js';
|
||||
import type { UmbDefaultTreeContext } from './default-tree.context.js';
|
||||
import { UMB_DEFAULT_TREE_CONTEXT } from './default-tree.context.js';
|
||||
import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { html, nothing, customElement, property, state, repeat } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
|
||||
@customElement('umb-default-tree')
|
||||
export class UmbDefaultTreeElement extends UmbLitElement {
|
||||
private _selectionConfiguration: UmbTreeSelectionConfiguration = {
|
||||
multiple: false,
|
||||
selectable: true,
|
||||
selection: [],
|
||||
};
|
||||
|
||||
@property({ type: Object, attribute: false })
|
||||
selectionConfiguration: UmbTreeSelectionConfiguration = this._selectionConfiguration;
|
||||
|
||||
@property({ type: Boolean, attribute: false })
|
||||
hideTreeRoot: boolean = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
selectableFilter: (item: UmbTreeItemModelBase) => boolean = () => true;
|
||||
|
||||
@property({ attribute: false })
|
||||
filter: (item: UmbTreeItemModelBase) => boolean = () => true;
|
||||
|
||||
@state()
|
||||
private _items: UmbTreeItemModelBase[] = [];
|
||||
|
||||
@state()
|
||||
private _treeRoot?: UmbTreeItemModelBase;
|
||||
|
||||
@state()
|
||||
private _currentPage = 1;
|
||||
|
||||
@state()
|
||||
private _totalPages = 1;
|
||||
|
||||
#treeContext?: UmbDefaultTreeContext<UmbTreeItemModelBase>;
|
||||
#init: Promise<unknown>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.#init = Promise.all([
|
||||
this.consumeContext(UMB_DEFAULT_TREE_CONTEXT, (instance) => {
|
||||
this.#treeContext = instance;
|
||||
|
||||
this.observe(this.#treeContext.pagination.currentPage, (value) => (this._currentPage = value));
|
||||
this.observe(this.#treeContext.pagination.totalPages, (value) => (this._totalPages = value));
|
||||
|
||||
this.#observeTreeRoot();
|
||||
}).asPromise(),
|
||||
]);
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.#init;
|
||||
}
|
||||
|
||||
protected async updated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): Promise<void> {
|
||||
super.updated(_changedProperties);
|
||||
await this.#init;
|
||||
|
||||
if (_changedProperties.has('selectionConfiguration')) {
|
||||
this._selectionConfiguration = this.selectionConfiguration;
|
||||
|
||||
this.#treeContext!.selection.setMultiple(this._selectionConfiguration.multiple ?? false);
|
||||
this.#treeContext!.selection.setSelectable(this._selectionConfiguration.selectable ?? true);
|
||||
this.#treeContext!.selection.setSelection(this._selectionConfiguration.selection ?? []);
|
||||
}
|
||||
|
||||
if (_changedProperties.has('hideTreeRoot')) {
|
||||
if (this.hideTreeRoot === true) {
|
||||
this.#observeRootItems();
|
||||
}
|
||||
}
|
||||
|
||||
if (_changedProperties.has('selectableFilter')) {
|
||||
this.#treeContext!.selectableFilter = this.selectableFilter;
|
||||
}
|
||||
|
||||
if (_changedProperties.has('filter')) {
|
||||
this.#treeContext!.filter = this.filter;
|
||||
}
|
||||
}
|
||||
|
||||
#observeTreeRoot() {
|
||||
if (!this.#treeContext) return;
|
||||
this.observe(
|
||||
this.#treeContext.treeRoot,
|
||||
(treeRoot) => {
|
||||
this._treeRoot = treeRoot;
|
||||
},
|
||||
'umbTreeRootObserver',
|
||||
);
|
||||
}
|
||||
|
||||
async #observeRootItems() {
|
||||
await this.#init;
|
||||
if (!this.#treeContext?.requestRootItems) throw new Error('Tree does not support root items');
|
||||
|
||||
const { asObservable } = await this.#treeContext.requestRootItems();
|
||||
|
||||
if (asObservable) {
|
||||
this.observe(
|
||||
asObservable(),
|
||||
(rootItems) => {
|
||||
const oldValue = this._items;
|
||||
this._items = rootItems;
|
||||
this.requestUpdate('_items', oldValue);
|
||||
},
|
||||
'umbRootItemsObserver',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getSelection() {
|
||||
return this.#treeContext?.selection.getSelection();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html` ${this.#renderTreeRoot()} ${this.#renderRootItems()}`;
|
||||
}
|
||||
|
||||
#renderTreeRoot() {
|
||||
if (this.hideTreeRoot || this._treeRoot === undefined) return nothing;
|
||||
return html`
|
||||
<umb-tree-item .entityType=${this._treeRoot.entityType} .props=${{ item: this._treeRoot }}></umb-tree-item>
|
||||
`;
|
||||
}
|
||||
|
||||
#renderRootItems() {
|
||||
if (this._items?.length === 0) return nothing;
|
||||
return html`
|
||||
${repeat(
|
||||
this._items,
|
||||
(item, index) => item.name + '___' + index,
|
||||
(item) => html`<umb-tree-item .entityType=${item.entityType} .props=${{ item }}></umb-tree-item>`,
|
||||
)}
|
||||
${this.#renderPaging()}
|
||||
`;
|
||||
}
|
||||
|
||||
#onLoadMoreClick = (event: any) => {
|
||||
event.stopPropagation();
|
||||
const next = (this._currentPage = this._currentPage + 1);
|
||||
this.#treeContext?.pagination.setCurrentPageNumber(next);
|
||||
};
|
||||
|
||||
#renderPaging() {
|
||||
if (this._totalPages <= 1 || this._currentPage === this._totalPages) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html` <uui-button @click=${this.#onLoadMoreClick} label="Load more"></uui-button> `;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbDefaultTreeElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-default-tree': UmbDefaultTreeElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { UmbDefaultTreeElement as UmbTreeDefaultElement } from './default-tree.element.js';
|
||||
export { UmbDefaultTreeContext as UmbTreeDefaultContext } from './default-tree.context.js';
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
const defaultTreeKind: UmbBackofficeManifestKind = {
|
||||
type: 'kind',
|
||||
alias: 'Umb.Kind.Tree.Default',
|
||||
matchKind: 'default',
|
||||
matchType: 'tree',
|
||||
manifest: {
|
||||
type: 'tree',
|
||||
api: () => import('./default-tree.context.js'),
|
||||
element: () => import('./default-tree.element.js'),
|
||||
},
|
||||
};
|
||||
|
||||
export const manifests = [defaultTreeKind];
|
||||
@@ -1,27 +1,14 @@
|
||||
export * from './components/index.js';
|
||||
export * from './tree-item-default/index.js';
|
||||
export * from './tree-item-base/index.js';
|
||||
export * from './tree-item/index.js';
|
||||
export * from './default/index.js';
|
||||
export * from './data/index.js';
|
||||
export * from './tree-menu-item-default/index.js';
|
||||
export * from './tree.context.js';
|
||||
export * from './tree.element.js';
|
||||
export * from './types.js';
|
||||
export * from './tree-repository.interface.js';
|
||||
export * from './tree-store.interface.js';
|
||||
|
||||
// Unique
|
||||
export * from './unique-tree-store.js';
|
||||
export * from './unique-tree-item/index.js';
|
||||
|
||||
// Data Source
|
||||
export * from './data-source/index.js';
|
||||
|
||||
// Folder
|
||||
export * from './folder/index.js';
|
||||
export * from './tree.element.js';
|
||||
|
||||
//
|
||||
export {
|
||||
UmbReloadTreeItemChildrenEntityAction,
|
||||
UmbReloadTreeItemChildrenRequestEntityActionEvent,
|
||||
} from './reload-tree-item-children/index.js';
|
||||
|
||||
export { UmbTreeRepositoryBase } from './tree-repository-base.js';
|
||||
export * from './types.js';
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
import { manifests as folderManifests } from './folder/manifests.js';
|
||||
import { manifests as defaultTreeItemManifests } from './tree-item/tree-item-default/manifests.js';
|
||||
import { manifests as defaultTreeManifests } from './default/manifests.js';
|
||||
import { manifests as treePickerManifests } from './tree-picker/manifests.js';
|
||||
|
||||
export const manifests = [...folderManifests];
|
||||
export const manifests = [
|
||||
...defaultTreeManifests,
|
||||
...folderManifests,
|
||||
...defaultTreeItemManifests,
|
||||
...treePickerManifests,
|
||||
];
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import type { UmbSelectionManager } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
// TODO: update interface
|
||||
export interface UmbTreeContext extends UmbContextBase<UmbTreeContext> {
|
||||
selection: UmbSelectionManager;
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './tree-item-base.context.js';
|
||||
export * from './tree-item-base.element.js';
|
||||
@@ -1,20 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/web-components';
|
||||
import './tree-item-base.element.js';
|
||||
import type { UmbTreeItemBaseElement } from './tree-item-base.element.js';
|
||||
|
||||
// TODO: provide tree item context to make this element render properly
|
||||
const meta: Meta<UmbTreeItemBaseElement> = {
|
||||
title: 'Components/Tree/Tree Item Default',
|
||||
component: 'umb-tree-item-default',
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<UmbTreeItemBaseElement>;
|
||||
|
||||
export const Overview: Story = {
|
||||
args: {},
|
||||
};
|
||||
|
||||
export const WithChildren: Story = {
|
||||
args: {},
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './tree-item.context.interface.js';
|
||||
export * from './tree-item.element.js';
|
||||
@@ -1,36 +0,0 @@
|
||||
import type { UmbTreeItemModelBase } from '../types.js';
|
||||
import { css, html, nothing, customElement, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type { ManifestTreeItem } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
|
||||
@customElement('umb-tree-item-default')
|
||||
export class UmbTreeItemDefaultElement extends UmbLitElement {
|
||||
@property({ type: Object, attribute: false })
|
||||
item?: UmbTreeItemModelBase;
|
||||
|
||||
render() {
|
||||
if (!this.item) return nothing;
|
||||
return html`<umb-extension-slot
|
||||
type="treeItem"
|
||||
.filter=${(manifests: ManifestTreeItem) => manifests.meta.entityTypes.includes(this.item!.entityType)}
|
||||
.props=${{
|
||||
item: this.item,
|
||||
}}></umb-extension-slot>`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
UmbTextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-tree-item-default': UmbTreeItemDefaultElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './tree-item-context.interface.js';
|
||||
export * from './tree-item.element.js';
|
||||
export * from './tree-item-default/index.js';
|
||||
export * from './tree-item-base/index.js';
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './tree-item-context-base.js';
|
||||
export * from './tree-item-element-base.js';
|
||||
@@ -1,28 +1,34 @@
|
||||
import type { UmbTreeItemContext } from '../tree-item-default/tree-item.context.interface.js';
|
||||
import type { UmbTreeContextBase } from '../tree.context.js';
|
||||
import type { UmbTreeItemModelBase } from '../types.js';
|
||||
import { UmbReloadTreeItemChildrenRequestEntityActionEvent } from '../reload-tree-item-children/index.js';
|
||||
import type { UmbTreeItemContext } from '../tree-item-context.interface.js';
|
||||
import { UMB_DEFAULT_TREE_CONTEXT, type UmbDefaultTreeContext } from '../../default/default-tree.context.js';
|
||||
import type { UmbTreeItemModelBase } from '../../types.js';
|
||||
import { UmbReloadTreeItemChildrenRequestEntityActionEvent } from '../../reload-tree-item-children/index.js';
|
||||
import { map } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import { UMB_SECTION_CONTEXT, UMB_SECTION_SIDEBAR_CONTEXT } from '@umbraco-cms/backoffice/section';
|
||||
import type { UmbSectionContext, UmbSectionSidebarContext } from '@umbraco-cms/backoffice/section';
|
||||
import type { ManifestTreeItem } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UmbBooleanState, UmbDeepState, UmbStringState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
||||
import { UMB_ACTION_EVENT_CONTEXT, type UmbActionEventContext } from '@umbraco-cms/backoffice/action';
|
||||
import type { UmbEntityActionEvent } from '@umbraco-cms/backoffice/entity-action';
|
||||
import { UmbPaginationManager } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
|
||||
export type UmbTreeItemUniqueFunction<TreeItemType extends UmbTreeItemModelBase> = (
|
||||
x: TreeItemType,
|
||||
) => string | null | undefined;
|
||||
|
||||
export class UmbTreeItemContextBase<TreeItemType extends UmbTreeItemModelBase>
|
||||
extends UmbControllerBase
|
||||
export abstract class UmbTreeItemContextBase<TreeItemType extends UmbTreeItemModelBase>
|
||||
extends UmbContextBase<UmbTreeItemContext<TreeItemType>>
|
||||
implements UmbTreeItemContext<TreeItemType>
|
||||
{
|
||||
public unique?: string | null;
|
||||
public entityType?: string;
|
||||
public readonly pagination = new UmbPaginationManager();
|
||||
|
||||
#manifest?: ManifestTreeItem;
|
||||
|
||||
#treeItem = new UmbDeepState<TreeItemType | undefined>(undefined);
|
||||
treeItem = this.#treeItem.asObservable();
|
||||
@@ -52,17 +58,46 @@ export class UmbTreeItemContextBase<TreeItemType extends UmbTreeItemModelBase>
|
||||
#path = new UmbStringState('');
|
||||
path = this.#path.asObservable();
|
||||
|
||||
treeContext?: UmbTreeContextBase<TreeItemType>;
|
||||
treeContext?: UmbDefaultTreeContext<TreeItemType>;
|
||||
#sectionContext?: UmbSectionContext;
|
||||
#sectionSidebarContext?: UmbSectionSidebarContext;
|
||||
#actionEventContext?: UmbActionEventContext;
|
||||
#getUniqueFunction: UmbTreeItemUniqueFunction<TreeItemType>;
|
||||
|
||||
// TODO: get this from the tree context
|
||||
#paging = {
|
||||
skip: 0,
|
||||
take: 50,
|
||||
};
|
||||
|
||||
constructor(host: UmbControllerHost, getUniqueFunction: UmbTreeItemUniqueFunction<TreeItemType>) {
|
||||
super(host);
|
||||
super(host, UMB_TREE_ITEM_CONTEXT);
|
||||
this.pagination.setPageSize(this.#paging.take);
|
||||
this.#getUniqueFunction = getUniqueFunction;
|
||||
this.#consumeContexts();
|
||||
this.provideContext(UMB_TREE_ITEM_CONTEXT, this);
|
||||
|
||||
// listen for page changes on the pagination manager
|
||||
this.pagination.addEventListener(UmbChangeEvent.TYPE, this.#onPageChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the manifest
|
||||
* @param {ManifestCollection} manifest
|
||||
* @memberof UmbCollectionContext
|
||||
*/
|
||||
// TODO: Revisit if this instead should be a getter/setter property because it might be set by extension initializer
|
||||
public setManifest(manifest: ManifestTreeItem | undefined) {
|
||||
if (this.#manifest === manifest) return;
|
||||
this.#manifest = manifest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the manifest.
|
||||
* @return {ManifestCollection}
|
||||
* @memberof UmbCollectionContext
|
||||
*/
|
||||
public getManifest() {
|
||||
return this.#manifest;
|
||||
}
|
||||
|
||||
public setTreeItem(treeItem: TreeItemType | undefined) {
|
||||
@@ -91,12 +126,23 @@ export class UmbTreeItemContextBase<TreeItemType extends UmbTreeItemModelBase>
|
||||
|
||||
public async requestChildren() {
|
||||
if (this.unique === undefined) throw new Error('Could not request children, unique key is missing');
|
||||
|
||||
// TODO: wait for tree context to be ready
|
||||
const repository = this.treeContext?.getRepository();
|
||||
if (!repository) throw new Error('Could not request children, repository is missing');
|
||||
|
||||
this.#isLoading.setValue(true);
|
||||
const response = await this.treeContext!.requestChildrenOf(this.unique);
|
||||
const { data, error, asObservable } = await repository.requestTreeItemsOf({
|
||||
parentUnique: this.unique,
|
||||
skip: this.#paging.skip,
|
||||
take: this.#paging.take,
|
||||
});
|
||||
|
||||
if (data) {
|
||||
this.pagination.setTotalItems(data.total);
|
||||
}
|
||||
|
||||
this.#isLoading.setValue(false);
|
||||
return response;
|
||||
return { data, error, asObservable };
|
||||
}
|
||||
|
||||
public toggleContextMenu() {
|
||||
@@ -127,7 +173,7 @@ export class UmbTreeItemContextBase<TreeItemType extends UmbTreeItemModelBase>
|
||||
this.#sectionSidebarContext = instance;
|
||||
});
|
||||
|
||||
this.consumeContext('umbTreeContext', (treeContext: UmbTreeContextBase<TreeItemType>) => {
|
||||
this.consumeContext(UMB_DEFAULT_TREE_CONTEXT, (treeContext: UmbDefaultTreeContext<TreeItemType>) => {
|
||||
this.treeContext = treeContext;
|
||||
this.#observeIsSelectable();
|
||||
this.#observeIsSelected();
|
||||
@@ -209,10 +255,16 @@ export class UmbTreeItemContextBase<TreeItemType extends UmbTreeItemModelBase>
|
||||
async #observeHasChildren() {
|
||||
if (!this.treeContext || !this.unique) return;
|
||||
|
||||
const observable = await this.treeContext.childrenOf(this.unique);
|
||||
const repository = this.treeContext.getRepository();
|
||||
if (!repository) return;
|
||||
|
||||
// TODO: use createObservablePart, to prevent unnesecary changes.
|
||||
const hasChildrenObservable = (await repository.treeItemsOf(this.unique)).pipe(
|
||||
map((children) => children.length > 0),
|
||||
);
|
||||
|
||||
// observe if any children will be added runtime to a tree item. Nested items/folders etc.
|
||||
this.observe(observable.pipe(map((children) => children.length > 0)), (hasChildren) => {
|
||||
this.observe(hasChildrenObservable, (hasChildren) => {
|
||||
// we need to skip the first value, because it will also return false until a child is in the store
|
||||
// we therefor rely on the value from the tree item itself
|
||||
if (this.#hasChildrenInitValueFlag === true) {
|
||||
@@ -230,6 +282,12 @@ export class UmbTreeItemContextBase<TreeItemType extends UmbTreeItemModelBase>
|
||||
this.requestChildren();
|
||||
};
|
||||
|
||||
#onPageChange = (event: UmbChangeEvent) => {
|
||||
const target = event.target as UmbPaginationManager;
|
||||
this.#paging.skip = target.getSkip();
|
||||
this.requestChildren();
|
||||
};
|
||||
|
||||
// TODO: use router context
|
||||
constructPath(pathname: string, entityType: string, unique: string | null) {
|
||||
return `section/${pathname}/workspace/${entityType}/edit/${unique}`;
|
||||
@@ -1,16 +1,23 @@
|
||||
import type { UmbTreeItemContext } from '../tree-item-default/index.js';
|
||||
import type { UmbTreeItemModelBase } from '../types.js';
|
||||
import { UMB_TREE_ITEM_CONTEXT } from './tree-item-base.context.js';
|
||||
import { html, nothing, customElement, state, ifDefined, repeat } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UmbTreeItemContext } from '../index.js';
|
||||
import type { UmbTreeItemModelBase } from '../../types.js';
|
||||
import { UMB_TREE_ITEM_CONTEXT } from './tree-item-context-base.js';
|
||||
import { html, nothing, state, ifDefined, repeat, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
|
||||
@customElement('umb-tree-item-base')
|
||||
export class UmbTreeItemBaseElement extends UmbLitElement {
|
||||
@state()
|
||||
private _item?: UmbTreeItemModelBase;
|
||||
// eslint-disable-next-line local-rules/enforce-element-suffix-on-element-class-name
|
||||
export abstract class UmbTreeItemElementBase<TreeItemModelType extends UmbTreeItemModelBase> extends UmbLitElement {
|
||||
_item?: TreeItemModelType;
|
||||
@property({ type: Object, attribute: false })
|
||||
get item(): TreeItemModelType | undefined {
|
||||
return this._item;
|
||||
}
|
||||
set item(newVal: TreeItemModelType) {
|
||||
this._item = newVal;
|
||||
this.#initTreeItem();
|
||||
}
|
||||
|
||||
@state()
|
||||
private _childItems?: UmbTreeItemModelBase[];
|
||||
private _childItems?: TreeItemModelType[];
|
||||
|
||||
@state()
|
||||
private _href?: string;
|
||||
@@ -33,7 +40,13 @@ export class UmbTreeItemBaseElement extends UmbLitElement {
|
||||
@state()
|
||||
private _iconSlotHasChildren = false;
|
||||
|
||||
#treeItemContext?: UmbTreeItemContext<UmbTreeItemModelBase>;
|
||||
@state()
|
||||
private _totalPages = 1;
|
||||
|
||||
@state()
|
||||
private _currentPage = 1;
|
||||
|
||||
#treeItemContext?: UmbTreeItemContext<TreeItemModelType>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -41,6 +54,9 @@ export class UmbTreeItemBaseElement extends UmbLitElement {
|
||||
this.consumeContext(UMB_TREE_ITEM_CONTEXT, (instance) => {
|
||||
this.#treeItemContext = instance;
|
||||
if (!this.#treeItemContext) return;
|
||||
|
||||
this.#initTreeItem();
|
||||
|
||||
// TODO: investigate if we can make an observe decorator
|
||||
this.observe(this.#treeItemContext.treeItem, (value) => (this._item = value));
|
||||
this.observe(this.#treeItemContext.hasChildren, (value) => (this._hasChildren = value));
|
||||
@@ -49,9 +65,17 @@ export class UmbTreeItemBaseElement extends UmbLitElement {
|
||||
this.observe(this.#treeItemContext.isSelectable, (value) => (this._isSelectable = value));
|
||||
this.observe(this.#treeItemContext.isSelected, (value) => (this._isSelected = value));
|
||||
this.observe(this.#treeItemContext.path, (value) => (this._href = value));
|
||||
this.observe(this.#treeItemContext.pagination.currentPage, (value) => (this._currentPage = value));
|
||||
this.observe(this.#treeItemContext.pagination.totalPages, (value) => (this._totalPages = value));
|
||||
});
|
||||
}
|
||||
|
||||
#initTreeItem() {
|
||||
if (!this.#treeItemContext) return;
|
||||
if (!this._item) return;
|
||||
this.#treeItemContext.setTreeItem(this._item);
|
||||
}
|
||||
|
||||
private _handleSelectedItem(event: Event) {
|
||||
event.stopPropagation();
|
||||
this.#treeItemContext?.select();
|
||||
@@ -81,9 +105,11 @@ export class UmbTreeItemBaseElement extends UmbLitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _openActions() {
|
||||
this.#treeItemContext?.toggleContextMenu();
|
||||
}
|
||||
#onLoadMoreClick = (event: any) => {
|
||||
event.stopPropagation();
|
||||
const next = (this._currentPage = this._currentPage + 1);
|
||||
this.#treeItemContext?.pagination.setCurrentPageNumber(next);
|
||||
};
|
||||
|
||||
// Note: Currently we want to prevent opening when the item is in a selectable context, but this might change in the future.
|
||||
// If we like to be able to open items in selectable context, then we might want to make it as a menu item action, so you have to click ... and chose an action called 'Edit'
|
||||
@@ -100,8 +126,9 @@ export class UmbTreeItemBaseElement extends UmbLitElement {
|
||||
.hasChildren=${this._hasChildren}
|
||||
label="${ifDefined(this._item?.name)}"
|
||||
href="${ifDefined(this._isSelectableContext ? undefined : this._href)}">
|
||||
${this.#renderIconContainer()} ${this.#renderLabel()} ${this.#renderActions()} ${this.#renderChildItems()}
|
||||
${this.renderIconContainer()} ${this.renderLabel()} ${this.#renderActions()} ${this.#renderChildItems()}
|
||||
<slot></slot>
|
||||
${this.#renderPaging()}
|
||||
</uui-menu-item>
|
||||
`;
|
||||
}
|
||||
@@ -110,7 +137,7 @@ export class UmbTreeItemBaseElement extends UmbLitElement {
|
||||
return (e.target as HTMLSlotElement).assignedNodes({ flatten: true }).length > 0;
|
||||
};
|
||||
|
||||
#renderIconContainer() {
|
||||
renderIconContainer() {
|
||||
return html`
|
||||
<slot
|
||||
name="icon"
|
||||
@@ -137,7 +164,7 @@ export class UmbTreeItemBaseElement extends UmbLitElement {
|
||||
return html`<uui-icon slot="icon" name="icon-circle-dotted"></uui-icon>`;
|
||||
}
|
||||
|
||||
#renderLabel() {
|
||||
renderLabel() {
|
||||
return html`<slot name="label" slot="label"></slot>`;
|
||||
}
|
||||
|
||||
@@ -157,17 +184,18 @@ export class UmbTreeItemBaseElement extends UmbLitElement {
|
||||
${this._childItems
|
||||
? repeat(
|
||||
this._childItems,
|
||||
// TODO: get unique here instead of name. we might be able to get it from the context
|
||||
(item) => item.name,
|
||||
(item) => html`<umb-tree-item-default .item=${item}></umb-tree-item-default>`,
|
||||
(item, index) => item.name + '___' + index,
|
||||
(item) => html`<umb-tree-item .entityType=${item.entityType} .props=${{ item }}></umb-tree-item>`,
|
||||
)
|
||||
: ''}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-tree-item-base': UmbTreeItemBaseElement;
|
||||
#renderPaging() {
|
||||
if (this._totalPages <= 1 || this._currentPage === this._totalPages) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html` <uui-button @click=${this.#onLoadMoreClick} label="Load more"></uui-button> `;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { UmbTreeItemModelBase } from '../types.js';
|
||||
import type { UmbPaginationManager } from '../../utils/pagination-manager/pagination.manager.js';
|
||||
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import type { ProblemDetails } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import type { UmbPagedModel } from '@umbraco-cms/backoffice/repository';
|
||||
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
|
||||
|
||||
export interface UmbTreeItemContext<TreeItemType extends UmbTreeItemModelBase> {
|
||||
export interface UmbTreeItemContext<TreeItemType extends UmbTreeItemModelBase> extends UmbApi {
|
||||
unique?: string | null;
|
||||
entityType?: string;
|
||||
treeItem: Observable<TreeItemType | undefined>;
|
||||
@@ -15,7 +17,7 @@ export interface UmbTreeItemContext<TreeItemType extends UmbTreeItemModelBase> {
|
||||
isActive: Observable<boolean>;
|
||||
hasActions: Observable<boolean>;
|
||||
path: Observable<string>;
|
||||
|
||||
pagination: UmbPaginationManager;
|
||||
setTreeItem(treeItem: TreeItemType | undefined): void;
|
||||
requestChildren(): Promise<{
|
||||
data?: UmbPagedModel<TreeItemType> | undefined;
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './tree-item-default.context.js';
|
||||
export * from './tree-item-default.element.js';
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
const kind: UmbBackofficeManifestKind = {
|
||||
type: 'kind',
|
||||
alias: 'Umb.Kind.TreeItem.Default',
|
||||
matchKind: 'default',
|
||||
matchType: 'treeItem',
|
||||
manifest: {
|
||||
type: 'treeItem',
|
||||
api: () => import('./tree-item-default.context.js'),
|
||||
element: () => import('./tree-item-default.element.js'),
|
||||
},
|
||||
};
|
||||
|
||||
export const manifests = [kind];
|
||||
@@ -1,11 +1,13 @@
|
||||
import { UmbTreeItemContextBase } from '../tree-item-base/tree-item-base.context.js';
|
||||
import type { UmbUniqueTreeItemModel } from '../types.js';
|
||||
import { UmbTreeItemContextBase } from '../tree-item-base/index.js';
|
||||
import type { UmbUniqueTreeItemModel } from '../../types.js';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
export class UmbUniqueTreeItemContext<
|
||||
export class UmbDefaultTreeItemContext<
|
||||
TreeItemModelType extends UmbUniqueTreeItemModel,
|
||||
> extends UmbTreeItemContextBase<TreeItemModelType> {
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host, (x: UmbUniqueTreeItemModel) => x.unique);
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbDefaultTreeItemContext;
|
||||
@@ -0,0 +1,14 @@
|
||||
import { UmbTreeItemElementBase } from '../tree-item-base/index.js';
|
||||
import type { UmbUniqueTreeItemModel } from '../../types.js';
|
||||
import { customElement } from '@umbraco-cms/backoffice/external/lit';
|
||||
|
||||
@customElement('umb-default-tree-item')
|
||||
export class UmbDefaultTreeItemElement extends UmbTreeItemElementBase<UmbUniqueTreeItemModel> {}
|
||||
|
||||
export default UmbDefaultTreeItemElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-default-tree-item': UmbDefaultTreeItemElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { customElement, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { ManifestTreeItem } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UmbExtensionInitializerElementBase, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
@customElement('umb-tree-item')
|
||||
export class UmbTreeItemElement extends UmbExtensionInitializerElementBase<ManifestTreeItem> {
|
||||
_entityType?: string;
|
||||
@property({ type: String, reflect: true })
|
||||
get entityType() {
|
||||
return this._entityType;
|
||||
}
|
||||
set entityType(newVal) {
|
||||
this._entityType = newVal;
|
||||
this.#observeManifest();
|
||||
}
|
||||
|
||||
#observeManifest() {
|
||||
if (!this._entityType) return;
|
||||
|
||||
const filterByEntityType = (manifest: ManifestTreeItem) => {
|
||||
if (!this._entityType) return false;
|
||||
return manifest.meta.entityTypes.includes(this._entityType);
|
||||
};
|
||||
|
||||
this.observe(
|
||||
umbExtensionsRegistry.byTypeAndFilter(this.getExtensionType(), filterByEntityType),
|
||||
(manifests) => {
|
||||
if (!manifests) return;
|
||||
// TODO: what should we do if there are multiple tree items for an entity type?
|
||||
const manifest = manifests[0];
|
||||
this.createApi(manifest);
|
||||
this.createElement(manifest);
|
||||
},
|
||||
'umbObserveTreeManifest',
|
||||
);
|
||||
}
|
||||
|
||||
getExtensionType() {
|
||||
return 'treeItem';
|
||||
}
|
||||
|
||||
getDefaultElementName() {
|
||||
return 'umb-default-tree-item';
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-tree-item': UmbTreeItemElement;
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,13 @@ export class UmbMenuItemTreeDefaultElement extends UmbLitElement implements UmbM
|
||||
? html`
|
||||
<umb-tree
|
||||
alias=${this.manifest?.meta.treeAlias}
|
||||
?hide-tree-root=${this.manifest.meta.hideTreeRoot === true}></umb-tree>
|
||||
.props=${{
|
||||
hideTreeRoot: this.manifest?.meta.hideTreeRoot === true,
|
||||
selectionConfiguration: {
|
||||
selectable: false,
|
||||
multiple: false,
|
||||
},
|
||||
}}></umb-tree>
|
||||
`
|
||||
: nothing;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export const manifests = [
|
||||
{
|
||||
type: 'modal',
|
||||
alias: 'Umb.Modal.TreePicker',
|
||||
name: 'Tree Picker Modal',
|
||||
js: () => import('./tree-picker-modal.element.js'),
|
||||
},
|
||||
];
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { UmbTreeSelectionConfiguration } from '../types.js';
|
||||
import { html, customElement, state, ifDefined } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UmbTreePickerModalData, UmbPickerModalValue } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbDeselectedEvent, UmbSelectedEvent, UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
import type { UmbTreeElement, UmbTreeItemModelBase, UmbTreeSelectionConfiguration } from '@umbraco-cms/backoffice/tree';
|
||||
import type { UmbTreeElement, UmbTreeItemModelBase } from '@umbraco-cms/backoffice/tree';
|
||||
|
||||
@customElement('umb-tree-picker-modal')
|
||||
export class UmbTreePickerModalElement<TreeItemType extends UmbTreeItemModelBase> extends UmbModalBaseElement<
|
||||
@@ -51,14 +52,16 @@ export class UmbTreePickerModalElement<TreeItemType extends UmbTreeItemModelBase
|
||||
<umb-body-layout headline="Select">
|
||||
<uui-box>
|
||||
<umb-tree
|
||||
?hide-tree-root=${this.data?.hideTreeRoot}
|
||||
alias=${ifDefined(this.data?.treeAlias)}
|
||||
.props=${{
|
||||
hideTreeRoot: this.data?.hideTreeRoot,
|
||||
selectionConfiguration: this._selectionConfiguration,
|
||||
filter: this.data?.filter,
|
||||
selectableFilter: this.data?.pickableFilter,
|
||||
}}
|
||||
@selection-change=${this.#onSelectionChange}
|
||||
@selected=${this.#onSelected}
|
||||
@deselected=${this.#onDeselected}
|
||||
.selectionConfiguration=${this._selectionConfiguration}
|
||||
.filter=${this.data?.filter}
|
||||
.selectableFilter=${this.data?.pickableFilter}></umb-tree>
|
||||
@deselected=${this.#onDeselected}></umb-tree>
|
||||
</uui-box>
|
||||
<div slot="actions">
|
||||
<uui-button label=${this.localize.term('general_close')} @click=${this._rejectModal}></uui-button>
|
||||
@@ -1,139 +1,22 @@
|
||||
import { UmbTreeContextBase } from './tree.context.js';
|
||||
import type { UmbTreeItemModelBase } from './types.js';
|
||||
import { html, nothing, customElement, property, state, repeat } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import type { UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
|
||||
|
||||
import './tree-item-default/tree-item.element.js';
|
||||
import './tree-item-base/tree-item-base.element.js';
|
||||
|
||||
export type UmbTreeSelectionConfiguration = {
|
||||
multiple?: boolean;
|
||||
selectable?: boolean;
|
||||
selection?: Array<string | null>;
|
||||
};
|
||||
import { customElement } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { ManifestTree } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UmbExtensionInitializerElementBase } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
@customElement('umb-tree')
|
||||
export class UmbTreeElement extends UmbLitElement {
|
||||
@property({ type: String, reflect: true })
|
||||
set alias(newVal) {
|
||||
this.#treeContext.setTreeAlias(newVal);
|
||||
}
|
||||
get alias() {
|
||||
return this.#treeContext.getTreeAlias();
|
||||
export class UmbTreeElement extends UmbExtensionInitializerElementBase<ManifestTree> {
|
||||
getExtensionType() {
|
||||
return 'tree';
|
||||
}
|
||||
|
||||
private _selectionConfiguration: UmbTreeSelectionConfiguration = {
|
||||
multiple: false,
|
||||
selectable: true,
|
||||
selection: [],
|
||||
};
|
||||
|
||||
@property({ type: Object })
|
||||
set selectionConfiguration(config: UmbTreeSelectionConfiguration) {
|
||||
this._selectionConfiguration = config;
|
||||
this.#treeContext.selection.setMultiple(config.multiple ?? false);
|
||||
this.#treeContext.selection.setSelectable(config.selectable ?? true);
|
||||
this.#treeContext.selection.setSelection(config.selection ?? []);
|
||||
}
|
||||
get selectionConfiguration(): UmbTreeSelectionConfiguration {
|
||||
return this._selectionConfiguration;
|
||||
}
|
||||
|
||||
// TODO: what is the best name for this functionality?
|
||||
private _hideTreeRoot = false;
|
||||
@property({ type: Boolean, attribute: 'hide-tree-root' })
|
||||
set hideTreeRoot(newVal: boolean) {
|
||||
const oldVal = this._hideTreeRoot;
|
||||
this._hideTreeRoot = newVal;
|
||||
if (newVal === true) {
|
||||
this.#observeRootItems();
|
||||
}
|
||||
|
||||
this.requestUpdate('hideTreeRoot', oldVal);
|
||||
}
|
||||
get hideTreeRoot() {
|
||||
return this._hideTreeRoot;
|
||||
}
|
||||
|
||||
@property()
|
||||
set selectableFilter(newVal) {
|
||||
this.#treeContext.selectableFilter = newVal;
|
||||
}
|
||||
get selectableFilter() {
|
||||
return this.#treeContext.selectableFilter;
|
||||
}
|
||||
|
||||
@property()
|
||||
set filter(newVal) {
|
||||
this.#treeContext.filter = newVal;
|
||||
}
|
||||
get filter() {
|
||||
return this.#treeContext.filter;
|
||||
}
|
||||
|
||||
@state()
|
||||
private _items: UmbTreeItemModelBase[] = [];
|
||||
|
||||
@state()
|
||||
private _treeRoot?: UmbTreeItemModelBase;
|
||||
|
||||
#treeContext = new UmbTreeContextBase<UmbTreeItemModelBase>(this);
|
||||
#rootItemsObserver?: UmbObserverController<Array<UmbTreeItemModelBase>>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.#observeTreeRoot();
|
||||
}
|
||||
|
||||
#observeTreeRoot() {
|
||||
this.observe(
|
||||
this.#treeContext.treeRoot,
|
||||
(treeRoot) => {
|
||||
this._treeRoot = treeRoot;
|
||||
},
|
||||
'umbTreeRootObserver',
|
||||
);
|
||||
}
|
||||
|
||||
async #observeRootItems() {
|
||||
if (!this.#treeContext?.requestRootItems) throw new Error('Tree does not support root items');
|
||||
this.#rootItemsObserver?.destroy();
|
||||
|
||||
const { asObservable } = await this.#treeContext.requestRootItems();
|
||||
|
||||
if (asObservable) {
|
||||
this.#rootItemsObserver = this.observe(asObservable(), (rootItems) => {
|
||||
const oldValue = this._items;
|
||||
this._items = rootItems;
|
||||
this.requestUpdate('_items', oldValue);
|
||||
});
|
||||
}
|
||||
getDefaultElementName() {
|
||||
return 'umb-default-tree';
|
||||
}
|
||||
|
||||
getSelection() {
|
||||
return this.#treeContext.selection.getSelection();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html` ${this.#renderTreeRoot()} ${this.#renderRootItems()}`;
|
||||
}
|
||||
|
||||
#renderTreeRoot() {
|
||||
if (this.hideTreeRoot || this._treeRoot === undefined) return nothing;
|
||||
return html` <umb-tree-item-default .item=${this._treeRoot}></umb-tree-item-default> `;
|
||||
}
|
||||
|
||||
#renderRootItems() {
|
||||
if (this._items?.length === 0) return nothing;
|
||||
return html`
|
||||
${repeat(
|
||||
this._items,
|
||||
// TODO: use unique here:
|
||||
(item, index) => item.name + '___' + index,
|
||||
(item) => html`<umb-tree-item-default .item=${item}></umb-tree-item-default>`,
|
||||
)}
|
||||
`;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// TODO: make base interface for a tree element
|
||||
return this._element?.getSelection?.() ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Meta, StoryObj } from '@storybook/web-components';
|
||||
import './tree.element.js';
|
||||
import './default/default-tree.element.js';
|
||||
import type { UmbTreeElement } from './tree.element.js';
|
||||
|
||||
const meta: Meta<UmbTreeElement> = {
|
||||
|
||||
@@ -14,3 +14,9 @@ export interface UmbUniqueTreeItemModel extends UmbTreeItemModelBase {
|
||||
export interface UmbUniqueTreeRootModel extends UmbTreeItemModelBase {
|
||||
unique: null;
|
||||
}
|
||||
|
||||
export type UmbTreeSelectionConfiguration = {
|
||||
multiple?: boolean;
|
||||
selectable?: boolean;
|
||||
selection?: Array<string | null>;
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user