Merge branch 'main' into feature/property-editor-media-picker
This commit is contained in:
@@ -80,4 +80,19 @@ export class UmbExtensionRegistry {
|
||||
)
|
||||
) as Observable<Array<ExtensionType>>;
|
||||
}
|
||||
|
||||
extensionsSortedByTypeAndWeight<ExtensionType = ManifestBase>(): Observable<Array<ExtensionType>> {
|
||||
return this.extensions.pipe(
|
||||
map((exts) => exts
|
||||
.sort((a, b) => {
|
||||
// If type is the same, sort by weight
|
||||
if (a.type === b.type) {
|
||||
return (a.weight || 0) - (b.weight || 0);
|
||||
}
|
||||
|
||||
// Otherwise sort by type
|
||||
return a.type.localeCompare(b.type);
|
||||
}))
|
||||
) as Observable<Array<ExtensionType>>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ManifestBase } from './models';
|
||||
import type { UmbTreeRepositoryFactory } from '@umbraco-cms/models';
|
||||
|
||||
export interface ManifestTree extends ManifestBase {
|
||||
type: 'tree';
|
||||
@@ -6,5 +7,6 @@ export interface ManifestTree extends ManifestBase {
|
||||
}
|
||||
|
||||
export interface MetaTree {
|
||||
storeAlias: string;
|
||||
storeAlias?: string;
|
||||
repository?: UmbTreeRepositoryFactory;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ import {
|
||||
DocumentTypeTreeItem,
|
||||
EntityTreeItem,
|
||||
FolderTreeItem,
|
||||
PagedEntityTreeItem,
|
||||
ProblemDetails,
|
||||
} from '@umbraco-cms/backend-api';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
// Extension Manifests
|
||||
export * from '@umbraco-cms/extensions-registry';
|
||||
@@ -149,3 +152,31 @@ export interface DocumentBlueprintDetails {
|
||||
icon: string;
|
||||
documentTypeKey: string;
|
||||
}
|
||||
|
||||
export interface DataSourceResponse<T = undefined> {
|
||||
data?: T;
|
||||
error?: ProblemDetails;
|
||||
}
|
||||
|
||||
// TODO; figure out why we can't add UmbControllerHostInterface as host type
|
||||
export interface UmbTreeRepositoryFactory {
|
||||
new (host: any): UmbTreeRepository;
|
||||
}
|
||||
|
||||
export interface UmbTreeRepository {
|
||||
requestRootItems: () => Promise<{
|
||||
data: PagedEntityTreeItem | undefined;
|
||||
error: ProblemDetails | undefined;
|
||||
}>;
|
||||
requestChildrenOf: (parentKey: string | null) => Promise<{
|
||||
data: PagedEntityTreeItem | undefined;
|
||||
error: ProblemDetails | undefined;
|
||||
}>;
|
||||
requestItems: (keys: string[]) => Promise<{
|
||||
data: Array<EntityTreeItem> | undefined;
|
||||
error: ProblemDetails | undefined;
|
||||
}>;
|
||||
rootItems: () => Promise<Observable<EntityTreeItem[]>>;
|
||||
childrenOf: (parentKey: string | null) => Promise<Observable<EntityTreeItem[]>>;
|
||||
items: (keys: string[]) => Promise<Observable<EntityTreeItem[]>>;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'router-slot';
|
||||
import { LitElement, PropertyValueMap } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { IRoute, RouterSlot } from 'router-slot';
|
||||
@@ -7,6 +8,8 @@ import { UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/r
|
||||
* @element umb-router-slot-element
|
||||
* @description - Component for wrapping Router Slot element, providing some local events for implementation.
|
||||
* @extends UmbRouterSlotElement
|
||||
* @fires {UmbRouterSlotInitEvent} init - fires when the media card is selected
|
||||
* @fires {UmbRouterSlotChangeEvent} change - fires when the media card is unselected
|
||||
*/
|
||||
@customElement('umb-router-slot')
|
||||
export class UmbRouterSlotElement extends LitElement {
|
||||
@@ -38,8 +41,11 @@ export class UmbRouterSlotElement extends LitElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.#router = document.createElement('router-slot');
|
||||
// Note: I decided not to use the local changestate event, because it is not fired when the route is changed from any router-slot. And for now I wanted to keep it local.
|
||||
//this.#router.addEventListener('changestate', this._onNavigationChanged);
|
||||
}
|
||||
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (this.#listening === false) {
|
||||
@@ -54,6 +60,7 @@ export class UmbRouterSlotElement extends LitElement {
|
||||
this.#listening = false;
|
||||
}
|
||||
|
||||
|
||||
protected firstUpdated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
|
||||
super.firstUpdated(_changedProperties);
|
||||
this._routerPath = this.#router.constructAbsolutePath('') || '';
|
||||
|
||||
459
src/Umbraco.Web.UI.Client/package-lock.json
generated
459
src/Umbraco.Web.UI.Client/package-lock.json
generated
@@ -28,7 +28,7 @@
|
||||
"@babel/core": "^7.20.12",
|
||||
"@mdx-js/react": "^2.2.1",
|
||||
"@open-wc/testing": "^3.1.7",
|
||||
"@playwright/test": "^1.29.2",
|
||||
"@playwright/test": "^1.30.0",
|
||||
"@storybook/addon-a11y": "^6.5.15",
|
||||
"@storybook/addon-actions": "^6.5.14",
|
||||
"@storybook/addon-essentials": "^6.5.15",
|
||||
@@ -40,7 +40,7 @@
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"@types/mocha": "^10.0.0",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.50.0",
|
||||
"@typescript-eslint/parser": "^5.48.1",
|
||||
"@web/dev-server-esbuild": "^0.3.3",
|
||||
"@web/dev-server-import-maps": "^0.0.7",
|
||||
@@ -66,7 +66,7 @@
|
||||
"rollup": "^3.10.0",
|
||||
"rollup-plugin-esbuild": "^5.0.0",
|
||||
"tiny-glob": "^0.2.9",
|
||||
"typescript": "^4.9.4",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "^4.0.4",
|
||||
"vite-plugin-static-copy": "^0.13.0",
|
||||
"vite-tsconfig-paths": "^4.0.3",
|
||||
@@ -3239,13 +3239,13 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.29.2.tgz",
|
||||
"integrity": "sha512-+3/GPwOgcoF0xLz/opTnahel1/y42PdcgZ4hs+BZGIUjtmEFSXGg+nFoaH3NSmuc7a6GSFwXDJ5L7VXpqzigNg==",
|
||||
"version": "1.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.30.0.tgz",
|
||||
"integrity": "sha512-SVxkQw1xvn/Wk/EvBnqWIq6NLo1AppwbYOjNLmyU0R1RoQ3rLEBtmjTnElcnz8VEtn11fptj1ECxK0tgURhajw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"playwright-core": "1.29.2"
|
||||
"playwright-core": "1.30.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -3254,6 +3254,18 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test/node_modules/playwright-core": {
|
||||
"version": "1.30.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.30.0.tgz",
|
||||
"integrity": "sha512-7AnRmTCf+GVYhHbLJsGUtskWTE33SwMZkybJ0v6rqR1boxq2x36U7p1vDRV7HO2IwTZgmycracLxPEJI49wu4g==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-node-resolve": {
|
||||
"version": "13.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz",
|
||||
@@ -6844,15 +6856,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "5.48.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.2.tgz",
|
||||
"integrity": "sha512-sR0Gja9Ky1teIq4qJOl0nC+Tk64/uYdX+mi+5iB//MH8gwyx8e3SOyhEzeLZEFEEfCaLf8KJq+Bd/6je1t+CAg==",
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.50.0.tgz",
|
||||
"integrity": "sha512-vwksQWSFZiUhgq3Kv7o1Jcj0DUNylwnIlGvKvLLYsq8pAWha6/WCnXUeaSoNNha/K7QSf2+jvmkxggC1u3pIwQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "5.48.2",
|
||||
"@typescript-eslint/type-utils": "5.48.2",
|
||||
"@typescript-eslint/utils": "5.48.2",
|
||||
"@typescript-eslint/scope-manager": "5.50.0",
|
||||
"@typescript-eslint/type-utils": "5.50.0",
|
||||
"@typescript-eslint/utils": "5.50.0",
|
||||
"debug": "^4.3.4",
|
||||
"grapheme-splitter": "^1.0.4",
|
||||
"ignore": "^5.2.0",
|
||||
"natural-compare-lite": "^1.4.0",
|
||||
"regexpp": "^3.2.0",
|
||||
@@ -6876,6 +6889,53 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.50.0.tgz",
|
||||
"integrity": "sha512-rt03kaX+iZrhssaT974BCmoUikYtZI24Vp/kwTSy841XhiYShlqoshRFDvN1FKKvU2S3gK+kcBW1EA7kNUrogg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "5.50.0",
|
||||
"@typescript-eslint/visitor-keys": "5.50.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": {
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.50.0.tgz",
|
||||
"integrity": "sha512-atruOuJpir4OtyNdKahiHZobPKFvZnBnfDiyEaBf6d9vy9visE7gDjlmhl+y29uxZ2ZDgvXijcungGFjGGex7w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.50.0.tgz",
|
||||
"integrity": "sha512-cdMeD9HGu6EXIeGOh2yVW6oGf9wq8asBgZx7nsR/D36gTfQ0odE5kcRYe5M81vjEFAcPeugXrHg78Imu55F6gg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "5.50.0",
|
||||
"eslint-visitor-keys": "^3.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
@@ -6954,13 +7014,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "5.48.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.48.2.tgz",
|
||||
"integrity": "sha512-QVWx7J5sPMRiOMJp5dYshPxABRoZV1xbRirqSk8yuIIsu0nvMTZesKErEA3Oix1k+uvsk8Cs8TGJ6kQ0ndAcew==",
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.50.0.tgz",
|
||||
"integrity": "sha512-dcnXfZ6OGrNCO7E5UY/i0ktHb7Yx1fV6fnQGGrlnfDhilcs6n19eIRcvLBqx6OQkrPaFlDPk3OJ0WlzQfrV0bQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/typescript-estree": "5.48.2",
|
||||
"@typescript-eslint/utils": "5.48.2",
|
||||
"@typescript-eslint/typescript-estree": "5.50.0",
|
||||
"@typescript-eslint/utils": "5.50.0",
|
||||
"debug": "^4.3.4",
|
||||
"tsutils": "^3.21.0"
|
||||
},
|
||||
@@ -6980,6 +7040,96 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": {
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.50.0.tgz",
|
||||
"integrity": "sha512-atruOuJpir4OtyNdKahiHZobPKFvZnBnfDiyEaBf6d9vy9visE7gDjlmhl+y29uxZ2ZDgvXijcungGFjGGex7w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.50.0.tgz",
|
||||
"integrity": "sha512-Gq4zapso+OtIZlv8YNAStFtT6d05zyVCK7Fx3h5inlLBx2hWuc/0465C2mg/EQDDU2LKe52+/jN4f0g9bd+kow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "5.50.0",
|
||||
"@typescript-eslint/visitor-keys": "5.50.0",
|
||||
"debug": "^4.3.4",
|
||||
"globby": "^11.1.0",
|
||||
"is-glob": "^4.0.3",
|
||||
"semver": "^7.3.7",
|
||||
"tsutils": "^3.21.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.50.0.tgz",
|
||||
"integrity": "sha512-cdMeD9HGu6EXIeGOh2yVW6oGf9wq8asBgZx7nsR/D36gTfQ0odE5kcRYe5M81vjEFAcPeugXrHg78Imu55F6gg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "5.50.0",
|
||||
"eslint-visitor-keys": "^3.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/semver": {
|
||||
"version": "7.3.8",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
|
||||
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "5.48.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.2.tgz",
|
||||
@@ -7054,16 +7204,16 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "5.48.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.48.2.tgz",
|
||||
"integrity": "sha512-2h18c0d7jgkw6tdKTlNaM7wyopbLRBiit8oAxoP89YnuBOzCZ8g8aBCaCqq7h208qUTroL7Whgzam7UY3HVLow==",
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.50.0.tgz",
|
||||
"integrity": "sha512-v/AnUFImmh8G4PH0NDkf6wA8hujNNcrwtecqW4vtQ1UOSNBaZl49zP1SHoZ/06e+UiwzHpgb5zP5+hwlYYWYAw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.9",
|
||||
"@types/semver": "^7.3.12",
|
||||
"@typescript-eslint/scope-manager": "5.48.2",
|
||||
"@typescript-eslint/types": "5.48.2",
|
||||
"@typescript-eslint/typescript-estree": "5.48.2",
|
||||
"@typescript-eslint/scope-manager": "5.50.0",
|
||||
"@typescript-eslint/types": "5.50.0",
|
||||
"@typescript-eslint/typescript-estree": "5.50.0",
|
||||
"eslint-scope": "^5.1.1",
|
||||
"eslint-utils": "^3.0.0",
|
||||
"semver": "^7.3.7"
|
||||
@@ -7079,6 +7229,80 @@
|
||||
"eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.50.0.tgz",
|
||||
"integrity": "sha512-rt03kaX+iZrhssaT974BCmoUikYtZI24Vp/kwTSy841XhiYShlqoshRFDvN1FKKvU2S3gK+kcBW1EA7kNUrogg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "5.50.0",
|
||||
"@typescript-eslint/visitor-keys": "5.50.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": {
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.50.0.tgz",
|
||||
"integrity": "sha512-atruOuJpir4OtyNdKahiHZobPKFvZnBnfDiyEaBf6d9vy9visE7gDjlmhl+y29uxZ2ZDgvXijcungGFjGGex7w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.50.0.tgz",
|
||||
"integrity": "sha512-Gq4zapso+OtIZlv8YNAStFtT6d05zyVCK7Fx3h5inlLBx2hWuc/0465C2mg/EQDDU2LKe52+/jN4f0g9bd+kow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "5.50.0",
|
||||
"@typescript-eslint/visitor-keys": "5.50.0",
|
||||
"debug": "^4.3.4",
|
||||
"globby": "^11.1.0",
|
||||
"is-glob": "^4.0.3",
|
||||
"semver": "^7.3.7",
|
||||
"tsutils": "^3.21.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.50.0.tgz",
|
||||
"integrity": "sha512-cdMeD9HGu6EXIeGOh2yVW6oGf9wq8asBgZx7nsR/D36gTfQ0odE5kcRYe5M81vjEFAcPeugXrHg78Imu55F6gg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "5.50.0",
|
||||
"eslint-visitor-keys": "^3.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils/node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
@@ -26666,9 +26890,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.9.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
|
||||
"integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==",
|
||||
"version": "4.9.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
@@ -31328,13 +31552,21 @@
|
||||
}
|
||||
},
|
||||
"@playwright/test": {
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.29.2.tgz",
|
||||
"integrity": "sha512-+3/GPwOgcoF0xLz/opTnahel1/y42PdcgZ4hs+BZGIUjtmEFSXGg+nFoaH3NSmuc7a6GSFwXDJ5L7VXpqzigNg==",
|
||||
"version": "1.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.30.0.tgz",
|
||||
"integrity": "sha512-SVxkQw1xvn/Wk/EvBnqWIq6NLo1AppwbYOjNLmyU0R1RoQ3rLEBtmjTnElcnz8VEtn11fptj1ECxK0tgURhajw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"playwright-core": "1.29.2"
|
||||
"playwright-core": "1.30.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": {
|
||||
"version": "1.30.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.30.0.tgz",
|
||||
"integrity": "sha512-7AnRmTCf+GVYhHbLJsGUtskWTE33SwMZkybJ0v6rqR1boxq2x36U7p1vDRV7HO2IwTZgmycracLxPEJI49wu4g==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@rollup/plugin-node-resolve": {
|
||||
@@ -34028,15 +34260,16 @@
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/eslint-plugin": {
|
||||
"version": "5.48.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.2.tgz",
|
||||
"integrity": "sha512-sR0Gja9Ky1teIq4qJOl0nC+Tk64/uYdX+mi+5iB//MH8gwyx8e3SOyhEzeLZEFEEfCaLf8KJq+Bd/6je1t+CAg==",
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.50.0.tgz",
|
||||
"integrity": "sha512-vwksQWSFZiUhgq3Kv7o1Jcj0DUNylwnIlGvKvLLYsq8pAWha6/WCnXUeaSoNNha/K7QSf2+jvmkxggC1u3pIwQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/scope-manager": "5.48.2",
|
||||
"@typescript-eslint/type-utils": "5.48.2",
|
||||
"@typescript-eslint/utils": "5.48.2",
|
||||
"@typescript-eslint/scope-manager": "5.50.0",
|
||||
"@typescript-eslint/type-utils": "5.50.0",
|
||||
"@typescript-eslint/utils": "5.50.0",
|
||||
"debug": "^4.3.4",
|
||||
"grapheme-splitter": "^1.0.4",
|
||||
"ignore": "^5.2.0",
|
||||
"natural-compare-lite": "^1.4.0",
|
||||
"regexpp": "^3.2.0",
|
||||
@@ -34044,6 +34277,32 @@
|
||||
"tsutils": "^3.21.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": {
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.50.0.tgz",
|
||||
"integrity": "sha512-rt03kaX+iZrhssaT974BCmoUikYtZI24Vp/kwTSy841XhiYShlqoshRFDvN1FKKvU2S3gK+kcBW1EA7kNUrogg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "5.50.0",
|
||||
"@typescript-eslint/visitor-keys": "5.50.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/types": {
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.50.0.tgz",
|
||||
"integrity": "sha512-atruOuJpir4OtyNdKahiHZobPKFvZnBnfDiyEaBf6d9vy9visE7gDjlmhl+y29uxZ2ZDgvXijcungGFjGGex7w==",
|
||||
"dev": true
|
||||
},
|
||||
"@typescript-eslint/visitor-keys": {
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.50.0.tgz",
|
||||
"integrity": "sha512-cdMeD9HGu6EXIeGOh2yVW6oGf9wq8asBgZx7nsR/D36gTfQ0odE5kcRYe5M81vjEFAcPeugXrHg78Imu55F6gg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "5.50.0",
|
||||
"eslint-visitor-keys": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
@@ -34093,15 +34352,72 @@
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/type-utils": {
|
||||
"version": "5.48.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.48.2.tgz",
|
||||
"integrity": "sha512-QVWx7J5sPMRiOMJp5dYshPxABRoZV1xbRirqSk8yuIIsu0nvMTZesKErEA3Oix1k+uvsk8Cs8TGJ6kQ0ndAcew==",
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.50.0.tgz",
|
||||
"integrity": "sha512-dcnXfZ6OGrNCO7E5UY/i0ktHb7Yx1fV6fnQGGrlnfDhilcs6n19eIRcvLBqx6OQkrPaFlDPk3OJ0WlzQfrV0bQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/typescript-estree": "5.48.2",
|
||||
"@typescript-eslint/utils": "5.48.2",
|
||||
"@typescript-eslint/typescript-estree": "5.50.0",
|
||||
"@typescript-eslint/utils": "5.50.0",
|
||||
"debug": "^4.3.4",
|
||||
"tsutils": "^3.21.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": {
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.50.0.tgz",
|
||||
"integrity": "sha512-atruOuJpir4OtyNdKahiHZobPKFvZnBnfDiyEaBf6d9vy9visE7gDjlmhl+y29uxZ2ZDgvXijcungGFjGGex7w==",
|
||||
"dev": true
|
||||
},
|
||||
"@typescript-eslint/typescript-estree": {
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.50.0.tgz",
|
||||
"integrity": "sha512-Gq4zapso+OtIZlv8YNAStFtT6d05zyVCK7Fx3h5inlLBx2hWuc/0465C2mg/EQDDU2LKe52+/jN4f0g9bd+kow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "5.50.0",
|
||||
"@typescript-eslint/visitor-keys": "5.50.0",
|
||||
"debug": "^4.3.4",
|
||||
"globby": "^11.1.0",
|
||||
"is-glob": "^4.0.3",
|
||||
"semver": "^7.3.7",
|
||||
"tsutils": "^3.21.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/visitor-keys": {
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.50.0.tgz",
|
||||
"integrity": "sha512-cdMeD9HGu6EXIeGOh2yVW6oGf9wq8asBgZx7nsR/D36gTfQ0odE5kcRYe5M81vjEFAcPeugXrHg78Imu55F6gg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "5.50.0",
|
||||
"eslint-visitor-keys": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.8",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
|
||||
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/types": {
|
||||
@@ -34152,21 +34468,62 @@
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/utils": {
|
||||
"version": "5.48.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.48.2.tgz",
|
||||
"integrity": "sha512-2h18c0d7jgkw6tdKTlNaM7wyopbLRBiit8oAxoP89YnuBOzCZ8g8aBCaCqq7h208qUTroL7Whgzam7UY3HVLow==",
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.50.0.tgz",
|
||||
"integrity": "sha512-v/AnUFImmh8G4PH0NDkf6wA8hujNNcrwtecqW4vtQ1UOSNBaZl49zP1SHoZ/06e+UiwzHpgb5zP5+hwlYYWYAw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.9",
|
||||
"@types/semver": "^7.3.12",
|
||||
"@typescript-eslint/scope-manager": "5.48.2",
|
||||
"@typescript-eslint/types": "5.48.2",
|
||||
"@typescript-eslint/typescript-estree": "5.48.2",
|
||||
"@typescript-eslint/scope-manager": "5.50.0",
|
||||
"@typescript-eslint/types": "5.50.0",
|
||||
"@typescript-eslint/typescript-estree": "5.50.0",
|
||||
"eslint-scope": "^5.1.1",
|
||||
"eslint-utils": "^3.0.0",
|
||||
"semver": "^7.3.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": {
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.50.0.tgz",
|
||||
"integrity": "sha512-rt03kaX+iZrhssaT974BCmoUikYtZI24Vp/kwTSy841XhiYShlqoshRFDvN1FKKvU2S3gK+kcBW1EA7kNUrogg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "5.50.0",
|
||||
"@typescript-eslint/visitor-keys": "5.50.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/types": {
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.50.0.tgz",
|
||||
"integrity": "sha512-atruOuJpir4OtyNdKahiHZobPKFvZnBnfDiyEaBf6d9vy9visE7gDjlmhl+y29uxZ2ZDgvXijcungGFjGGex7w==",
|
||||
"dev": true
|
||||
},
|
||||
"@typescript-eslint/typescript-estree": {
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.50.0.tgz",
|
||||
"integrity": "sha512-Gq4zapso+OtIZlv8YNAStFtT6d05zyVCK7Fx3h5inlLBx2hWuc/0465C2mg/EQDDU2LKe52+/jN4f0g9bd+kow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "5.50.0",
|
||||
"@typescript-eslint/visitor-keys": "5.50.0",
|
||||
"debug": "^4.3.4",
|
||||
"globby": "^11.1.0",
|
||||
"is-glob": "^4.0.3",
|
||||
"semver": "^7.3.7",
|
||||
"tsutils": "^3.21.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/visitor-keys": {
|
||||
"version": "5.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.50.0.tgz",
|
||||
"integrity": "sha512-cdMeD9HGu6EXIeGOh2yVW6oGf9wq8asBgZx7nsR/D36gTfQ0odE5kcRYe5M81vjEFAcPeugXrHg78Imu55F6gg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "5.50.0",
|
||||
"eslint-visitor-keys": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
@@ -49396,9 +49753,9 @@
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.9.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
|
||||
"integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==",
|
||||
"version": "4.9.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
||||
"dev": true
|
||||
},
|
||||
"typical": {
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
"@babel/core": "^7.20.12",
|
||||
"@mdx-js/react": "^2.2.1",
|
||||
"@open-wc/testing": "^3.1.7",
|
||||
"@playwright/test": "^1.29.2",
|
||||
"@playwright/test": "^1.30.0",
|
||||
"@storybook/addon-a11y": "^6.5.15",
|
||||
"@storybook/addon-actions": "^6.5.14",
|
||||
"@storybook/addon-essentials": "^6.5.15",
|
||||
@@ -88,7 +88,7 @@
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"@types/mocha": "^10.0.0",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.50.0",
|
||||
"@typescript-eslint/parser": "^5.48.1",
|
||||
"@web/dev-server-esbuild": "^0.3.3",
|
||||
"@web/dev-server-import-maps": "^0.0.7",
|
||||
@@ -114,7 +114,7 @@
|
||||
"rollup": "^3.10.0",
|
||||
"rollup-plugin-esbuild": "^5.0.0",
|
||||
"tiny-glob": "^0.2.9",
|
||||
"typescript": "^4.9.4",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "^4.0.4",
|
||||
"vite-plugin-static-copy": "^0.13.0",
|
||||
"vite-tsconfig-paths": "^4.0.3",
|
||||
|
||||
@@ -9,7 +9,7 @@ import '@umbraco-ui/uui-modal-container';
|
||||
import '@umbraco-ui/uui-modal-dialog';
|
||||
import '@umbraco-ui/uui-modal-sidebar';
|
||||
import 'element-internals-polyfill';
|
||||
import 'router-slot';
|
||||
import '@umbraco-cms/router';
|
||||
|
||||
import type { Guard, IRoute } from 'router-slot/model';
|
||||
|
||||
@@ -17,6 +17,7 @@ import { UUIIconRegistryEssential } from '@umbraco-ui/uui';
|
||||
import { css, html } from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
|
||||
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
import { tryExecuteAndNotify } from '@umbraco-cms/resources';
|
||||
import { OpenAPI, RuntimeLevel, ServerResource } from '@umbraco-cms/backend-api';
|
||||
@@ -151,7 +152,7 @@ export class UmbApp extends UmbLitElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<router-slot id="router-slot" .routes=${this._routes}></router-slot>`;
|
||||
return html`<umb-router-slot id="router-slot" .routes=${this._routes}></umb-router-slot>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,9 +30,10 @@ import { UmbDictionaryDetailStore } from './translation/dictionary/dictionary.de
|
||||
import { UmbDictionaryTreeStore } from './translation/dictionary/dictionary.tree.store';
|
||||
import { UmbDocumentBlueprintDetailStore } from './documents/document-blueprints/document-blueprint.detail.store';
|
||||
import { UmbDocumentBlueprintTreeStore } from './documents/document-blueprints/document-blueprint.tree.store';
|
||||
|
||||
import { UmbDataTypeDetailStore } from './settings/data-types/data-type.detail.store';
|
||||
import { UmbDataTypeTreeStore } from './settings/data-types/data-type.tree.store';
|
||||
import { UmbDataTypeTreeStore } from './settings/data-types/tree/data-type.tree.store';
|
||||
import { UmbTemplateTreeStore } from './templating/templates/tree/data/template.tree.store';
|
||||
import { UmbTemplateDetailStore } from './templating/templates/workspace/data/template.detail.store';
|
||||
import { UmbThemeContext } from './themes/theme.context';
|
||||
import { UmbLanguageStore } from './settings/languages/language.store';
|
||||
import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/notification';
|
||||
@@ -48,6 +49,7 @@ import './translation';
|
||||
import './users';
|
||||
import './packages';
|
||||
import './search';
|
||||
import './templating';
|
||||
import './shared';
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
|
||||
@@ -96,6 +98,8 @@ export class UmbBackofficeElement extends UmbLitElement {
|
||||
new UmbDictionaryTreeStore(this);
|
||||
new UmbDocumentBlueprintDetailStore(this);
|
||||
new UmbDocumentBlueprintTreeStore(this);
|
||||
new UmbTemplateTreeStore(this);
|
||||
new UmbTemplateDetailStore(this);
|
||||
new UmbLanguageStore(this);
|
||||
|
||||
this.provideContext(UMB_BACKOFFICE_CONTEXT_TOKEN, new UmbBackofficeContext());
|
||||
|
||||
@@ -54,7 +54,7 @@ export class UmbCreatedPackagesSectionViewElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<router-slot .routes=${this._routes}></router-slot>`;
|
||||
return html`<umb-router-slot .routes=${this._routes}></umb-router-slot>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ export class UmbInstalledPackagesSectionViewElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<router-slot .routes=${this._routes}></router-slot>`;
|
||||
return html`<umb-router-slot .routes=${this._routes}></umb-router-slot>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { UmbDashboardExamineIndexElement } from './views/section-view-examine-in
|
||||
import { UmbDashboardExamineSearcherElement } from './views/section-view-examine-searchers';
|
||||
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
import { UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/router';
|
||||
|
||||
@customElement('umb-dashboard-examine-management')
|
||||
export class UmbDashboardExamineManagementElement extends UmbLitElement {
|
||||
@@ -46,28 +47,24 @@ export class UmbDashboardExamineManagementElement extends UmbLitElement {
|
||||
];
|
||||
|
||||
@state()
|
||||
private _currentPath?: string;
|
||||
private _routerPath?: string;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
@state()
|
||||
private _activePath = '';
|
||||
|
||||
private _onRouteChange() {
|
||||
this._currentPath = path();
|
||||
}
|
||||
|
||||
private get backbutton(): boolean {
|
||||
return !(this._currentPath?.endsWith('examine-management/'));
|
||||
}
|
||||
|
||||
render() {
|
||||
return html` ${this.backbutton
|
||||
? html` <a href="section/settings/dashboard/examine-management"> ← Back to overview </a> `
|
||||
return html` ${this._routerPath && this._activePath !== ''
|
||||
? html` <a href=${this._routerPath}> ← Back to overview </a> `
|
||||
: nothing}
|
||||
<router-slot @changestate="${this._onRouteChange}" .routes=${this._routes}></router-slot>`;
|
||||
<umb-router-slot
|
||||
.routes=${this._routes}
|
||||
@init=${(event: UmbRouterSlotInitEvent) => {
|
||||
this._routerPath = event.target.absoluteRouterPath;
|
||||
}}
|
||||
@change=${(event: UmbRouterSlotChangeEvent) => {
|
||||
this._activePath = event.target.localActiveViewPath || '';
|
||||
}}></umb-router-slot>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ export class UmbDashboardHealthCheckElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
return html` <router-slot .routes=${this._routes}></router-slot>`;
|
||||
return html` <umb-router-slot .routes=${this._routes}></umb-router-slot>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { UMB_DATA_TYPE_TREE_STORE_CONTEXT_TOKEN } from '../data-type.tree.store';
|
||||
import { UMB_DATA_TYPE_TREE_STORE_CONTEXT_TOKEN } from './data-type.tree.store';
|
||||
import type { ManifestTree, ManifestTreeItemAction } from '@umbraco-cms/models';
|
||||
|
||||
const tree: ManifestTree = {
|
||||
type: 'tree',
|
||||
alias: 'Umb.Tree.DataTypes',
|
||||
name: 'Data Types Tree',
|
||||
weight: 100,
|
||||
meta: {
|
||||
storeAlias: UMB_DATA_TYPE_TREE_STORE_CONTEXT_TOKEN.toString(),
|
||||
},
|
||||
|
||||
@@ -1,33 +1,55 @@
|
||||
import { html } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { isManifestElementNameType , umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
|
||||
import { isManifestElementNameType, umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
|
||||
import type { ManifestBase } from '@umbraco-cms/models';
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/modal';
|
||||
|
||||
@customElement('umb-extension-root-workspace')
|
||||
export class UmbExtensionRootWorkspaceElement extends UmbLitElement {
|
||||
@state()
|
||||
private _extensions?: Array<ManifestBase> = undefined;
|
||||
|
||||
private _modalService?: UmbModalService;
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this._observeExtensions();
|
||||
|
||||
this.consumeContext(UMB_MODAL_SERVICE_CONTEXT_TOKEN, (modalService) => {
|
||||
this._modalService = modalService;
|
||||
});
|
||||
}
|
||||
|
||||
private _observeExtensions() {
|
||||
this.observe(umbExtensionsRegistry.extensions, (extensions) => {
|
||||
this.observe(umbExtensionsRegistry.extensionsSortedByTypeAndWeight(), (extensions) => {
|
||||
this._extensions = extensions || undefined;
|
||||
});
|
||||
}
|
||||
|
||||
#removeExtension(extension: ManifestBase) {
|
||||
const modalHandler = this._modalService?.confirm({
|
||||
headline: 'Unload extension',
|
||||
confirmLabel: 'Unload',
|
||||
content: html`<p>Are you sure you want to unload the extension <strong>${extension.alias}</strong>?</p>`,
|
||||
color: 'danger',
|
||||
});
|
||||
|
||||
modalHandler?.onClose().then(({ confirmed }: any) => {
|
||||
if (confirmed) {
|
||||
umbExtensionsRegistry.unregister(extension.alias);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<umb-workspace-layout headline="Extensions" alias="Umb.Workspace.ExtensionRoot">
|
||||
<uui-box>
|
||||
<p>List of currently loaded extensions</p>
|
||||
<uui-table>
|
||||
<uui-table-head>
|
||||
<uui-table-head-cell>Type</uui-table-head-cell>
|
||||
<uui-table-head-cell>Weight</uui-table-head-cell>
|
||||
<uui-table-head-cell>Name</uui-table-head-cell>
|
||||
<uui-table-head-cell>Alias</uui-table-head-cell>
|
||||
<uui-table-head-cell>Actions</uui-table-head-cell>
|
||||
@@ -37,14 +59,19 @@ export class UmbExtensionRootWorkspaceElement extends UmbLitElement {
|
||||
(extension) => html`
|
||||
<uui-table-row>
|
||||
<uui-table-cell>${extension.type}</uui-table-cell>
|
||||
<uui-table-cell>${extension.weight ? extension.weight : 'Not Set'} </uui-table-cell>
|
||||
<uui-table-cell>
|
||||
${isManifestElementNameType(extension) ? extension.name : 'Custom extension'}
|
||||
${isManifestElementNameType(extension) ? extension.name : `[Custom extension] ${extension.name}`}
|
||||
</uui-table-cell>
|
||||
<uui-table-cell>${extension.alias}</uui-table-cell>
|
||||
<uui-table-cell>
|
||||
<uui-button
|
||||
label="unload"
|
||||
@click=${() => umbExtensionsRegistry.unregister(extension.alias)}></uui-button>
|
||||
label="Unload"
|
||||
color="danger"
|
||||
look="primary"
|
||||
@click=${() => this.#removeExtension(extension)}>
|
||||
<uui-icon name="umb:trash"></uui-icon>
|
||||
</uui-button>
|
||||
</uui-table-cell>
|
||||
</uui-table-row>
|
||||
`
|
||||
|
||||
@@ -103,7 +103,7 @@ export class UmbCollectionElement extends UmbLitElement {
|
||||
return html`
|
||||
<umb-body-layout no-header-background>
|
||||
<umb-collection-toolbar slot="header"></umb-collection-toolbar>
|
||||
<router-slot id="router-slot" .routes="${this._routes}"></router-slot>
|
||||
<umb-router-slot id="router-slot" .routes="${this._routes}"></umb-router-slot>
|
||||
${this._selection && this._selection.length > 0
|
||||
? html`<umb-collection-selection-actions slot="footer"></umb-collection-selection-actions>`
|
||||
: nothing}
|
||||
|
||||
@@ -2,12 +2,12 @@ import { defineElement } from '@umbraco-ui/uui-base/lib/registration';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html } from 'lit';
|
||||
import { state } from 'lit/decorators.js';
|
||||
import { IRoutingInfo } from 'router-slot';
|
||||
import { UmbSectionContext, UMB_SECTION_CONTEXT_TOKEN } from '../section/section.context';
|
||||
import { UmbBackofficeContext, UMB_BACKOFFICE_CONTEXT_TOKEN } from './backoffice.context';
|
||||
import type { ManifestSection } from '@umbraco-cms/models';
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
import { createExtensionElementOrFallback } from '@umbraco-cms/extensions-api';
|
||||
import { UmbRouterSlotChangeEvent } from '@umbraco-cms/router';
|
||||
|
||||
@defineElement('umb-backoffice-main')
|
||||
export class UmbBackofficeMain extends UmbLitElement {
|
||||
@@ -67,9 +67,6 @@ export class UmbBackofficeMain extends UmbLitElement {
|
||||
return {
|
||||
path: this._routePrefix + section.meta.pathname,
|
||||
component: () => createExtensionElementOrFallback(section, 'umb-section'),
|
||||
setup: this._onRouteSetup,
|
||||
// TODO: sometimes we can end up in a state where this callback doesn't get called. It could look like a bug in the router-slot.
|
||||
// Niels: Could this be because _backofficeContext is not available at that state?
|
||||
};
|
||||
});
|
||||
|
||||
@@ -79,8 +76,8 @@ export class UmbBackofficeMain extends UmbLitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _onRouteSetup = (_component: HTMLElement, info: IRoutingInfo) => {
|
||||
const currentPath = info.match.route.path;
|
||||
private _onRouteChange = (event: UmbRouterSlotChangeEvent) => {
|
||||
const currentPath = event.target.localActiveViewPath || ''
|
||||
const section = this._sections.find((s) => this._routePrefix + s.meta.pathname === currentPath);
|
||||
if (!section) return;
|
||||
this._backofficeContext?.setActiveSectionAlias(section.alias);
|
||||
@@ -97,7 +94,11 @@ export class UmbBackofficeMain extends UmbLitElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<umb-router-slot .routes=${this._routes}></umb-router-slot>`;
|
||||
return html`
|
||||
<umb-router-slot
|
||||
.routes=${this._routes}
|
||||
@change=${this._onRouteChange}
|
||||
></umb-router-slot>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
|
||||
@customElement('umb-empty-state')
|
||||
export class UmbEmptyStateElement extends LitElement {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
text-align: center;
|
||||
padding: var(--uui-size-space-4);
|
||||
}
|
||||
|
||||
:host([position='center']) {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
max-width: 400px;
|
||||
width: 80%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
:host(:not([position='center'])) {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
:host(:not([size='small'])) {
|
||||
font-size: var(--uui-size-6);
|
||||
}
|
||||
|
||||
:host([size='small']) {
|
||||
font-size: var(--uui-size-5);
|
||||
}
|
||||
|
||||
slot {
|
||||
margin: auto;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
/**
|
||||
* Set the text size
|
||||
*/
|
||||
@property({ type: String })
|
||||
size: 'small' | 'large' = 'large';
|
||||
|
||||
/**
|
||||
* Set the element position
|
||||
* 'center' => element is absolutely centered
|
||||
* undefined => element has auto margin, to center in parent
|
||||
*/
|
||||
@property({ type: String })
|
||||
position: 'center' | undefined;
|
||||
|
||||
render() {
|
||||
return html`<slot></slot>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-empty-state': UmbEmptyStateElement;
|
||||
}
|
||||
}
|
||||
@@ -16,3 +16,5 @@ import './tree/tree.element';
|
||||
import './workspace/workspace-content/workspace-content.element';
|
||||
import './input-media-picker/input-media-picker.element';
|
||||
import './input-document-picker/input-document-picker.element';
|
||||
import './empty-state/empty-state.element';
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { createExtensionElement, umbExtensionsRegistry } from '@umbraco-cms/exte
|
||||
import type { ManifestDashboard, ManifestDashboardCollection, ManifestWithMeta } from '@umbraco-cms/models';
|
||||
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
import { UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/router';
|
||||
|
||||
@customElement('umb-section-dashboards')
|
||||
export class UmbSectionDashboardsElement extends UmbLitElement {
|
||||
@@ -27,7 +28,8 @@ export class UmbSectionDashboardsElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
#scroll-container {
|
||||
flex: 1;
|
||||
flex:1;
|
||||
position:relative;
|
||||
}
|
||||
|
||||
#router-slot {
|
||||
@@ -41,14 +43,14 @@ export class UmbSectionDashboardsElement extends UmbLitElement {
|
||||
@state()
|
||||
private _dashboards?: Array<ManifestDashboard | ManifestDashboardCollection>;
|
||||
|
||||
@state()
|
||||
private _currentDashboardPathname = '';
|
||||
|
||||
@state()
|
||||
private _routes: Array<any> = [];
|
||||
|
||||
@state()
|
||||
private _currentSectionPathname = '';
|
||||
private _routerPath?: string;
|
||||
|
||||
@state()
|
||||
private _activePath?: string;
|
||||
|
||||
private _currentSectionAlias?: string;
|
||||
private _sectionContext?: UmbSectionContext;
|
||||
@@ -69,9 +71,6 @@ export class UmbSectionDashboardsElement extends UmbLitElement {
|
||||
this._currentSectionAlias = alias;
|
||||
this._observeDashboards();
|
||||
});
|
||||
this.observe(this._sectionContext.pathname.pipe(first()), (pathname) => {
|
||||
this._currentSectionPathname = pathname || '';
|
||||
});
|
||||
}
|
||||
|
||||
private _observeDashboards() {
|
||||
@@ -108,7 +107,6 @@ export class UmbSectionDashboardsElement extends UmbLitElement {
|
||||
return createExtensionElement(dashboard);
|
||||
},
|
||||
setup: (component: Promise<HTMLElement> | HTMLElement, info: IRoutingInfo) => {
|
||||
this._currentDashboardPathname = info.match.route.path;
|
||||
// When its using import, we get an element, when using createExtensionElement we get a Promise.
|
||||
// TODO: this is a bit hacky, can we do it in a more appropriate way:
|
||||
if ((component as any).then) {
|
||||
@@ -135,9 +133,9 @@ export class UmbSectionDashboardsElement extends UmbLitElement {
|
||||
${this._dashboards.map(
|
||||
(dashboard) => html`
|
||||
<uui-tab
|
||||
href="${`section/${this._currentSectionPathname}/dashboard/${dashboard.meta.pathname}`}"
|
||||
href="${this._routerPath}/${dashboard.meta.pathname}"
|
||||
label=${dashboard.meta.label || dashboard.name}
|
||||
?active="${dashboard.meta.pathname === this._currentDashboardPathname}"></uui-tab>
|
||||
?active="${dashboard.meta.pathname === this._activePath}"></uui-tab>
|
||||
`
|
||||
)}
|
||||
</uui-tab-group>
|
||||
@@ -150,7 +148,16 @@ export class UmbSectionDashboardsElement extends UmbLitElement {
|
||||
return html`
|
||||
${this._renderNavigation()}
|
||||
<uui-scroll-container id="scroll-container">
|
||||
<router-slot id="router-slot" .routes="${this._routes}"></router-slot>
|
||||
<umb-router-slot
|
||||
id="router-slot"
|
||||
.routes="${this._routes}"
|
||||
@init=${(event: UmbRouterSlotInitEvent) => {
|
||||
this._routerPath = event.target.absoluteRouterPath;
|
||||
}}
|
||||
@change=${(event: UmbRouterSlotChangeEvent) => {
|
||||
this._activePath = event.target.localActiveViewPath;
|
||||
}}
|
||||
></umb-router-slot>
|
||||
</uui-scroll-container>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { UmbLitElement } from '@umbraco-cms/element';
|
||||
|
||||
import './section-sidebar-menu/section-sidebar-menu.element.ts';
|
||||
import './section-views/section-views.element.ts';
|
||||
import { UmbRouterSlotChangeEvent } from '@umbraco-cms/router';
|
||||
|
||||
@customElement('umb-section')
|
||||
export class UmbSectionElement extends UmbLitElement {
|
||||
@@ -173,9 +174,6 @@ export class UmbSectionElement extends UmbLitElement {
|
||||
return {
|
||||
path: 'view/' + view.meta.pathname,
|
||||
component: () => createExtensionElement(view),
|
||||
setup: () => {
|
||||
this._sectionContext?.setActiveView(view);
|
||||
},
|
||||
};
|
||||
}) ?? [];
|
||||
|
||||
@@ -187,6 +185,13 @@ export class UmbSectionElement extends UmbLitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _onRouteChange = (event: UmbRouterSlotChangeEvent) => {
|
||||
const currentPath = event.target.localActiveViewPath;
|
||||
const view = this._views?.find((view) => 'view/' + view.meta.pathname === currentPath);
|
||||
if (!view) return;
|
||||
this._sectionContext?.setActiveView(view);
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
${this._menuItems && this._menuItems.length > 0
|
||||
@@ -199,7 +204,7 @@ export class UmbSectionElement extends UmbLitElement {
|
||||
<umb-section-main>
|
||||
${this._views && this._views.length > 0 ? html`<umb-section-views></umb-section-views>` : nothing}
|
||||
${this._routes && this._routes.length > 0
|
||||
? html`<router-slot id="router-slot" .routes="${this._routes}"></router-slot>`
|
||||
? html`<umb-router-slot id="router-slot" .routes="${this._routes}" @change=${this._onRouteChange}></umb-router-slot>`
|
||||
: nothing}
|
||||
<slot></slot>
|
||||
</umb-section-main>
|
||||
|
||||
@@ -167,8 +167,21 @@ export class UmbTreeItem extends UmbLitElement {
|
||||
private _onShowChildren() {
|
||||
if (this._childItems && this._childItems.length > 0) return;
|
||||
this._observeChildren();
|
||||
this._observeRepositoryChildren();
|
||||
}
|
||||
|
||||
private async _observeRepositoryChildren() {
|
||||
if (!this._treeContext?.requestChildrenOf) return;
|
||||
|
||||
// TODO: add loading state
|
||||
this._treeContext.requestChildrenOf(this.key);
|
||||
|
||||
this.observe(await this._treeContext.childrenOf(this.key), (childItems) => {
|
||||
this._childItems = childItems as Entity[];
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: remove when repositories are in place
|
||||
private _observeChildren() {
|
||||
if (!this._store?.getTreeItemChildren) return;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { ManifestTree } from '@umbraco-cms/models';
|
||||
import type { ManifestTree, UmbTreeRepository } from '@umbraco-cms/models';
|
||||
import { DeepState } from '@umbraco-cms/observable-api';
|
||||
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
|
||||
|
||||
export interface UmbTreeContext {
|
||||
tree: ManifestTree;
|
||||
@@ -12,6 +13,7 @@ export interface UmbTreeContext {
|
||||
}
|
||||
|
||||
export class UmbTreeContextBase implements UmbTreeContext {
|
||||
#host: UmbControllerHostInterface;
|
||||
public tree: ManifestTree;
|
||||
|
||||
#selectable = new DeepState(false);
|
||||
@@ -20,8 +22,15 @@ export class UmbTreeContextBase implements UmbTreeContext {
|
||||
#selection = new DeepState(<Array<string>>[]);
|
||||
public readonly selection = this.#selection.asObservable();
|
||||
|
||||
constructor(tree: ManifestTree) {
|
||||
repository!: UmbTreeRepository;
|
||||
|
||||
constructor(host: UmbControllerHostInterface, tree: ManifestTree) {
|
||||
this.#host = host;
|
||||
this.tree = tree;
|
||||
|
||||
if (this.tree.meta.repository) {
|
||||
this.repository = new this.tree.meta.repository(this.#host);
|
||||
}
|
||||
}
|
||||
|
||||
public setSelectable(value: boolean) {
|
||||
@@ -35,7 +44,7 @@ export class UmbTreeContextBase implements UmbTreeContext {
|
||||
|
||||
public select(key: string) {
|
||||
const oldSelection = this.#selection.getValue();
|
||||
if(oldSelection.indexOf(key) !== -1) return;
|
||||
if (oldSelection.indexOf(key) !== -1) return;
|
||||
|
||||
const selection = [...oldSelection, key];
|
||||
this.#selection.next(selection);
|
||||
@@ -45,4 +54,20 @@ export class UmbTreeContextBase implements UmbTreeContext {
|
||||
const selection = this.#selection.getValue();
|
||||
this.#selection.next(selection.filter((x) => x !== key));
|
||||
}
|
||||
|
||||
public async requestRootItems() {
|
||||
return this.repository.requestRootItems();
|
||||
}
|
||||
|
||||
public async requestChildrenOf(parentKey: string | null) {
|
||||
return this.repository.requestChildrenOf(parentKey);
|
||||
}
|
||||
|
||||
public async rootItems() {
|
||||
return this.repository.rootItems();
|
||||
}
|
||||
|
||||
public async childrenOf(parentKey: string | null) {
|
||||
return this.repository.childrenOf(parentKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,8 +64,9 @@ export class UmbTreeElement extends UmbLitElement {
|
||||
private _treeContext?: UmbTreeContextBase;
|
||||
private _store?: UmbTreeStore<Entity>;
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
#treeRepository?: any; // TODO: make interface
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this._observeTree();
|
||||
}
|
||||
|
||||
@@ -76,12 +77,15 @@ export class UmbTreeElement extends UmbLitElement {
|
||||
umbExtensionsRegistry
|
||||
.extensionsOfType('tree')
|
||||
.pipe(map((trees) => trees.find((tree) => tree.alias === this.alias))),
|
||||
(tree) => {
|
||||
async (tree) => {
|
||||
if (this._tree?.alias === tree?.alias) return;
|
||||
|
||||
this._tree = tree;
|
||||
if (tree) {
|
||||
this._provideTreeContext();
|
||||
this._provideTreeContext();
|
||||
|
||||
// TODO: remove this when repositories are in place.
|
||||
if (this._tree?.meta.storeAlias) {
|
||||
this._provideStore();
|
||||
this._observeTreeRoot();
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -92,15 +96,17 @@ export class UmbTreeElement extends UmbLitElement {
|
||||
|
||||
// TODO: if a new tree comes around, which is different, then we should clean up and re provide.
|
||||
|
||||
this._treeContext = new UmbTreeContextBase(this._tree);
|
||||
this._treeContext = new UmbTreeContextBase(this, this._tree);
|
||||
this._treeContext.setSelectable(this.selectable);
|
||||
this._treeContext.setSelection(this.selection);
|
||||
|
||||
this._observeSelection();
|
||||
this._observeRepositoryTreeRoot();
|
||||
|
||||
this.provideContext('umbTreeContext', this._treeContext);
|
||||
}
|
||||
|
||||
// TODO: remove this when repositories are in place.
|
||||
private _provideStore() {
|
||||
// TODO: Clean up store, if already existing.
|
||||
|
||||
@@ -109,6 +115,17 @@ export class UmbTreeElement extends UmbLitElement {
|
||||
this.consumeContext(this._tree.meta.storeAlias, (store: UmbTreeStore<Entity>) => {
|
||||
this._store = store;
|
||||
this.provideContext('umbStore', store);
|
||||
this._observeStoreTreeRoot();
|
||||
});
|
||||
}
|
||||
|
||||
private async _observeRepositoryTreeRoot() {
|
||||
if (!this._treeContext?.requestRootItems) return;
|
||||
|
||||
this._treeContext.requestRootItems();
|
||||
|
||||
this.observe(await this._treeContext.rootItems(), (rootItems) => {
|
||||
this._items = rootItems as Entity[];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -122,7 +139,7 @@ export class UmbTreeElement extends UmbLitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _observeTreeRoot() {
|
||||
private _observeStoreTreeRoot() {
|
||||
if (!this._store?.getTreeRoot) return;
|
||||
|
||||
this._loading = true;
|
||||
|
||||
12
src/Umbraco.Web.UI.Client/src/backoffice/templating/index.ts
Normal file
12
src/Umbraco.Web.UI.Client/src/backoffice/templating/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { manifests as templateManifests } from './templates/manifests';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
|
||||
import { ManifestTypes } from '@umbraco-cms/extensions-registry';
|
||||
|
||||
const registerExtensions = (manifests: Array<ManifestTypes>) => {
|
||||
manifests.forEach((manifest) => {
|
||||
if (umbExtensionsRegistry.isRegistered(manifest.alias)) return;
|
||||
umbExtensionsRegistry.register(manifest);
|
||||
});
|
||||
};
|
||||
|
||||
registerExtensions([...templateManifests]);
|
||||
@@ -0,0 +1,5 @@
|
||||
import { manifests as sidebarMenuItemManifests } from './sidebar-menu-item/manifests';
|
||||
import { manifests as treeManifests } from './tree/manifests';
|
||||
import { manifests as workspaceManifests } from './workspace/manifests';
|
||||
|
||||
export const manifests = [...sidebarMenuItemManifests, ...treeManifests, ...workspaceManifests];
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { ManifestSidebarMenuItem } from '@umbraco-cms/models';
|
||||
|
||||
const sidebarMenuItem: ManifestSidebarMenuItem = {
|
||||
type: 'sidebarMenuItem',
|
||||
alias: 'Umb.SidebarMenuItem.Templates',
|
||||
name: 'Templates Sidebar Menu Item',
|
||||
weight: 40,
|
||||
loader: () => import('./templates-sidebar-menu-item.element'),
|
||||
meta: {
|
||||
label: 'Templates',
|
||||
icon: 'umb:folder',
|
||||
sections: ['Umb.Section.Settings'],
|
||||
entityType: 'template',
|
||||
},
|
||||
};
|
||||
|
||||
export const manifests = [sidebarMenuItem];
|
||||
@@ -0,0 +1,40 @@
|
||||
import { html, nothing } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
|
||||
@customElement('umb-templates-sidebar-menu-item')
|
||||
export class UmbTemplatesSidebarMenuItemElement extends UmbLitElement {
|
||||
@state()
|
||||
private _renderTree = false;
|
||||
|
||||
private _onShowChildren() {
|
||||
this._renderTree = true;
|
||||
}
|
||||
|
||||
private _onHideChildren() {
|
||||
this._renderTree = false;
|
||||
}
|
||||
|
||||
// TODO: check if root has children before settings the has-children attribute
|
||||
// TODO: how do we want to cache the tree? (do we want to rerender every time the user opens the tree)?
|
||||
// TODO: can we make this reusable?
|
||||
render() {
|
||||
return html`<umb-tree-item
|
||||
label="Templates"
|
||||
icon="umb:folder"
|
||||
entity-type="template"
|
||||
@show-children=${this._onShowChildren}
|
||||
@hide-children=${this._onHideChildren}
|
||||
has-children>
|
||||
${this._renderTree ? html`<umb-tree alias="Umb.Tree.Templates"></umb-tree>` : nothing}
|
||||
</umb-tree-item> `;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbTemplatesSidebarMenuItemElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-templates-sidebar-menu-item': UmbTemplatesSidebarMenuItemElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css';
|
||||
import { css, html } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import UmbTreeItemActionElement from '../../../../../shared/components/tree/action/tree-item-action.element';
|
||||
|
||||
@customElement('umb-create-template-tree-action')
|
||||
export default class UmbCreateTemplateTreeAction extends UmbTreeItemActionElement {
|
||||
static styles = [UUITextStyles, css``];
|
||||
|
||||
// TODO: how do we handle the href?
|
||||
private _constructUrl() {
|
||||
return `section/settings/${this._activeTreeItem?.type}/create/${this._activeTreeItem?.key || 'root'}`;
|
||||
}
|
||||
|
||||
private _handleLabelClick() {
|
||||
if (!this._treeContextMenuService) return;
|
||||
this._treeContextMenuService.close();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<uui-menu-item
|
||||
label=${this.treeAction?.meta.label ?? ''}
|
||||
@click-label="${this._handleLabelClick}"
|
||||
href="${this._constructUrl()}">
|
||||
<uui-icon slot="icon" name=${this.treeAction?.meta.icon ?? ''}></uui-icon>
|
||||
</uui-menu-item>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-create-template-tree-action': UmbCreateTemplateTreeAction;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css';
|
||||
import { css, html } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import UmbTreeItemActionElement from '../../../../../shared/components/tree/action/tree-item-action.element';
|
||||
import { UmbTemplateDetailRepository } from '../../../workspace/data/template.detail.repository';
|
||||
|
||||
@customElement('umb-delete-template-tree-action')
|
||||
export default class UmbDeleteTemplateTreeAction extends UmbTreeItemActionElement {
|
||||
static styles = [UUITextStyles, css``];
|
||||
#templateDetailRepo = new UmbTemplateDetailRepository(this);
|
||||
|
||||
private _handleLabelClick() {
|
||||
if (!this._activeTreeItem?.key) return;
|
||||
if (!this._treeContextMenuService) return;
|
||||
|
||||
this.#templateDetailRepo.delete(this._activeTreeItem.key);
|
||||
this._treeContextMenuService.close();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<uui-menu-item label=${this.treeAction?.meta.label ?? ''} @click-label="${this._handleLabelClick}">
|
||||
<uui-icon slot="icon" name=${this.treeAction?.meta.icon ?? ''}></uui-icon>
|
||||
</uui-menu-item>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-delete-template-tree-action': UmbDeleteTemplateTreeAction;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { DataSourceResponse } from '@umbraco-cms/models';
|
||||
import { EntityTreeItem, PagedEntityTreeItem } from '@umbraco-cms/backend-api';
|
||||
|
||||
export interface TemplateTreeDataSource {
|
||||
getRootItems(): Promise<DataSourceResponse<PagedEntityTreeItem>>;
|
||||
getChildrenOf(parentKey: string): Promise<DataSourceResponse<PagedEntityTreeItem>>;
|
||||
getItems(key: Array<string>): Promise<DataSourceResponse<EntityTreeItem[]>>;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { TemplateTreeDataSource } from '.';
|
||||
import { ProblemDetails, TemplateResource } from '@umbraco-cms/backend-api';
|
||||
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
|
||||
import { tryExecuteAndNotify } from '@umbraco-cms/resources';
|
||||
|
||||
/**
|
||||
* A data source for the Template tree that fetches data from the server
|
||||
* @export
|
||||
* @class TemplateTreeServerDataSource
|
||||
* @implements {TemplateTreeDataSource}
|
||||
*/
|
||||
export class TemplateTreeServerDataSource implements TemplateTreeDataSource {
|
||||
#host: UmbControllerHostInterface;
|
||||
|
||||
/**
|
||||
* Creates an instance of TemplateTreeServerDataSource.
|
||||
* @param {UmbControllerHostInterface} host
|
||||
* @memberof TemplateTreeServerDataSource
|
||||
*/
|
||||
constructor(host: UmbControllerHostInterface) {
|
||||
this.#host = host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the root items for the tree from the server
|
||||
* @return {*}
|
||||
* @memberof TemplateTreeServerDataSource
|
||||
*/
|
||||
async getRootItems() {
|
||||
return tryExecuteAndNotify(this.#host, TemplateResource.getTreeTemplateRoot({}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the children of a given parent key from the server
|
||||
* @param {(string | null)} parentKey
|
||||
* @return {*}
|
||||
* @memberof TemplateTreeServerDataSource
|
||||
*/
|
||||
async getChildrenOf(parentKey: string | null) {
|
||||
if (!parentKey) {
|
||||
const error: ProblemDetails = { title: 'Parent key is missing' };
|
||||
return { error };
|
||||
}
|
||||
|
||||
return tryExecuteAndNotify(
|
||||
this.#host,
|
||||
TemplateResource.getTreeTemplateChildren({
|
||||
parentKey,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the items for the given keys from the server
|
||||
* @param {Array<string>} keys
|
||||
* @return {*}
|
||||
* @memberof TemplateTreeServerDataSource
|
||||
*/
|
||||
async getItems(keys: Array<string>) {
|
||||
if (keys) {
|
||||
const error: ProblemDetails = { title: 'Keys are missing' };
|
||||
return { error };
|
||||
}
|
||||
|
||||
return tryExecuteAndNotify(
|
||||
this.#host,
|
||||
TemplateResource.getTreeTemplateItem({
|
||||
key: keys,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import { TemplateTreeServerDataSource } from './sources/template.tree.server.data';
|
||||
import { UmbTemplateTreeStore, UMB_TEMPLATE_TREE_STORE_CONTEXT_TOKEN } from './template.tree.store';
|
||||
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
|
||||
import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/notification';
|
||||
import { UmbContextConsumerController } from '@umbraco-cms/context-api';
|
||||
import { ProblemDetails } from '@umbraco-cms/backend-api';
|
||||
import type { UmbTreeRepository } from '@umbraco-cms/models';
|
||||
|
||||
// Move to documentation / JSdoc
|
||||
/* We need to create a new instance of the repository from within the element context. We want the notifications to be displayed in the right context. */
|
||||
// element -> context -> repository -> (store) -> data source
|
||||
// All methods should be async and return a promise. Some methods might return an observable as part of the promise response.
|
||||
export class UmbTemplateTreeRepository implements UmbTreeRepository {
|
||||
#host: UmbControllerHostInterface;
|
||||
#dataSource: TemplateTreeServerDataSource;
|
||||
#treeStore?: UmbTemplateTreeStore;
|
||||
#notificationService?: UmbNotificationService;
|
||||
#initResolver?: () => void;
|
||||
#initialized = false;
|
||||
|
||||
constructor(host: UmbControllerHostInterface) {
|
||||
this.#host = host;
|
||||
// TODO: figure out how spin up get the correct data source
|
||||
this.#dataSource = new TemplateTreeServerDataSource(this.#host);
|
||||
|
||||
new UmbContextConsumerController(this.#host, UMB_TEMPLATE_TREE_STORE_CONTEXT_TOKEN, (instance) => {
|
||||
this.#treeStore = instance;
|
||||
this.#checkIfInitialized();
|
||||
});
|
||||
|
||||
new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN, (instance) => {
|
||||
this.#notificationService = instance;
|
||||
this.#checkIfInitialized();
|
||||
});
|
||||
}
|
||||
|
||||
#init = new Promise<void>((resolve) => {
|
||||
this.#initialized ? resolve() : (this.#initResolver = resolve);
|
||||
});
|
||||
|
||||
#checkIfInitialized() {
|
||||
if (this.#treeStore && this.#notificationService) {
|
||||
this.#initialized = true;
|
||||
this.#initResolver?.();
|
||||
}
|
||||
}
|
||||
|
||||
async requestRootItems() {
|
||||
await this.#init;
|
||||
|
||||
const { data, error } = await this.#dataSource.getRootItems();
|
||||
|
||||
if (data) {
|
||||
this.#treeStore?.appendItems(data.items);
|
||||
}
|
||||
|
||||
return { data, error };
|
||||
}
|
||||
|
||||
async requestChildrenOf(parentKey: string | null) {
|
||||
await this.#init;
|
||||
|
||||
if (!parentKey) {
|
||||
const error: ProblemDetails = { title: 'Parent key is missing' };
|
||||
return { data: undefined, error };
|
||||
}
|
||||
|
||||
const { data, error } = await this.#dataSource.getChildrenOf(parentKey);
|
||||
|
||||
if (data) {
|
||||
this.#treeStore?.appendItems(data.items);
|
||||
}
|
||||
|
||||
return { data, error };
|
||||
}
|
||||
|
||||
async requestItems(keys: Array<string>) {
|
||||
await this.#init;
|
||||
|
||||
if (!keys) {
|
||||
const error: ProblemDetails = { title: 'Keys are missing' };
|
||||
return { data: undefined, error };
|
||||
}
|
||||
|
||||
const { data, error } = await this.#dataSource.getItems(keys);
|
||||
|
||||
return { data, error };
|
||||
}
|
||||
|
||||
async rootItems() {
|
||||
await this.#init;
|
||||
return this.#treeStore!.rootItems();
|
||||
}
|
||||
|
||||
async childrenOf(parentKey: string | null) {
|
||||
await this.#init;
|
||||
return this.#treeStore!.childrenOf(parentKey);
|
||||
}
|
||||
|
||||
async items(keys: Array<string>) {
|
||||
await this.#init;
|
||||
return this.#treeStore!.items(keys);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { EntityTreeItem } from '@umbraco-cms/backend-api';
|
||||
import { UmbContextToken } from '@umbraco-cms/context-api';
|
||||
import { ArrayState } from '@umbraco-cms/observable-api';
|
||||
import { UmbStoreBase } from '@umbraco-cms/store';
|
||||
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
|
||||
|
||||
/**
|
||||
* @export
|
||||
* @class UmbTemplateTreeStore
|
||||
* @extends {UmbStoreBase}
|
||||
* @description - Tree Data Store for Templates
|
||||
*/
|
||||
export class UmbTemplateTreeStore extends UmbStoreBase {
|
||||
#data = new ArrayState<EntityTreeItem>([], (x) => x.key);
|
||||
|
||||
/**
|
||||
* Creates an instance of UmbTemplateTreeStore.
|
||||
* @param {UmbControllerHostInterface} host
|
||||
* @memberof UmbTemplateTreeStore
|
||||
*/
|
||||
constructor(host: UmbControllerHostInterface) {
|
||||
super(host, UMB_TEMPLATE_TREE_STORE_CONTEXT_TOKEN.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends items to the store
|
||||
* @param {Array<EntityTreeItem>} items
|
||||
* @memberof UmbTemplateTreeStore
|
||||
*/
|
||||
appendItems(items: Array<EntityTreeItem>) {
|
||||
this.#data.append(items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an item in the store
|
||||
* @param {string} key
|
||||
* @param {Partial<EntityTreeItem>} data
|
||||
* @memberof UmbTemplateTreeStore
|
||||
*/
|
||||
updateItem(key: string, data: Partial<EntityTreeItem>) {
|
||||
const entries = this.#data.getValue();
|
||||
const entry = entries.find((entry) => entry.key === key);
|
||||
|
||||
if (entry) {
|
||||
this.#data.appendOne({ ...entry, ...data });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item from the store
|
||||
* @param {string} key
|
||||
* @memberof UmbTemplateTreeStore
|
||||
*/
|
||||
removeItem(key: string) {
|
||||
const entries = this.#data.getValue();
|
||||
const entry = entries.find((entry) => entry.key === key);
|
||||
|
||||
if (entry) {
|
||||
this.#data.remove([key]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable to observe the root items
|
||||
* @return {*}
|
||||
* @memberof UmbTemplateTreeStore
|
||||
*/
|
||||
rootItems() {
|
||||
return this.#data.getObservablePart((items) => items.filter((item) => item.parentKey === null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable to observe the children of a given parent
|
||||
* @param {(string | null)} parentKey
|
||||
* @return {*}
|
||||
* @memberof UmbTemplateTreeStore
|
||||
*/
|
||||
childrenOf(parentKey: string | null) {
|
||||
return this.#data.getObservablePart((items) => items.filter((item) => item.parentKey === parentKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable to observe the items with the given keys
|
||||
* @param {Array<string>} keys
|
||||
* @return {*}
|
||||
* @memberof UmbTemplateTreeStore
|
||||
*/
|
||||
items(keys: Array<string>) {
|
||||
return this.#data.getObservablePart((items) => items.filter((item) => keys.includes(item.key ?? '')));
|
||||
}
|
||||
}
|
||||
|
||||
export const UMB_TEMPLATE_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbTemplateTreeStore>(
|
||||
UmbTemplateTreeStore.name
|
||||
);
|
||||
@@ -0,0 +1,40 @@
|
||||
import { UmbTemplateTreeRepository } from './data/template.tree.repository';
|
||||
import type { ManifestTree, ManifestTreeItemAction } from '@umbraco-cms/models';
|
||||
|
||||
const tree: ManifestTree = {
|
||||
type: 'tree',
|
||||
alias: 'Umb.Tree.Templates',
|
||||
name: 'Templates Tree',
|
||||
meta: {
|
||||
repository: UmbTemplateTreeRepository,
|
||||
},
|
||||
};
|
||||
|
||||
const treeItemActions: Array<ManifestTreeItemAction> = [
|
||||
{
|
||||
type: 'treeItemAction',
|
||||
alias: 'Umb.TreeItemAction.Template.Create',
|
||||
name: 'Create Template Tree Action',
|
||||
loader: () => import('./actions/create/create-template-tree-action.element'),
|
||||
weight: 300,
|
||||
meta: {
|
||||
entityType: 'template',
|
||||
label: 'Create',
|
||||
icon: 'umb:add',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'treeItemAction',
|
||||
alias: 'Umb.TreeItemAction.Template.Delete',
|
||||
name: 'Delete Template Tree Action',
|
||||
loader: () => import('./actions/delete/delete-template-tree-action.element'),
|
||||
weight: 200,
|
||||
meta: {
|
||||
entityType: 'template',
|
||||
label: 'Delete',
|
||||
icon: 'umb:trash',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const manifests = [tree, ...treeItemActions];
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Template } from '@umbraco-cms/backend-api';
|
||||
import type { DataSourceResponse } from '@umbraco-cms/models';
|
||||
|
||||
export interface TemplateDetailDataSource {
|
||||
createScaffold(parentKey: string | null): Promise<DataSourceResponse<Template>>;
|
||||
get(key: string): Promise<DataSourceResponse<Template>>;
|
||||
insert(template: Template): Promise<DataSourceResponse>;
|
||||
update(template: Template): Promise<DataSourceResponse>;
|
||||
delete(key: string): Promise<DataSourceResponse>;
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { TemplateDetailDataSource } from '.';
|
||||
import { ProblemDetails, Template, TemplateResource } from '@umbraco-cms/backend-api';
|
||||
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
|
||||
import { tryExecuteAndNotify } from '@umbraco-cms/resources';
|
||||
|
||||
/**
|
||||
* A data source for the Template detail that fetches data from the server
|
||||
* @export
|
||||
* @class UmbTemplateDetailServerDataSource
|
||||
* @implements {TemplateDetailDataSource}
|
||||
*/
|
||||
export class UmbTemplateDetailServerDataSource implements TemplateDetailDataSource {
|
||||
#host: UmbControllerHostInterface;
|
||||
|
||||
/**
|
||||
* Creates an instance of UmbTemplateDetailServerDataSource.
|
||||
* @param {UmbControllerHostInterface} host
|
||||
* @memberof UmbTemplateDetailServerDataSource
|
||||
*/
|
||||
constructor(host: UmbControllerHostInterface) {
|
||||
this.#host = host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a Template with the given key from the server
|
||||
* @param {string} key
|
||||
* @return {*}
|
||||
* @memberof UmbTemplateDetailServerDataSource
|
||||
*/
|
||||
get(key: string) {
|
||||
return tryExecuteAndNotify(this.#host, TemplateResource.getTemplateByKey({ key }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Template scaffold
|
||||
* @param {(string | null)} parentKey
|
||||
* @return {*}
|
||||
* @memberof UmbTemplateDetailServerDataSource
|
||||
*/
|
||||
async createScaffold(parentKey: string | null) {
|
||||
let masterTemplateAlias: string | undefined = undefined;
|
||||
let error = undefined;
|
||||
const data: Template = {
|
||||
key: uuid(),
|
||||
name: '',
|
||||
alias: '',
|
||||
content: '',
|
||||
};
|
||||
|
||||
// TODO: update when backend is updated so we don't have to do two calls
|
||||
if (parentKey) {
|
||||
const { data: parentData, error: parentError } = await tryExecuteAndNotify(
|
||||
this.#host,
|
||||
TemplateResource.getTemplateByKey({ key: parentKey })
|
||||
);
|
||||
masterTemplateAlias = parentData?.alias;
|
||||
error = parentError;
|
||||
}
|
||||
|
||||
const { data: scaffoldData, error: scaffoldError } = await tryExecuteAndNotify(
|
||||
this.#host,
|
||||
TemplateResource.getTemplateScaffold({ masterTemplateAlias })
|
||||
);
|
||||
|
||||
error = scaffoldError;
|
||||
data.content = scaffoldData?.content || '';
|
||||
|
||||
return { data, error };
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new Template on the server
|
||||
* @param {Template} template
|
||||
* @return {*}
|
||||
* @memberof UmbTemplateDetailServerDataSource
|
||||
*/
|
||||
async insert(template: Template) {
|
||||
const payload = { requestBody: template };
|
||||
return tryExecuteAndNotify(this.#host, TemplateResource.postTemplate(payload));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a Template on the server
|
||||
* @param {Template} template
|
||||
* @return {*}
|
||||
* @memberof UmbTemplateDetailServerDataSource
|
||||
*/
|
||||
async update(template: Template) {
|
||||
if (!template.key) {
|
||||
const error: ProblemDetails = { title: 'Template key is missing' };
|
||||
return { error };
|
||||
}
|
||||
|
||||
const payload = { key: template.key, requestBody: template };
|
||||
return tryExecuteAndNotify(this.#host, TemplateResource.putTemplateByKey(payload));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a Template on the server
|
||||
* @param {string} key
|
||||
* @return {*}
|
||||
* @memberof UmbTemplateDetailServerDataSource
|
||||
*/
|
||||
async delete(key: string) {
|
||||
if (!key) {
|
||||
const error: ProblemDetails = { title: 'Key is missing' };
|
||||
return { error };
|
||||
}
|
||||
|
||||
return await tryExecuteAndNotify(this.#host, TemplateResource.deleteTemplateByKey({ key }));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
import { UmbTemplateTreeStore, UMB_TEMPLATE_TREE_STORE_CONTEXT_TOKEN } from '../../tree/data/template.tree.store';
|
||||
import { UmbTemplateDetailStore, UMB_TEMPLATE_DETAIL_STORE_CONTEXT_TOKEN } from './template.detail.store';
|
||||
import { UmbTemplateDetailServerDataSource } from './sources/template.detail.server.data';
|
||||
import { ProblemDetails, Template } from '@umbraco-cms/backend-api';
|
||||
import { UmbContextConsumerController } from '@umbraco-cms/context-api';
|
||||
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
|
||||
import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/notification';
|
||||
|
||||
// Move to documentation / JSdoc
|
||||
/* We need to create a new instance of the repository from within the element context. We want the notifications to be displayed in the right context. */
|
||||
// element -> context -> repository -> (store) -> data source
|
||||
// All methods should be async and return a promise. Some methods might return an observable as part of the promise response.
|
||||
export class UmbTemplateDetailRepository {
|
||||
|
||||
#host: UmbControllerHostInterface;
|
||||
#dataSource: UmbTemplateDetailServerDataSource;
|
||||
#detailStore?: UmbTemplateDetailStore;
|
||||
#treeStore?: UmbTemplateTreeStore;
|
||||
#notificationService?: UmbNotificationService;
|
||||
#initResolver?: () => void;
|
||||
#initialized = false;
|
||||
|
||||
constructor(host: UmbControllerHostInterface) {
|
||||
this.#host = host;
|
||||
|
||||
// TODO: figure out how spin up get the correct data source
|
||||
this.#dataSource = new UmbTemplateDetailServerDataSource(this.#host);
|
||||
|
||||
// TODO: should we allow promises so each method can request the context when it needs it instead of initializing it upfront?
|
||||
new UmbContextConsumerController(this.#host, UMB_TEMPLATE_DETAIL_STORE_CONTEXT_TOKEN, (instance) => {
|
||||
this.#detailStore = instance;
|
||||
this.#checkIfInitialized();
|
||||
});
|
||||
|
||||
new UmbContextConsumerController(this.#host, UMB_TEMPLATE_TREE_STORE_CONTEXT_TOKEN, (instance) => {
|
||||
this.#treeStore = instance;
|
||||
this.#checkIfInitialized();
|
||||
});
|
||||
|
||||
new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN, (instance) => {
|
||||
this.#notificationService = instance;
|
||||
this.#checkIfInitialized();
|
||||
});
|
||||
}
|
||||
|
||||
#init() {
|
||||
// TODO: This would only works with one user of this method. If two, the first one would be forgotten, but maybe its alright for now as I guess this is temporary.
|
||||
return new Promise<void>((resolve) => {
|
||||
this.#initialized ? resolve() : (this.#initResolver = resolve);
|
||||
});
|
||||
}
|
||||
|
||||
#checkIfInitialized() {
|
||||
if (this.#detailStore && this.#detailStore && this.#notificationService) {
|
||||
this.#initialized = true;
|
||||
this.#initResolver?.();
|
||||
}
|
||||
}
|
||||
|
||||
async createScaffold(parentKey: string | null) {
|
||||
await this.#init();
|
||||
|
||||
// TODO: should we show a notification if the parent key is missing?
|
||||
// Investigate what is best for Acceptance testing, cause in that perspective a thrown error might be the best choice?
|
||||
if (!parentKey) {
|
||||
const error: ProblemDetails = { title: 'Parent key is missing' };
|
||||
return { data: undefined, error };
|
||||
}
|
||||
|
||||
return this.#dataSource.createScaffold(parentKey);
|
||||
}
|
||||
|
||||
async get(key: string) {
|
||||
await this.#init();
|
||||
|
||||
// TODO: should we show a notification if the key is missing?
|
||||
// Investigate what is best for Acceptance testing, cause in that perspective a thrown error might be the best choice?
|
||||
if (!key) {
|
||||
const error: ProblemDetails = { title: 'Key is missing' };
|
||||
return { error };
|
||||
}
|
||||
|
||||
return this.#dataSource.get(key);
|
||||
}
|
||||
|
||||
async insert(template: Template) {
|
||||
await this.#init();
|
||||
|
||||
// TODO: should we show a notification if the template is missing?
|
||||
// Investigate what is best for Acceptance testing, cause in that perspective a thrown error might be the best choice?
|
||||
if (!template) {
|
||||
const error: ProblemDetails = { title: 'Template is missing' };
|
||||
return { error };
|
||||
}
|
||||
|
||||
const { error } = await this.#dataSource.insert(template);
|
||||
|
||||
if (!error) {
|
||||
const notification = { data: { message: `Template created` } };
|
||||
this.#notificationService?.peek('positive', notification);
|
||||
}
|
||||
|
||||
// TODO: we currently don't use the detail store for anything.
|
||||
// Consider to look up the data before fetching from the server
|
||||
this.#detailStore?.append(template);
|
||||
// TODO: Update tree store with the new item?
|
||||
|
||||
return { error };
|
||||
}
|
||||
|
||||
async update(template: Template) {
|
||||
await this.#init();
|
||||
|
||||
// TODO: should we show a notification if the template is missing?
|
||||
// Investigate what is best for Acceptance testing, cause in that perspective a thrown error might be the best choice?
|
||||
if (!template || !template.key) {
|
||||
const error: ProblemDetails = { title: 'Template is missing' };
|
||||
return { error };
|
||||
}
|
||||
|
||||
const { error } = await this.#dataSource.update(template);
|
||||
|
||||
if (!error) {
|
||||
const notification = { data: { message: `Template saved` } };
|
||||
this.#notificationService?.peek('positive', notification);
|
||||
}
|
||||
|
||||
// TODO: we currently don't use the detail store for anything.
|
||||
// Consider to look up the data before fetching from the server
|
||||
// Consider notify a workspace if a template is updated in the store while someone is editing it.
|
||||
this.#detailStore?.append(template);
|
||||
this.#treeStore?.updateItem(template.key, { name: template.name });
|
||||
// TODO: would be nice to align the stores on methods/methodNames.
|
||||
|
||||
return { error };
|
||||
}
|
||||
|
||||
async delete(key: string) {
|
||||
await this.#init();
|
||||
|
||||
// TODO: should we show a notification if the key is missing?
|
||||
if (!key) {
|
||||
const error: ProblemDetails = { title: 'Key is missing' };
|
||||
return { error };
|
||||
}
|
||||
|
||||
const { error } = await this.#dataSource.delete(key);
|
||||
|
||||
if (!error) {
|
||||
const notification = { data: { message: `Template deleted` } };
|
||||
this.#notificationService?.peek('positive', notification);
|
||||
}
|
||||
|
||||
// TODO: we currently don't use the detail store for anything.
|
||||
// Consider to look up the data before fetching from the server.
|
||||
// Consider notify a workspace if a template is deleted from the store while someone is editing it.
|
||||
this.#detailStore?.remove([key]);
|
||||
this.#treeStore?.removeItem(key);
|
||||
// TODO: would be nice to align the stores on methods/methodNames.
|
||||
|
||||
return { error };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { UmbContextToken } from '@umbraco-cms/context-api';
|
||||
import { ArrayState } from '@umbraco-cms/observable-api';
|
||||
import { UmbStoreBase } from '@umbraco-cms/store';
|
||||
import { Template } from '@umbraco-cms/backend-api';
|
||||
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
|
||||
|
||||
/**
|
||||
* @export
|
||||
* @class UmbTemplateDetailStore
|
||||
* @extends {UmbStoreBase}
|
||||
* @description - Data Store for Template Details
|
||||
*/
|
||||
export class UmbTemplateDetailStore extends UmbStoreBase {
|
||||
#data = new ArrayState<Template>([], (x) => x.key);
|
||||
|
||||
/**
|
||||
* Creates an instance of UmbTemplateDetailStore.
|
||||
* @param {UmbControllerHostInterface} host
|
||||
* @memberof UmbTemplateDetailStore
|
||||
*/
|
||||
constructor(host: UmbControllerHostInterface) {
|
||||
super(host, UmbTemplateDetailStore.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a template to the store
|
||||
* @param {Template} template
|
||||
* @memberof UmbTemplateDetailStore
|
||||
*/
|
||||
append(template: Template) {
|
||||
this.#data.append([template]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes templates in the store with the given uniques
|
||||
* @param {string[]} uniques
|
||||
* @memberof UmbTemplateDetailStore
|
||||
*/
|
||||
remove(uniques: string[]) {
|
||||
this.#data.remove(uniques);
|
||||
}
|
||||
}
|
||||
|
||||
export const UMB_TEMPLATE_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbTemplateDetailStore>(
|
||||
UmbTemplateDetailStore.name
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { ManifestWorkspace, ManifestWorkspaceAction, ManifestWorkspaceView } from '@umbraco-cms/models';
|
||||
|
||||
const workspace: ManifestWorkspace = {
|
||||
type: 'workspace',
|
||||
alias: 'Umb.Workspace.Template',
|
||||
name: 'Template Workspace',
|
||||
loader: () => import('./template-workspace.element'),
|
||||
meta: {
|
||||
entityType: 'template',
|
||||
},
|
||||
};
|
||||
|
||||
const workspaceViews: Array<ManifestWorkspaceView> = [];
|
||||
|
||||
const workspaceActions: Array<ManifestWorkspaceAction> = [];
|
||||
|
||||
export const manifests = [workspace, ...workspaceViews, ...workspaceActions];
|
||||
@@ -0,0 +1,49 @@
|
||||
import { UmbTemplateDetailRepository } from './data/template.detail.repository';
|
||||
import { createObservablePart, DeepState } from '@umbraco-cms/observable-api';
|
||||
import { Template } from '@umbraco-cms/backend-api';
|
||||
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
|
||||
|
||||
export class UmbTemplateWorkspaceContext {
|
||||
#host: UmbControllerHostInterface;
|
||||
#templateDetailRepo: UmbTemplateDetailRepository;
|
||||
|
||||
#data = new DeepState<Template | undefined>(undefined);
|
||||
data = this.#data.asObservable();
|
||||
name = createObservablePart(this.#data, (data) => data?.name);
|
||||
content = createObservablePart(this.#data, (data) => data?.content);
|
||||
|
||||
constructor(host: UmbControllerHostInterface) {
|
||||
this.#host = host;
|
||||
this.#templateDetailRepo = new UmbTemplateDetailRepository(this.#host);
|
||||
}
|
||||
|
||||
setName(value: string) {
|
||||
this.#data.next({ ...this.#data.value, name: value });
|
||||
}
|
||||
|
||||
setContent(value: string) {
|
||||
this.#data.next({ ...this.#data.value, content: value });
|
||||
}
|
||||
|
||||
async load(entityKey: string) {
|
||||
const { data } = await this.#templateDetailRepo.get(entityKey);
|
||||
if (data) {
|
||||
this.#data.next(data);
|
||||
}
|
||||
}
|
||||
|
||||
async createScaffold(parentKey: string | null) {
|
||||
const { data } = await this.#templateDetailRepo.createScaffold(parentKey);
|
||||
if (!data) return;
|
||||
this.#data.next(data);
|
||||
}
|
||||
|
||||
async save(isNew: boolean) {
|
||||
if (!this.#data.value) return;
|
||||
isNew ? this.#templateDetailRepo.insert(this.#data.value) : this.#templateDetailRepo.update(this.#data.value);
|
||||
}
|
||||
|
||||
async delete(key: string) {
|
||||
await this.#templateDetailRepo.delete(key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html } from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { UmbTemplateWorkspaceContext } from './template-workspace.context';
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
import { UUIInputElement, UUITextareaElement } from '@umbraco-ui/uui';
|
||||
|
||||
@customElement('umb-template-workspace')
|
||||
export class UmbTemplateWorkspaceElement extends UmbLitElement {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#content {
|
||||
height: 200px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public load(entityKey: string) {
|
||||
this.#templateWorkspaceContext.load(entityKey);
|
||||
}
|
||||
|
||||
public create(parentKey: string | null) {
|
||||
this.#isNew = true;
|
||||
this.#templateWorkspaceContext.createScaffold(parentKey);
|
||||
}
|
||||
|
||||
@state()
|
||||
private _name?: string | null = '';
|
||||
|
||||
@state()
|
||||
private _content?: string | null = '';
|
||||
|
||||
#templateWorkspaceContext = new UmbTemplateWorkspaceContext(this);
|
||||
#isNew = false;
|
||||
|
||||
async connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
this.observe(this.#templateWorkspaceContext.name, (name) => {
|
||||
this._name = name;
|
||||
});
|
||||
|
||||
this.observe(this.#templateWorkspaceContext.content, (content) => {
|
||||
this._content = content;
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: temp code for testing create and save
|
||||
#onNameInput(event: Event) {
|
||||
const target = event.target as UUIInputElement;
|
||||
const value = target.value as string;
|
||||
this.#templateWorkspaceContext.setName(value);
|
||||
}
|
||||
|
||||
#onTextareaInput(event: Event) {
|
||||
const target = event.target as UUITextareaElement;
|
||||
const value = target.value as string;
|
||||
this.#templateWorkspaceContext.setContent(value);
|
||||
}
|
||||
|
||||
#onSave() {
|
||||
this.#templateWorkspaceContext.save(this.#isNew);
|
||||
}
|
||||
|
||||
render() {
|
||||
// TODO: add correct UI elements
|
||||
return html`<umb-workspace-layout>
|
||||
<uui-input .value=${this._name} @input=${this.#onNameInput}></uui-input>
|
||||
<uui-textarea id="content" .value=${this._content} @input="${this.#onTextareaInput}"></uui-textarea>
|
||||
<uui-button label="Save" look="primary" color="positive" @click=${this.#onSave}></uui-button>
|
||||
</umb-workspace-layout>`;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbTemplateWorkspaceElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-template-workspace': UmbTemplateWorkspaceElement;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { css, html } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import type { IRoute, IRoutingInfo } from 'router-slot';
|
||||
import { UmbUserStore, UMB_USER_STORE_CONTEXT_TOKEN } from '../../../users/user.store';
|
||||
import { umbExtensionsRegistry , createExtensionElement } from '@umbraco-cms/extensions-api';
|
||||
|
||||
import './list-view-layouts/table/workspace-view-users-table.element';
|
||||
@@ -9,7 +10,6 @@ import './list-view-layouts/grid/workspace-view-users-grid.element';
|
||||
import './workspace-view-users-selection.element';
|
||||
import './workspace-view-users-invite.element';
|
||||
import type { ManifestWorkspace, UserDetails } from '@umbraco-cms/models';
|
||||
import { UmbUserStore, UMB_USER_STORE_CONTEXT_TOKEN } from 'src/backoffice/users/users/user.store';
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
import { DeepState } from '@umbraco-cms/observable-api';
|
||||
|
||||
@@ -128,7 +128,7 @@ export class UmbSectionViewUsersElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<router-slot .routes=${this._routes}></router-slot>`;
|
||||
return html`<umb-router-slot .routes=${this._routes}></umb-router-slot>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -220,7 +220,7 @@ export class UmbWorkspaceViewUsersOverviewElement extends UmbLitElement {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<router-slot .routes=${this._routes}></router-slot>
|
||||
<umb-router-slot .routes=${this._routes}></umb-router-slot>
|
||||
|
||||
${this._renderSelection()}
|
||||
`;
|
||||
|
||||
@@ -19,6 +19,7 @@ import { handlers as dictionaryHandlers } from './domains/dictionary.handlers';
|
||||
import { handlers as mediaTypeHandlers } from './domains/media-type.handlers';
|
||||
import { handlers as memberGroupHandlers } from './domains/member-group.handlers';
|
||||
import { handlers as memberTypeHandlers } from './domains/member-type.handlers';
|
||||
import { handlers as templateHandlers } from './domains/template.handlers';
|
||||
import { handlers as languageHandlers } from './domains/language.handlers';
|
||||
import { handlers as redirectManagementHandlers } from './domains/redirect-management.handlers';
|
||||
|
||||
@@ -44,6 +45,7 @@ const handlers = [
|
||||
...healthCheckHandlers,
|
||||
...profilingHandlers,
|
||||
...dictionaryHandlers,
|
||||
...templateHandlers,
|
||||
...languageHandlers,
|
||||
...redirectManagementHandlers,
|
||||
];
|
||||
|
||||
120
src/Umbraco.Web.UI.Client/src/core/mocks/data/template.data.ts
Normal file
120
src/Umbraco.Web.UI.Client/src/core/mocks/data/template.data.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { UmbEntityData } from './entity.data';
|
||||
import { createEntityTreeItem } from './utils';
|
||||
import {
|
||||
EntityTreeItem,
|
||||
PagedEntityTreeItem,
|
||||
Template,
|
||||
TemplateCreateModel,
|
||||
TemplateScaffold,
|
||||
} from '@umbraco-cms/backend-api';
|
||||
|
||||
type TemplateDBItem = Template & EntityTreeItem;
|
||||
|
||||
const createTemplate = (dbItem: TemplateDBItem): Template => {
|
||||
return {
|
||||
key: dbItem.key,
|
||||
name: dbItem.name,
|
||||
alias: dbItem.alias,
|
||||
content: dbItem.content,
|
||||
};
|
||||
};
|
||||
|
||||
export const data: Array<TemplateDBItem> = [
|
||||
{
|
||||
key: '2bf464b6-3aca-4388-b043-4eb439cc2643',
|
||||
isContainer: false,
|
||||
parentKey: null,
|
||||
name: 'Doc 1',
|
||||
type: 'template',
|
||||
icon: 'icon-layout',
|
||||
hasChildren: false,
|
||||
alias: 'Doc1',
|
||||
content:
|
||||
'@using Umbraco.Cms.Web.Common.PublishedModels;\n@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<ContentModels.Doc1>\r\n@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels;\r\n@{\r\n\tLayout = null;\r\n}',
|
||||
},
|
||||
{
|
||||
key: '9a84c0b3-03b4-4dd4-84ac-706740ac0f71',
|
||||
isContainer: false,
|
||||
parentKey: null,
|
||||
name: 'Test',
|
||||
type: 'template',
|
||||
icon: 'icon-layout',
|
||||
hasChildren: true,
|
||||
alias: 'Test',
|
||||
content:
|
||||
'@using Umbraco.Cms.Web.Common.PublishedModels;\n@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<ContentModels.Test>\r\n@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels;\r\n@{\r\n\tLayout = null;\r\n}',
|
||||
},
|
||||
{
|
||||
key: '9a84c0b3-03b4-4dd4-84ac-706740ac0f72',
|
||||
isContainer: false,
|
||||
parentKey: '9a84c0b3-03b4-4dd4-84ac-706740ac0f71',
|
||||
name: 'Child',
|
||||
type: 'template',
|
||||
icon: 'icon-layout',
|
||||
hasChildren: false,
|
||||
alias: 'Test',
|
||||
content:
|
||||
'@using Umbraco.Cms.Web.Common.PublishedModels;\n@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<ContentModels.Test>\r\n@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels;\r\n@{\r\n\tLayout = null;\r\n}',
|
||||
},
|
||||
];
|
||||
|
||||
export const createTemplateScaffold = (masterTemplateAlias: string) => {
|
||||
return `Template Scaffold Mock for master template: ${masterTemplateAlias}`;
|
||||
};
|
||||
|
||||
// Temp mocked database
|
||||
// TODO: all properties are optional in the server schema. I don't think this is correct.
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
class UmbTemplateData extends UmbEntityData<TemplateDBItem> {
|
||||
constructor() {
|
||||
super(data);
|
||||
}
|
||||
|
||||
getByKey(key: string): Template | undefined {
|
||||
const item = this.data.find((item) => item.key === key);
|
||||
return item ? createTemplate(item) : undefined;
|
||||
}
|
||||
|
||||
getScaffold(masterTemplateAlias: string): TemplateScaffold {
|
||||
return {
|
||||
content: `Template Scaffold Mock: Layout = ${masterTemplateAlias || null};`,
|
||||
};
|
||||
}
|
||||
|
||||
create(templateData: TemplateCreateModel) {
|
||||
const template = {
|
||||
key: uuid(),
|
||||
...templateData,
|
||||
};
|
||||
this.data.push(template);
|
||||
return template;
|
||||
}
|
||||
|
||||
update(template: Template) {
|
||||
this.updateData(template);
|
||||
return template;
|
||||
}
|
||||
|
||||
getTreeRoot(): PagedEntityTreeItem {
|
||||
const items = this.data.filter((item) => item.parentKey === null);
|
||||
const treeItems = items.map((item) => createEntityTreeItem(item));
|
||||
const total = items.length;
|
||||
return { items: treeItems, total };
|
||||
}
|
||||
|
||||
getTreeItemChildren(key: string): PagedEntityTreeItem {
|
||||
const items = this.data.filter((item) => item.parentKey === key);
|
||||
const treeItems = items.map((item) => createEntityTreeItem(item));
|
||||
const total = items.length;
|
||||
return { items: treeItems, total };
|
||||
}
|
||||
|
||||
getTreeItem(keys: Array<string>): Array<EntityTreeItem> {
|
||||
const items = this.data.filter((item) => keys.includes(item.key ?? ''));
|
||||
return items.map((item) => createEntityTreeItem(item));
|
||||
}
|
||||
}
|
||||
|
||||
export const umbTemplateData = new UmbTemplateData();
|
||||
@@ -0,0 +1,61 @@
|
||||
import { rest } from 'msw';
|
||||
import { umbracoPath } from '@umbraco-cms/utils';
|
||||
import { umbTemplateData } from '../data/template.data';
|
||||
import { TemplateCreateModel, TemplateUpdateModel } from '@umbraco-cms/backend-api';
|
||||
|
||||
// TODO: add schema
|
||||
export const handlers = [
|
||||
rest.get(umbracoPath('/tree/template/root'), (req, res, ctx) => {
|
||||
const response = umbTemplateData.getTreeRoot();
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
|
||||
rest.get(umbracoPath('/tree/template/children'), (req, res, ctx) => {
|
||||
const parentKey = req.url.searchParams.get('parentKey');
|
||||
if (!parentKey) return;
|
||||
|
||||
const response = umbTemplateData.getTreeItemChildren(parentKey);
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
|
||||
rest.get(umbracoPath('/tree/template/item'), (req, res, ctx) => {
|
||||
const keys = req.url.searchParams.getAll('key');
|
||||
if (!keys) return;
|
||||
|
||||
const items = umbTemplateData.getTreeItem(keys);
|
||||
return res(ctx.status(200), ctx.json(items));
|
||||
}),
|
||||
|
||||
rest.get(umbracoPath('/template/scaffold'), (req, res, ctx) => {
|
||||
const masterTemplateAlias = req.url.searchParams.get('masterTemplateAlias');
|
||||
if (!masterTemplateAlias) return;
|
||||
|
||||
const response = umbTemplateData.getScaffold(masterTemplateAlias);
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
|
||||
rest.get(umbracoPath('/template/:key'), (req, res, ctx) => {
|
||||
const key = req.params.key as string;
|
||||
if (!key) return;
|
||||
|
||||
const response = umbTemplateData.getByKey(key);
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
|
||||
rest.put<TemplateUpdateModel>(umbracoPath('/template/:key'), async (req, res, ctx) => {
|
||||
const key = req.params.key as string;
|
||||
const data = await req.json();
|
||||
if (!key) return;
|
||||
|
||||
umbTemplateData.update(data);
|
||||
return res(ctx.status(200));
|
||||
}),
|
||||
|
||||
rest.post<TemplateCreateModel>(umbracoPath('/template'), async (req, res, ctx) => {
|
||||
const data = await req.json();
|
||||
if (!data) return;
|
||||
|
||||
const created = umbTemplateData.create(data);
|
||||
return res(ctx.status(200), ctx.json(created));
|
||||
}),
|
||||
];
|
||||
Reference in New Issue
Block a user