Merge branch 'main' of https://github.com/umbraco/Umbraco.CMS.Backoffice
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# Copy this to .env.local and change what you want to test.
|
||||
VITE_UMBRACO_USE_MSW=on # on = turns on MSW, off = disables all mock handlers
|
||||
VITE_UMBRACO_API_URL=http://localhost:9000
|
||||
VITE_UMBRACO_API_URL=http://localhost:11000
|
||||
VITE_UMBRACO_INSTALL_STATUS=running # running or must-install or must-upgrade
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
VITE_UMBRACO_INSTALL_STATUS=running # running or must-install or must-upgrade
|
||||
VITE_UMBRACO_USE_MSW=off
|
||||
VITE_UMBRACO_API_URL=
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
# path: coverage/
|
||||
# retention-days: 30
|
||||
- name: Report code coverage
|
||||
uses: zgosalvez/github-actions-report-lcov@v1
|
||||
uses: zgosalvez/github-actions-report-lcov@v2
|
||||
if: always()
|
||||
continue-on-error: true
|
||||
with:
|
||||
|
||||
@@ -27,7 +27,11 @@ jobs:
|
||||
|
||||
- name: Run DevSkim scanner
|
||||
uses: microsoft/DevSkim-Action@v1
|
||||
|
||||
with:
|
||||
directory-to-scan: src
|
||||
should-scan-archives: false
|
||||
ignore-globs: "**/.git/**,*.md,*.mdx,*.stories.ts,*.js"
|
||||
|
||||
- name: Upload DevSkim scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
with:
|
||||
|
||||
2
src/Umbraco.Web.UI.Client/.gitignore
vendored
2
src/Umbraco.Web.UI.Client/.gitignore
vendored
@@ -19,6 +19,8 @@ types
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
!.vscode/settings.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
|
||||
3
src/Umbraco.Web.UI.Client/.storybook/package.json
Normal file
3
src/Umbraco.Web.UI.Client/.storybook/package.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "commonjs"
|
||||
}
|
||||
@@ -9,9 +9,8 @@ import { initialize, mswDecorator } from 'msw-storybook-addon';
|
||||
import { setCustomElements } from '@storybook/web-components';
|
||||
|
||||
import customElementManifests from '../custom-elements.json';
|
||||
import { UmbDataTypeStore } from '../src/core/stores/data-type/data-type.store';
|
||||
import { UmbDocumentTypeStore } from '../src/core/stores/document-type.store';
|
||||
import { UmbNodeStore } from '../src/core/stores/node.store';
|
||||
import { UmbDataTypesStore } from '../src/core/stores/data-types/data-types.store';
|
||||
import { UmbDocumentTypeStore } from '../src/core/stores/document-type/document-type.store';
|
||||
import { UmbIconStore } from '../src/core/stores/icon/icon.store';
|
||||
import { onUnhandledRequest } from '../src/core/mocks/browser';
|
||||
import { handlers } from '../src/core/mocks/browser-handlers';
|
||||
@@ -22,14 +21,15 @@ import { manifests as sectionManifests } from '../src/backoffice/sections/manife
|
||||
import { manifests as propertyEditorModelManifests } from '../src/backoffice/property-editor-models/manifests';
|
||||
import { manifests as propertyEditorUIManifests } from '../src/backoffice/property-editor-uis/manifests';
|
||||
import { manifests as treeManifests } from '../src/backoffice/trees/manifests';
|
||||
import { manifests as editorManifests } from '../src/backoffice/editors/manifests';
|
||||
import { manifests as workspaceManifests } from '../src/backoffice/workspaces/manifests';
|
||||
import { manifests as propertyActionManifests } from '../src/backoffice/property-actions/manifests';
|
||||
|
||||
import { umbExtensionsRegistry } from '../src/core/extensions-registry';
|
||||
|
||||
import '../src/core/context-api/provide/context-provider.element';
|
||||
import '../src/core/css/custom-properties.css';
|
||||
import '../src/backoffice/components/backoffice-modal-container.element';
|
||||
import '../src/backoffice/components/backoffice-frame/backoffice-modal-container.element';
|
||||
import '../src/backoffice/components/shared/code-block.element';
|
||||
|
||||
class UmbStoryBookElement extends LitElement {
|
||||
_umbIconStore = new UmbIconStore();
|
||||
@@ -40,7 +40,7 @@ class UmbStoryBookElement extends LitElement {
|
||||
|
||||
this._registerExtensions(sectionManifests);
|
||||
this._registerExtensions(treeManifests);
|
||||
this._registerExtensions(editorManifests);
|
||||
this._registerExtensions(workspaceManifests);
|
||||
this._registerExtensions(propertyEditorModelManifests);
|
||||
this._registerExtensions(propertyEditorUIManifests);
|
||||
this._registerExtensions(propertyActionManifests);
|
||||
@@ -62,12 +62,8 @@ customElements.define('umb-storybook', UmbStoryBookElement);
|
||||
|
||||
const storybookProvider = (story) => html` <umb-storybook>${story()}</umb-storybook> `;
|
||||
|
||||
const nodeStoreProvider = (story) => html`
|
||||
<umb-context-provider key="umbNodeStore" .value=${new UmbNodeStore()}>${story()}</umb-context-provider>
|
||||
`;
|
||||
|
||||
const dataTypeStoreProvider = (story) => html`
|
||||
<umb-context-provider key="umbDataTypeStore" .value=${new UmbDataTypeStore()}>${story()}</umb-context-provider>
|
||||
<umb-context-provider key="umbDataTypeStore" .value=${new UmbDataTypesStore()}>${story()}</umb-context-provider>
|
||||
`;
|
||||
|
||||
const documentTypeStoreProvider = (story) => html`
|
||||
@@ -90,7 +86,6 @@ initialize({ onUnhandledRequest });
|
||||
export const decorators = [
|
||||
mswDecorator,
|
||||
storybookProvider,
|
||||
nodeStoreProvider,
|
||||
dataTypeStoreProvider,
|
||||
documentTypeStoreProvider,
|
||||
modalServiceProvider,
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"dbaeumer.vscode-eslint",
|
||||
"runem.lit-plugin",
|
||||
"esbenp.prettier-vscode",
|
||||
"hbenl.vscode-test-explorer"
|
||||
"hbenl.vscode-test-explorer",
|
||||
"vunguyentuan.vscode-css-variables"
|
||||
]
|
||||
}
|
||||
|
||||
27
src/Umbraco.Web.UI.Client/.vscode/lit.code-snippets
vendored
Normal file
27
src/Umbraco.Web.UI.Client/.vscode/lit.code-snippets
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"Create Lit Component": {
|
||||
"prefix": "lit new",
|
||||
"scope": "typescript",
|
||||
"body": [
|
||||
"import { UUITextStyles } from '@umbraco-ui/uui-css';",
|
||||
"import { css, html, LitElement } from 'lit';",
|
||||
"import { customElement } from 'lit/decorators.js';",
|
||||
"",
|
||||
"@customElement('umb-${TM_FILENAME_BASE/(.*)\\..+$/$1/}')",
|
||||
"export class Umb${TM_FILENAME_BASE/(.*)\\..+$/${1:/pascalcase}/}Element extends LitElement {",
|
||||
"\tstatic styles = [UUITextStyles, css``];",
|
||||
"",
|
||||
"\trender() {",
|
||||
"\t\treturn html`${0:umb-${TM_FILENAME_BASE/(.*)\\..+$/$1/}}`;",
|
||||
"\t}",
|
||||
"}",
|
||||
"",
|
||||
"declare global {",
|
||||
"\tinterface HTMLElementTagNameMap {",
|
||||
"\t\t'umb-${TM_FILENAME_BASE/(.*)\\..+$/$1/}': Umb${TM_FILENAME_BASE/(.*)\\..+$/${1:/pascalcase}/}Element;",
|
||||
"\t}",
|
||||
"}"
|
||||
],
|
||||
"description": "Create a new Lit Component"
|
||||
}
|
||||
}
|
||||
3
src/Umbraco.Web.UI.Client/.vscode/settings.json
vendored
Normal file
3
src/Umbraco.Web.UI.Client/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"cssVariables.lookupFiles": ["node_modules/@umbraco-ui/uui-css/dist/custom-properties.css"]
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { rest } from 'msw';
|
||||
|
||||
import { expect, test } from '../test';
|
||||
import { umbracoPath } from '@umbraco-cms/utils';
|
||||
import { ProblemDetails, RuntimeLevel, ServerStatus } from '@umbraco-cms/backend-api';
|
||||
import { expect, test } from './test';
|
||||
|
||||
test.describe('installer tests', () => {
|
||||
test.beforeEach(async ({ page, worker }) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { expect, test } from '../test';
|
||||
import { expect, test } from './test';
|
||||
|
||||
test('login', async ({ page }) => {
|
||||
// Go to /login
|
||||
|
||||
4
src/Umbraco.Web.UI.Client/e2e/package.json
Normal file
4
src/Umbraco.Web.UI.Client/e2e/package.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "backoffice-e2e",
|
||||
"type": "commonjs"
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { expect, test as base } from '@playwright/test';
|
||||
import { createWorkerFixture } from 'playwright-msw';
|
||||
import type { MockServiceWorker } from 'playwright-msw';
|
||||
|
||||
import { handlers } from './src/core/mocks/e2e-handlers';
|
||||
import { handlers } from '../src/core/mocks/e2e-handlers';
|
||||
|
||||
const test = base.extend<{
|
||||
worker: MockServiceWorker;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { rest } from 'msw';
|
||||
import { expect, test } from '../test';
|
||||
import { umbracoPath } from '@umbraco-cms/utils';
|
||||
import { ProblemDetails, RuntimeLevel, ServerStatus } from '@umbraco-cms/backend-api';
|
||||
import { expect, test } from './test';
|
||||
|
||||
test.describe('upgrader tests', () => {
|
||||
test.beforeEach(async ({ page, worker }) => {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Umbraco</title>
|
||||
<script type="module" src="/src/index.ts"></script>
|
||||
|
||||
7573
src/Umbraco.Web.UI.Client/package-lock.json
generated
7573
src/Umbraco.Web.UI.Client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,16 @@
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"main": "dist/main.js",
|
||||
"exports": {
|
||||
".": "./dist/main.js"
|
||||
},
|
||||
"types": "types/src/app.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"types"
|
||||
],
|
||||
"repository": {
|
||||
"url": "https://github.com/umbraco/Umbraco.CMS.Backoffice",
|
||||
"type": "git"
|
||||
@@ -18,7 +28,9 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build --mode staging",
|
||||
"build:production": "tsc && vite build",
|
||||
"build:for:static": "tsc && vite build",
|
||||
"build:for:cms": "tsc && vite build -c vite.cms.config.ts",
|
||||
"build:for:cms:watch": "npm run build:for:cms -- --watch",
|
||||
"preview": "vite preview --open",
|
||||
"test": "web-test-runner --coverage",
|
||||
"test:watch": "web-test-runner --watch",
|
||||
@@ -31,9 +43,9 @@
|
||||
"generate:api-dev": "openapi --input http://localhost:9000/umbraco/swagger/v1/swagger.json --output src/core/backend-api --postfix Resource --useOptions",
|
||||
"storybook": "npm run wc-analyze && start-storybook -p 6006",
|
||||
"build-storybook": "npm run wc-analyze && build-storybook",
|
||||
"generate:icons": "node ./devops/icons/index.mjs",
|
||||
"generate:icons": "node ./devops/icons/index.js",
|
||||
"wc-analyze": "wca **/*.element.ts --outFile custom-elements.json",
|
||||
"new-extension": "plop --plopfile ./devops/plop/plop.mjs",
|
||||
"new-extension": "plop --plopfile ./devops/plop/plop.js",
|
||||
"compile": "tsc"
|
||||
},
|
||||
"engines": {
|
||||
@@ -49,57 +61,57 @@
|
||||
"@umbraco-ui/uui-modal-sidebar": "file:umbraco-ui-uui-modal-sidebar-0.0.0.tgz",
|
||||
"@umbraco-ui/uui-color-swatches": "file:umbraco-ui-uui-color-swatches-2.0.0.tgz",
|
||||
"@umbraco-ui/uui-color-swatch": "file:umbraco-ui-uui-color-swatch-0.0.0.tgz",
|
||||
"element-internals-polyfill": "^1.1.16",
|
||||
"lit": "^2.4.1",
|
||||
"element-internals-polyfill": "^1.1.17",
|
||||
"lit": "^2.5.0",
|
||||
"lodash": "^4.17.21",
|
||||
"openapi-typescript-fetch": "^1.1.3",
|
||||
"router-slot": "^1.5.5",
|
||||
"rxjs": "^7.5.7",
|
||||
"rxjs": "^7.8.0",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.2",
|
||||
"@mdx-js/react": "^2.1.5",
|
||||
"@babel/core": "^7.20.5",
|
||||
"@mdx-js/react": "^2.2.1",
|
||||
"@open-wc/testing": "^3.1.7",
|
||||
"@playwright/test": "^1.28.0",
|
||||
"@storybook/addon-a11y": "^6.5.13",
|
||||
"@storybook/addon-actions": "^6.5.13",
|
||||
"@storybook/addon-essentials": "^6.5.13",
|
||||
"@playwright/test": "^1.28.1",
|
||||
"@storybook/addon-a11y": "^6.5.14",
|
||||
"@storybook/addon-actions": "^6.5.14",
|
||||
"@storybook/addon-essentials": "^6.5.14",
|
||||
"@storybook/addon-links": "^6.5.13",
|
||||
"@storybook/builder-vite": "^0.2.5",
|
||||
"@storybook/builder-vite": "^0.2.6",
|
||||
"@storybook/mdx2-csf": "^0.0.3",
|
||||
"@storybook/web-components": "^6.5.13",
|
||||
"@storybook/web-components": "^6.5.14",
|
||||
"@types/chai": "^4.3.4",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"@types/mocha": "^10.0.0",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.43.0",
|
||||
"@typescript-eslint/parser": "^5.43.0",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.46.1",
|
||||
"@typescript-eslint/parser": "^5.46.1",
|
||||
"@web/dev-server-esbuild": "^0.3.3",
|
||||
"@web/dev-server-import-maps": "^0.0.7",
|
||||
"@web/test-runner": "^0.15.0",
|
||||
"@web/test-runner-playwright": "^0.9.0",
|
||||
"babel-loader": "^9.1.0",
|
||||
"eslint": "^8.27.0",
|
||||
"eslint": "^8.29.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-import-resolver-typescript": "^3.5.2",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-lit": "^1.6.1",
|
||||
"eslint-plugin-lit": "^1.7.1",
|
||||
"eslint-plugin-lit-a11y": "^2.3.0",
|
||||
"eslint-plugin-local-rules": "^1.3.2",
|
||||
"eslint-plugin-storybook": "^0.6.7",
|
||||
"lit-html": "^2.4.0",
|
||||
"msw": "^0.48.3",
|
||||
"eslint-plugin-storybook": "^0.6.8",
|
||||
"lit-html": "^2.5.0",
|
||||
"msw": "^0.49.2",
|
||||
"msw-storybook-addon": "^1.6.3",
|
||||
"openapi-typescript-codegen": "^0.23.0",
|
||||
"playwright-msw": "^2.0.1",
|
||||
"playwright-msw": "^2.1.0",
|
||||
"plop": "^3.1.1",
|
||||
"prettier": "2.7.1",
|
||||
"prettier": "2.8.1",
|
||||
"tiny-glob": "^0.2.9",
|
||||
"typescript": "^4.9.3",
|
||||
"typescript": "^4.9.4",
|
||||
"vite": "^3.2.4",
|
||||
"vite-plugin-static-copy": "^0.12.0",
|
||||
"vite-tsconfig-paths": "^3.5.2",
|
||||
"vite-plugin-static-copy": "^0.13.0",
|
||||
"vite-tsconfig-paths": "^4.0.3",
|
||||
"web-component-analyzer": "^2.0.0-next.4"
|
||||
},
|
||||
"msw": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const template = document.createElement('template');
|
||||
template.innerHTML = `
|
||||
<umb-editor-layout role="aside">
|
||||
<umb-body-layout role="aside">
|
||||
<h1 slot="header">My package view</h1>
|
||||
|
||||
<uui-box>
|
||||
@@ -10,7 +10,7 @@ template.innerHTML = `
|
||||
<uui-action-bar slot="footer">
|
||||
<uui-button look="primary" type="button">Close</uui-button>
|
||||
</uui-action-bar>
|
||||
</umb-editor-layout>
|
||||
</umb-body-layout>
|
||||
`;
|
||||
|
||||
export default class MyPackageViewCustom extends HTMLElement {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -2,7 +2,7 @@
|
||||
/* tslint:disable */
|
||||
|
||||
/**
|
||||
* Mock Service Worker (0.48.3).
|
||||
* Mock Service Worker (0.49.2).
|
||||
* @see https://github.com/mswjs/msw
|
||||
* - Please do NOT modify this file.
|
||||
* - Please do NOT serve this file on production.
|
||||
|
||||
@@ -6,13 +6,14 @@ import '@umbraco-ui/uui-modal-sidebar';
|
||||
import '@umbraco-ui/uui-color-swatch';
|
||||
import '@umbraco-ui/uui-color-swatches';
|
||||
import 'router-slot';
|
||||
import 'element-internals-polyfill';
|
||||
|
||||
// TODO: remove these imports when they are part of UUI
|
||||
import type { Guard, IRoute } from 'router-slot/model';
|
||||
|
||||
import { UUIIconRegistryEssential } from '@umbraco-ui/uui';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import {customElement, property, state} from 'lit/decorators.js';
|
||||
|
||||
import { OpenAPI, RuntimeLevel, ServerResource } from '@umbraco-cms/backend-api';
|
||||
import { UmbContextProviderMixin } from '@umbraco-cms/context-api';
|
||||
@@ -32,6 +33,9 @@ export class UmbApp extends UmbContextProviderMixin(LitElement) {
|
||||
}
|
||||
`;
|
||||
|
||||
@property({ type: String })
|
||||
private umbracoUrl?: string;
|
||||
|
||||
@state()
|
||||
private _routes: IRoute[] = [
|
||||
{
|
||||
@@ -66,9 +70,11 @@ export class UmbApp extends UmbContextProviderMixin(LitElement) {
|
||||
async connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
OpenAPI.BASE = import.meta.env.VITE_UMBRACO_USE_MSW === 'on' ? '' : import.meta.env.VITE_UMBRACO_API_URL;
|
||||
OpenAPI.BASE = import.meta.env.VITE_UMBRACO_USE_MSW === 'on' ? '' : this.umbracoUrl ?? import.meta.env.VITE_UMBRACO_API_URL ?? '';
|
||||
OpenAPI.WITH_CREDENTIALS = true;
|
||||
|
||||
this.provideContext('UMBRACOBASE', OpenAPI.BASE);
|
||||
|
||||
await this._setInitStatus();
|
||||
await this._registerExtensionManifestsFromServer();
|
||||
this._redirect();
|
||||
@@ -114,7 +120,8 @@ export class UmbApp extends UmbContextProviderMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _isAuthorized(): boolean {
|
||||
return sessionStorage.getItem('is-authenticated') === 'true';
|
||||
return true; // TODO: Return true for now, until new login page is up and running
|
||||
//return sessionStorage.getItem('is-authenticated') === 'true';
|
||||
}
|
||||
|
||||
private _isAuthorizedGuard(redirectTo?: string): Guard {
|
||||
@@ -123,13 +130,14 @@ export class UmbApp extends UmbContextProviderMixin(LitElement) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let returnPath = '/login';
|
||||
let returnPath = `${OpenAPI.BASE}/umbraco/login`;
|
||||
|
||||
if (redirectTo) {
|
||||
returnPath += `?redirectTo=${redirectTo}`;
|
||||
}
|
||||
|
||||
history.replaceState(null, '', returnPath);
|
||||
// Redirect user completely to login page
|
||||
location.href = returnPath;
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export class UmbAuthLayout extends LitElement {
|
||||
background-position: 50%;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-image: url('/login.jpeg');
|
||||
background-image: url('login.jpeg');
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
4
src/Umbraco.Web.UI.Client/src/auth/auth.ts
Normal file
4
src/Umbraco.Web.UI.Client/src/auth/auth.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default function() {
|
||||
sessionStorage.setItem('is-authenticated', 'true');
|
||||
history.replaceState(null, '', 'section');
|
||||
}
|
||||
@@ -47,7 +47,7 @@ export default class UmbLogin extends LitElement {
|
||||
this._loggingIn = false;
|
||||
let { redirectTo } = query();
|
||||
if (!redirectTo) {
|
||||
redirectTo = '/section';
|
||||
redirectTo = 'section';
|
||||
}
|
||||
sessionStorage.setItem('is-authenticated', 'true');
|
||||
history.pushState(null, '', redirectTo);
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
//TODO: we need to figure out what components should be available for extensions and load them upfront
|
||||
import './components/backoffice-header.element';
|
||||
import './components/backoffice-main.element';
|
||||
import './components/backoffice-modal-container.element';
|
||||
import './components/backoffice-notification-container.element';
|
||||
import './components/node-property/node-property.element';
|
||||
import './workspaces/shared/workspace-entity-layout/workspace-entity-layout.element';
|
||||
import './components/ref-property-editor-ui/ref-property-editor-ui.element';
|
||||
import './components/backoffice-frame/backoffice-header.element';
|
||||
import './components/backoffice-frame/backoffice-main.element';
|
||||
import './components/backoffice-frame/backoffice-modal-container.element';
|
||||
import './components/backoffice-frame/backoffice-notification-container.element';
|
||||
import './components/content-property/content-property.element';
|
||||
import './components/table/table.element';
|
||||
import './components/shared/code-block.element';
|
||||
import './components/extension-slot/extension-slot.element';
|
||||
import './sections/shared/section-main/section-main.element';
|
||||
import './sections/shared/section-sidebar/section-sidebar.element';
|
||||
import './sections/shared/section.element';
|
||||
@@ -17,23 +21,29 @@ import { css, html, LitElement } from 'lit';
|
||||
|
||||
import { UmbModalService } from '../core/services/modal';
|
||||
import { UmbNotificationService } from '../core/services/notification';
|
||||
import { UmbDataTypeStore } from '../core/stores/data-type/data-type.store';
|
||||
import { UmbDocumentTypeStore } from '../core/stores/document-type.store';
|
||||
import { UmbNodeStore } from '../core/stores/node.store';
|
||||
import { UmbDataTypesStore } from '../core/stores/data-types/data-types.store';
|
||||
import { UmbDocumentTypeStore } from '../core/stores/document-type/document-type.store';
|
||||
import { UmbMediaTypeStore } from '../core/stores/media-type/media-type.store';
|
||||
import { UmbMemberTypeStore } from '../core/stores/member-type/member-type.store';
|
||||
import { UmbDocumentStore } from '../core/stores/document/document.store';
|
||||
import { UmbMediaStore } from '../core/stores/media/media.store';
|
||||
import { UmbMemberGroupStore } from '../core/stores/member-group/member-group.store';
|
||||
import { UmbSectionStore } from '../core/stores/section.store';
|
||||
import { UmbEntityStore } from '../core/stores/entity.store';
|
||||
import { UmbUserStore } from '../core/stores/user/user.store';
|
||||
import { UmbIconStore } from '../core/stores/icon/icon.store';
|
||||
import { UmbUserGroupStore } from '../core/stores/user/user-group.store';
|
||||
import { UmbCurrentUserHistoryStore } from '../core/stores/current-user-history/current-user-history.store';
|
||||
import { manifests as sectionManifests } from './sections/manifests';
|
||||
import { manifests as propertyEditorModelManifests } from './property-editor-models/manifests';
|
||||
import { manifests as propertyEditorUIManifests } from './property-editor-uis/manifests';
|
||||
import { manifests as treeManifests } from './trees/manifests';
|
||||
import { manifests as editorManifests } from './editors/manifests';
|
||||
import { manifests as editorManifests } from './workspaces/manifests';
|
||||
import { manifests as propertyActionManifests } from './property-actions/manifests';
|
||||
import { manifests as externalLoginProviderManifests } from './external-login-providers/manifests';
|
||||
import { manifests as userDashboards } from './user-dashboards/manifests';
|
||||
import { UmbContextConsumerMixin, UmbContextProviderMixin } from '@umbraco-cms/context-api';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry';
|
||||
import type { ManifestTypes, ManifestWithLoader } from '@umbraco-cms/models';
|
||||
import type { ManifestTypes } from '@umbraco-cms/models';
|
||||
|
||||
@defineElement('umb-backoffice')
|
||||
export class UmbBackofficeElement extends UmbContextConsumerMixin(UmbContextProviderMixin(LitElement)) {
|
||||
@@ -53,7 +63,6 @@ export class UmbBackofficeElement extends UmbContextConsumerMixin(UmbContextProv
|
||||
];
|
||||
|
||||
private _umbIconRegistry = new UmbIconStore();
|
||||
private _umbEntityStore = new UmbEntityStore();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -64,21 +73,27 @@ export class UmbBackofficeElement extends UmbContextConsumerMixin(UmbContextProv
|
||||
this._registerExtensions(propertyEditorModelManifests);
|
||||
this._registerExtensions(propertyEditorUIManifests);
|
||||
this._registerExtensions(propertyActionManifests);
|
||||
this._registerExtensions(externalLoginProviderManifests);
|
||||
this._registerExtensions(userDashboards);
|
||||
|
||||
this._umbIconRegistry.attach(this);
|
||||
|
||||
this.provideContext('umbEntityStore', this._umbEntityStore);
|
||||
this.provideContext('umbNodeStore', new UmbNodeStore(this._umbEntityStore));
|
||||
this.provideContext('umbDataTypeStore', new UmbDataTypeStore(this._umbEntityStore));
|
||||
this.provideContext('umbDocumentTypeStore', new UmbDocumentTypeStore(this._umbEntityStore));
|
||||
this.provideContext('umbUserStore', new UmbUserStore(this._umbEntityStore));
|
||||
this.provideContext('umbUserGroupStore', new UmbUserGroupStore(this._umbEntityStore));
|
||||
this.provideContext('umbDocumentStore', new UmbDocumentStore());
|
||||
this.provideContext('umbMediaStore', new UmbMediaStore());
|
||||
this.provideContext('umbDataTypeStore', new UmbDataTypesStore());
|
||||
this.provideContext('umbDocumentTypeStore', new UmbDocumentTypeStore());
|
||||
this.provideContext('umbMediaTypeStore', new UmbMediaTypeStore());
|
||||
this.provideContext('umbMemberTypeStore', new UmbMemberTypeStore());
|
||||
this.provideContext('umbUserStore', new UmbUserStore());
|
||||
this.provideContext('umbUserGroupStore', new UmbUserGroupStore());
|
||||
this.provideContext('umbMemberGroupStore', new UmbMemberGroupStore());
|
||||
this.provideContext('umbNotificationService', new UmbNotificationService());
|
||||
this.provideContext('umbModalService', new UmbModalService());
|
||||
this.provideContext('umbSectionStore', new UmbSectionStore());
|
||||
this.provideContext('umbCurrentUserHistoryStore', new UmbCurrentUserHistoryStore());
|
||||
}
|
||||
|
||||
private _registerExtensions(manifests: Array<ManifestWithLoader<ManifestTypes>> | Array<ManifestTypes>) {
|
||||
private _registerExtensions(manifests: Array<ManifestTypes> | Array<ManifestTypes>) {
|
||||
manifests.forEach((manifest) => {
|
||||
if (umbExtensionsRegistry.isRegistered(manifest.alias)) return;
|
||||
umbExtensionsRegistry.register(manifest);
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, CSSResultGroup, html, LitElement } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import { ManifestHeaderApp, umbExtensionsRegistry } from '@umbraco-cms/extensions-registry';
|
||||
|
||||
@customElement('umb-backoffice-header-apps')
|
||||
export class UmbBackofficeHeaderApps extends LitElement {
|
||||
|
||||
static styles: CSSResultGroup = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
#apps {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--uui-size-space-2);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._registerHeaderApps();
|
||||
}
|
||||
|
||||
private _registerHeaderApps() {
|
||||
const headerApps: Array<ManifestHeaderApp> = [
|
||||
{
|
||||
type: 'headerApp',
|
||||
alias: 'Umb.HeaderApp.Search',
|
||||
name: 'Header App Search',
|
||||
loader: () => import('../../header-apps/header-app-button.element'),
|
||||
weight: 10,
|
||||
meta: {
|
||||
label: 'Search',
|
||||
icon: 'search',
|
||||
pathname: 'search',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'headerApp',
|
||||
alias: 'Umb.HeaderApp.Favorites',
|
||||
name: 'Header App Favorites',
|
||||
loader: () => import('../../header-apps/header-app-button.element'),
|
||||
weight: 100,
|
||||
meta: {
|
||||
label: 'Favorites',
|
||||
icon: 'favorite',
|
||||
pathname: 'favorites',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'headerApp',
|
||||
alias: 'Umb.HeaderApp.CurrentUser',
|
||||
name: 'Current User',
|
||||
loader: () => import('../../header-apps/header-app-current-user.element'),
|
||||
weight: 1000,
|
||||
meta: {
|
||||
label: 'TODO: how should we enable this to not be set.',
|
||||
icon: 'TODO: how should we enable this to not be set.',
|
||||
pathname: 'user',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// TODO: Can we make this functionality reuseable...
|
||||
headerApps.forEach((headerApp) => {
|
||||
if (umbExtensionsRegistry.isRegistered(headerApp.alias)) return;
|
||||
umbExtensionsRegistry.register(headerApp);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<umb-extension-slot id="apps" type="headerApp"></umb-extension-slot>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-backoffice-header-apps': UmbBackofficeHeaderApps;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, CSSResultGroup, html, LitElement } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { when } from 'lit/directives/when.js';
|
||||
import { UmbSectionStore } from '../../core/stores/section.store';
|
||||
import { UmbSectionStore } from '../../../core/stores/section.store';
|
||||
import { UmbObserverMixin } from '@umbraco-cms/observable-api';
|
||||
import { UmbContextConsumerMixin, UmbContextProviderMixin } from '@umbraco-cms/context-api';
|
||||
import type { ManifestSection } from '@umbraco-cms/models';
|
||||
@@ -112,7 +112,7 @@ export class UmbBackofficeHeaderSections extends UmbContextProviderMixin(
|
||||
<uui-tab
|
||||
@click="${this._handleTabClick}"
|
||||
?active="${this._currentSectionAlias === section.alias}"
|
||||
href="${`/section/${section.meta.pathname}`}"
|
||||
href="${`section/${section.meta.pathname}`}"
|
||||
label="${section.meta.label || section.name}"
|
||||
data-alias="${section.alias}"></uui-tab>
|
||||
`
|
||||
@@ -3,7 +3,7 @@ import { css, CSSResultGroup, html, LitElement } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
|
||||
import './backoffice-header-sections.element';
|
||||
import './backoffice-header-tools.element';
|
||||
import './backoffice-header-apps.element';
|
||||
|
||||
@customElement('umb-backoffice-header')
|
||||
export class UmbBackofficeHeader extends LitElement {
|
||||
@@ -43,11 +43,11 @@ export class UmbBackofficeHeader extends LitElement {
|
||||
return html`
|
||||
<div id="appHeader">
|
||||
<uui-button id="logo" look="primary" label="Umbraco" compact>
|
||||
<img src="/umbraco_logomark_white.svg" alt="Umbraco" />
|
||||
<img src="umbraco_logomark_white.svg" alt="Umbraco" />
|
||||
</uui-button>
|
||||
|
||||
<umb-backoffice-header-sections id="sections"></umb-backoffice-header-sections>
|
||||
<umb-backoffice-header-tools></umb-backoffice-header-tools>
|
||||
<umb-backoffice-header-apps></umb-backoffice-header-apps>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -3,8 +3,8 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { state } from 'lit/decorators.js';
|
||||
import { IRoutingInfo } from 'router-slot';
|
||||
import { UmbSectionStore } from '../../core/stores/section.store';
|
||||
import { UmbSectionContext } from '../sections/section.context';
|
||||
import { UmbSectionStore } from '../../../core/stores/section.store';
|
||||
import { UmbSectionContext } from '../../sections/section.context';
|
||||
import { UmbObserverMixin } from '@umbraco-cms/observable-api';
|
||||
import { createExtensionElement } from '@umbraco-cms/extensions-api';
|
||||
import { UmbContextConsumerMixin, UmbContextProviderMixin } from '@umbraco-cms/context-api';
|
||||
@@ -2,7 +2,7 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, CSSResultGroup, html, LitElement } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { UmbModalHandler, UmbModalService } from '../../core/services/modal';
|
||||
import { UmbModalHandler, UmbModalService } from '@umbraco-cms/services';
|
||||
import { UmbObserverMixin } from '@umbraco-cms/observable-api';
|
||||
import { UmbContextConsumerMixin } from '@umbraco-cms/context-api';
|
||||
|
||||
@@ -2,7 +2,8 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, CSSResultGroup, html, LitElement } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import type { UmbNotificationService, UmbNotificationHandler } from '../../core/services/notification';
|
||||
import type { UmbNotificationHandler } from '../../../core/services/notification';
|
||||
import type { UmbNotificationService } from '@umbraco-cms/services';
|
||||
import { UmbObserverMixin } from '@umbraco-cms/observable-api';
|
||||
import { UmbContextConsumerMixin } from '@umbraco-cms/context-api';
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, CSSResultGroup, html, LitElement } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
|
||||
@customElement('umb-backoffice-header-tools')
|
||||
export class UmbBackofficeHeaderTools extends LitElement {
|
||||
static styles: CSSResultGroup = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
#tools {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--uui-size-space-2);
|
||||
}
|
||||
|
||||
.tool {
|
||||
font-size: 18px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div id="tools">
|
||||
<uui-button class="tool" look="primary" label="Search" compact>
|
||||
<uui-icon name="search"></uui-icon>
|
||||
</uui-button>
|
||||
<uui-button class="tool" look="primary" label="Help" compact>
|
||||
<uui-icon name="favorite"></uui-icon>
|
||||
</uui-button>
|
||||
<uui-button look="primary" style="font-size: 14px;" label="User" compact>
|
||||
<uui-avatar name="Mr Rabbit"></uui-avatar>
|
||||
</uui-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-backoffice-header-tools': UmbBackofficeHeaderTools;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import { css, html, LitElement, nothing } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
|
||||
@customElement('umb-body-layout')
|
||||
export class UmbBodyLayout extends LitElement {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
background-color: var(--uui-color-background);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
min-height: 60px;
|
||||
|
||||
background-color: var(--uui-color-surface);
|
||||
border-bottom: 1px solid var(--uui-color-border);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#headline {
|
||||
display: block;
|
||||
margin: 0 var(--uui-size-layout-1);
|
||||
}
|
||||
|
||||
#tabs {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
#main {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: 54px; /* TODO: missing var(--uui-size-18);*/
|
||||
border-top: 1px solid var(--uui-color-border);
|
||||
background-color: var(--uui-color-surface);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
margin: 0 var(--uui-size-layout-1);
|
||||
margin-left: auto;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.shadowRoot?.removeEventListener('slotchange', this._slotChanged);
|
||||
this.shadowRoot?.addEventListener('slotchange', this._slotChanged);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.shadowRoot?.removeEventListener('slotchange', this._slotChanged);
|
||||
}
|
||||
|
||||
private _slotChanged = (e: Event) => {
|
||||
(e.target as any).style.display =
|
||||
(e.target as HTMLSlotElement).assignedNodes({ flatten: true }).length > 0 ? '' : 'none';
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders a headline in the header.
|
||||
* @public
|
||||
* @type {string}
|
||||
* @attr
|
||||
* @default ''
|
||||
*/
|
||||
@property()
|
||||
public headline = '';
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div id="header">
|
||||
${this.headline ? html`<h3 id="headline">${this.headline}</h3>` : nothing}
|
||||
|
||||
<slot name="header"></slot>
|
||||
<slot id="tabs" name="tabs"></slot>
|
||||
</div>
|
||||
<uui-scroll-container id="main">
|
||||
<slot></slot>
|
||||
</uui-scroll-container>
|
||||
<div id="footer">
|
||||
<slot name="footer"></slot>
|
||||
<slot id="actions" name="actions"></slot>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-body-layout': UmbBodyLayout;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
import './editor-layout.element';
|
||||
import './body-layout.element';
|
||||
|
||||
import { Meta, Story } from '@storybook/web-components';
|
||||
import { html } from 'lit-html';
|
||||
|
||||
import type { UmbEditorLayout } from './editor-layout.element';
|
||||
import type { UmbBodyLayout } from './body-layout.element';
|
||||
|
||||
export default {
|
||||
title: 'Editors/Shared/Editor Layout',
|
||||
component: 'umb-editor-layout',
|
||||
id: 'umb-editor-layout',
|
||||
title: 'Workspaces/Shared/Workspace Layout',
|
||||
component: 'umb-body-layout',
|
||||
id: 'umb-body-layout',
|
||||
} as Meta;
|
||||
|
||||
export const AAAOverview: Story<UmbEditorLayout> = () => html` <umb-editor-layout>
|
||||
export const AAAOverview: Story<UmbBodyLayout> = () => html` <umb-body-layout>
|
||||
<div slot="header"><uui-button color="" look="placeholder">Header slot</uui-button></div>
|
||||
<uui-button color="" look="placeholder">Main slot</uui-button>
|
||||
<div slot="footer"><uui-button color="" look="placeholder">Footer slot</uui-button></div>
|
||||
</umb-editor-layout>`;
|
||||
</umb-body-layout>`;
|
||||
AAAOverview.storyName = 'Overview';
|
||||
@@ -4,17 +4,16 @@ import { ifDefined } from 'lit-html/directives/if-defined.js';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { EMPTY, of, switchMap } from 'rxjs';
|
||||
|
||||
import { UmbDataTypeStore } from '../../../core/stores/data-type/data-type.store';
|
||||
import { NodeProperty } from '../../../core/mocks/data/node.data';
|
||||
import { UmbDataTypesStore } from '../../../core/stores/data-types/data-types.store';
|
||||
import { UmbObserverMixin } from '@umbraco-cms/observable-api';
|
||||
import type { ManifestTypes } from '@umbraco-cms/models';
|
||||
import type { ContentProperty, ManifestTypes } from '@umbraco-cms/models';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry';
|
||||
import { UmbContextConsumerMixin } from '@umbraco-cms/context-api';
|
||||
|
||||
import '../entity-property/entity-property.element';
|
||||
|
||||
@customElement('umb-node-property')
|
||||
export class UmbNodePropertyElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) {
|
||||
@customElement('umb-content-property')
|
||||
export class UmbContentPropertyElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
@@ -24,12 +23,12 @@ export class UmbNodePropertyElement extends UmbContextConsumerMixin(UmbObserverM
|
||||
`,
|
||||
];
|
||||
|
||||
private _property?: NodeProperty;
|
||||
private _property?: ContentProperty;
|
||||
@property({ type: Object, attribute: false })
|
||||
public get property(): NodeProperty | undefined {
|
||||
public get property(): ContentProperty | undefined {
|
||||
return this._property;
|
||||
}
|
||||
public set property(value: NodeProperty | undefined) {
|
||||
public set property(value: ContentProperty | undefined) {
|
||||
this._property = value;
|
||||
this._observeDataType();
|
||||
}
|
||||
@@ -43,7 +42,7 @@ export class UmbNodePropertyElement extends UmbContextConsumerMixin(UmbObserverM
|
||||
@state()
|
||||
private _dataTypeData?: any;
|
||||
|
||||
private _dataTypeStore?: UmbDataTypeStore;
|
||||
private _dataTypeStore?: UmbDataTypesStore;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -86,6 +85,6 @@ export class UmbNodePropertyElement extends UmbContextConsumerMixin(UmbObserverM
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-node-property': UmbNodePropertyElement;
|
||||
'umb-content-property': UmbContentPropertyElement;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { UmbContextConsumerMixin } from '@umbraco-cms/context-api';
|
||||
import type { ManifestPropertyEditorUI, ManifestTypes } from '@umbraco-cms/models';
|
||||
|
||||
import '../../property-actions/shared/property-action-menu/property-action-menu.element';
|
||||
import '../../editors/shared/editor-property-layout/editor-property-layout.element';
|
||||
import '../../workspaces/shared/workspace-property-layout/workspace-property-layout.element';
|
||||
|
||||
/**
|
||||
* @element umb-entity-property
|
||||
@@ -174,11 +174,10 @@ export class UmbEntityPropertyElement extends UmbContextConsumerMixin(UmbObserve
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<umb-editor-property-layout id="layout" label="${this.label}" description="${this.description}">
|
||||
<umb-workspace-property-layout id="layout" label="${this.label}" description="${this.description}">
|
||||
${this._renderPropertyActionMenu()}
|
||||
<div slot="editor">${this._element}</div>
|
||||
</umb-editor-property-layout>
|
||||
<hr />
|
||||
</umb-workspace-property-layout>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import { LitElement, nothing } from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { map } from 'rxjs';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { ManifestTypes, umbExtensionsRegistry } from '@umbraco-cms/extensions-registry';
|
||||
import { UmbObserverMixin } from '@umbraco-cms/observable-api';
|
||||
import { createExtensionElement } from '@umbraco-cms/extensions-api';
|
||||
|
||||
type InitializedExtensionItem = {alias: string, weight: number, component: HTMLElement|null}
|
||||
|
||||
/**
|
||||
* @element umb-extension-slot
|
||||
* @description
|
||||
* @slot default - slot for inserting additional things into this slot.
|
||||
* @export
|
||||
* @class UmbExtensionSlot
|
||||
* @extends {UmbObserverMixin(LitElement)}
|
||||
*/
|
||||
@customElement('umb-extension-slot')
|
||||
export class UmbExtensionSlotElement extends UmbObserverMixin(LitElement) {
|
||||
|
||||
@state()
|
||||
private _extensions:InitializedExtensionItem[] = [];
|
||||
|
||||
@property({ type: String })
|
||||
public type= "";
|
||||
|
||||
@property({ type: Object, attribute: false })
|
||||
public filter: (manifest:ManifestTypes) => boolean = () => true;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
/*
|
||||
this.extensionManager = new ExtensionManager(this, (x) => {x.meta.entityType === this.entityType}, (extensionManifests) => {
|
||||
this._createElement(extensionManifests[0]);
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this._observeExtensions();
|
||||
}
|
||||
|
||||
private _observeExtensions() {
|
||||
this.observe(
|
||||
umbExtensionsRegistry
|
||||
?.extensionsOfType(this.type)
|
||||
.pipe(map((extensions) => extensions.filter(this.filter))),
|
||||
async (extensions: ManifestTypes[]) => {
|
||||
|
||||
const oldLength = this._extensions.length;
|
||||
this._extensions = this._extensions.filter(current => extensions.find(incoming => incoming.alias === current.alias));
|
||||
if(this._extensions.length !== oldLength) {
|
||||
this.requestUpdate('_extensions');
|
||||
}
|
||||
|
||||
extensions.forEach(async (extension: ManifestTypes) => {
|
||||
|
||||
const hasExt = this._extensions.find(x => x.alias === extension.alias);
|
||||
if(!hasExt) {
|
||||
const extensionObject:InitializedExtensionItem = {alias: extension.alias, weight: (extension as any).weight || 0, component: null};
|
||||
this._extensions.push(extensionObject);
|
||||
const component = await createExtensionElement(extension);
|
||||
if(component) {
|
||||
|
||||
(component as any).manifest = extension;
|
||||
extensionObject.component = component;
|
||||
|
||||
// sort:
|
||||
// TODO: Make sure its right to have highest last?
|
||||
this._extensions.sort((a, b) => a.weight - b.weight);
|
||||
} else {
|
||||
// Remove cause we could not get the component, so we will get rid of this.
|
||||
//this._extensions.splice(this._extensions.indexOf(extensionObject), 1);
|
||||
// Actually not, because if, then the same extension would come around again in next update.
|
||||
}
|
||||
this.requestUpdate('_extensions');
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
// TODO: check if we can use repeat directly.
|
||||
return repeat(this._extensions, (ext) => ext.alias, (ext) => ext.component || nothing);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-extension-slot': UmbExtensionSlotElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { html, LitElement } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import { UUIModalSidebarSize } from '@umbraco-ui/uui-modal-sidebar';
|
||||
import { UmbPickerData } from '../../../core/services/modal/layouts/modal-layout-picker-base';
|
||||
import { UmbContextConsumerMixin } from '@umbraco-cms/context-api';
|
||||
|
||||
//TODO: These should probably be imported dynamically.
|
||||
import '../../../core/services/modal/layouts/picker-section/picker-layout-section.element';
|
||||
import '../../../core/services/modal/layouts/picker-user-group/picker-layout-user-group.element';
|
||||
import '../../../core/services/modal/layouts/picker-user/picker-layout-user.element';
|
||||
import { UmbModalService, UmbModalType } from '@umbraco-cms/services';
|
||||
|
||||
/** TODO: Make use of UUI FORM Mixin, to make it easily take part of a form. */
|
||||
export class UmbInputListBase extends UmbContextConsumerMixin(LitElement) {
|
||||
|
||||
@property({ type: Array })
|
||||
public value: Array<string> = [];
|
||||
|
||||
@property({ type: Boolean })
|
||||
public multiple = true;
|
||||
|
||||
@property({ type: String })
|
||||
public modalType: UmbModalType = 'sidebar';
|
||||
|
||||
@property({ type: String })
|
||||
public modalSize: UUIModalSidebarSize = 'small';
|
||||
|
||||
protected pickerLayout?: string;
|
||||
private _modalService?: UmbModalService;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.consumeContext('umbModalService', (modalService: UmbModalService) => {
|
||||
this._modalService = modalService;
|
||||
});
|
||||
}
|
||||
|
||||
private _openPicker() {
|
||||
if (!this.pickerLayout) return;
|
||||
|
||||
const modalHandler = this._modalService?.open(this.pickerLayout, {
|
||||
type: this.modalType,
|
||||
size: this.modalSize,
|
||||
data: {
|
||||
multiple: this.multiple,
|
||||
selection: this.value,
|
||||
},
|
||||
});
|
||||
modalHandler?.onClose().then((data: UmbPickerData<string>) => {
|
||||
if (data) {
|
||||
this.value = data.selection;
|
||||
this.selectionUpdated();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected removeFromSelection(key: string) {
|
||||
this.value = this.value.filter((k) => k !== key);
|
||||
this.selectionUpdated();
|
||||
}
|
||||
|
||||
protected selectionUpdated() {
|
||||
// override this method to react to selection changes
|
||||
}
|
||||
|
||||
protected renderButton() {
|
||||
return html`<uui-button id="add-button" look="placeholder" @click=${this._openPicker} label="open">
|
||||
Add
|
||||
</uui-button>`;
|
||||
}
|
||||
protected renderContent() {
|
||||
return html``;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`${this.renderContent()}${this.renderButton()}`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css';
|
||||
import { css, html, nothing } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { UmbInputListBase } from '../input-list-base/input-list-base';
|
||||
import type { ManifestSection } from '@umbraco-cms/models';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry';
|
||||
|
||||
@customElement('umb-input-section')
|
||||
export class UmbInputPickerSectionElement extends UmbInputListBase {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--uui-size-space-4);
|
||||
}
|
||||
#user-group-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--uui-size-space-4);
|
||||
}
|
||||
.user-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--uui-size-space-2);
|
||||
}
|
||||
.user-group div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--uui-size-4);
|
||||
}
|
||||
.user-group uui-button {
|
||||
margin-left: auto;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@state()
|
||||
private _sections: Array<ManifestSection> = [];
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.pickerLayout = 'umb-picker-layout-section';
|
||||
this._observeSections();
|
||||
}
|
||||
|
||||
private _observeSections() {
|
||||
if (this.value.length > 0) {
|
||||
umbExtensionsRegistry.extensionsOfType('section').subscribe((sections: Array<ManifestSection>) => {
|
||||
this._sections = sections.filter((section) => this.value.includes(section.alias));
|
||||
});
|
||||
} else {
|
||||
this._sections = [];
|
||||
}
|
||||
}
|
||||
|
||||
selectionUpdated() {
|
||||
this._observeSections();
|
||||
this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true }));
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
if (this._sections.length === 0) return html`${nothing}`;
|
||||
|
||||
return html`
|
||||
<div id="user-list">
|
||||
${this._sections.map(
|
||||
(section) => html`
|
||||
<div class="user-group">
|
||||
<div>
|
||||
<span>${section.meta.label}</span>
|
||||
</div>
|
||||
<uui-button
|
||||
@click=${() => this.removeFromSelection(section.alias)}
|
||||
label="remove"
|
||||
color="danger"></uui-button>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-input-section': UmbInputPickerSectionElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
//TODO: Test has been commented out while we figure out how to setup import maps for the test environment
|
||||
// import { UmbPickerSectionElement } from './picker-section.element';
|
||||
// import { defaultA11yConfig } from '@umbraco-cms/test-utils';
|
||||
|
||||
// describe('UmbPickerSectionElement', () => {
|
||||
// let element: UmbPickerSectionElement;
|
||||
// beforeEach(async () => {
|
||||
// element = await fixture(html`<umb-input-section></umb-input-section>`);
|
||||
// });
|
||||
|
||||
// it('is defined with its own instance', () => {
|
||||
// expect(element).to.be.instanceOf(UmbPickerSectionElement);
|
||||
// });
|
||||
|
||||
// it('passes the a11y audit', async () => {
|
||||
// await expect(element).shadowDom.to.be.accessible(defaultA11yConfig);
|
||||
// });
|
||||
// });
|
||||
@@ -0,0 +1,100 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css';
|
||||
import { css, html, nothing } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { UmbInputListBase } from '../input-list-base/input-list-base';
|
||||
import type { UserGroupEntity } from '@umbraco-cms/models';
|
||||
import { UmbObserverMixin } from '@umbraco-cms/observable-api';
|
||||
import { UmbUserGroupStore } from '@umbraco-cms/stores/user/user-group.store';
|
||||
|
||||
@customElement('umb-input-user-group')
|
||||
export class UmbInputPickerUserGroupElement extends UmbObserverMixin(UmbInputListBase) {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--uui-size-space-4);
|
||||
}
|
||||
#user-group-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--uui-size-space-4);
|
||||
}
|
||||
.user-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--uui-size-space-2);
|
||||
}
|
||||
.user-group div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--uui-size-4);
|
||||
}
|
||||
.user-group uui-button {
|
||||
margin-left: auto;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@state()
|
||||
private _userGroups: Array<UserGroupEntity> = [];
|
||||
|
||||
private _userGroupStore?: UmbUserGroupStore;
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.pickerLayout = 'umb-picker-layout-user-group';
|
||||
this.consumeContext('umbUserGroupStore', (usersContext: UmbUserGroupStore) => {
|
||||
this._userGroupStore = usersContext;
|
||||
this._observeUserGroups();
|
||||
});
|
||||
}
|
||||
|
||||
private _observeUserGroups() {
|
||||
if (this.value.length > 0 && this._userGroupStore) {
|
||||
this.observe<Array<UserGroupEntity>>(
|
||||
this._userGroupStore.getByKeys(this.value),
|
||||
(userGroups) => (this._userGroups = userGroups)
|
||||
);
|
||||
} else {
|
||||
this._userGroups = [];
|
||||
}
|
||||
}
|
||||
|
||||
selectionUpdated() {
|
||||
this._observeUserGroups();
|
||||
this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true }));
|
||||
}
|
||||
|
||||
private _renderUserGroupList() {
|
||||
if (this._userGroups.length === 0) return nothing;
|
||||
|
||||
return html`<div id="user-list">
|
||||
${this._userGroups.map(
|
||||
(userGroup) => html`
|
||||
<div class="user-group">
|
||||
<div>
|
||||
<uui-icon .name=${userGroup.icon}></uui-icon>
|
||||
<span>${userGroup.name}</span>
|
||||
</div>
|
||||
<uui-button
|
||||
@click=${() => this.removeFromSelection(userGroup.key)}
|
||||
label="remove"
|
||||
color="danger"></uui-button>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div> `;
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
return html`${this._renderUserGroupList()}`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-input-user-group': UmbInputPickerUserGroupElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
//TODO: Test has been commented out while we figure out how to setup import maps for the test environment
|
||||
// import { UmbPickerUserGroupElement } from './picker-user-group.element';
|
||||
// import { defaultA11yConfig } from '@umbraco-cms/test-utils';
|
||||
|
||||
// describe('UmbPickerLayoutUserGroupElement', () => {
|
||||
// let element: UmbPickerUserGroupElement;
|
||||
// beforeEach(async () => {
|
||||
// element = await fixture(html`<umb-input-user-group></umb-input-user-group>`);
|
||||
// });
|
||||
|
||||
// it('is defined with its own instance', () => {
|
||||
// expect(element).to.be.instanceOf(UmbPickerUserGroupElement);
|
||||
// });
|
||||
|
||||
// it('passes the a11y audit', async () => {
|
||||
// await expect(element).shadowDom.to.be.accessible(defaultA11yConfig);
|
||||
// });
|
||||
// });
|
||||
@@ -0,0 +1,94 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css';
|
||||
import { css, html, nothing, PropertyValueMap } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { UmbInputListBase } from '../input-list-base/input-list-base';
|
||||
import { UmbObserverMixin } from '@umbraco-cms/observable-api';
|
||||
import type { UserEntity } from '@umbraco-cms/models';
|
||||
import { UmbUserStore } from '@umbraco-cms/stores/user/user.store';
|
||||
|
||||
@customElement('umb-input-user')
|
||||
export class UmbPickerUserElement extends UmbObserverMixin(UmbInputListBase) {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--uui-size-space-4);
|
||||
}
|
||||
#user-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--uui-size-space-4);
|
||||
}
|
||||
.user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--uui-size-space-2);
|
||||
}
|
||||
.user uui-button {
|
||||
margin-left: auto;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@state()
|
||||
private _users: Array<UserEntity> = [];
|
||||
|
||||
private _userStore?: UmbUserStore;
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.pickerLayout = 'umb-picker-layout-user';
|
||||
this.consumeContext('umbUserStore', (userStore: UmbUserStore) => {
|
||||
this._userStore = userStore;
|
||||
this._observeUser();
|
||||
});
|
||||
}
|
||||
|
||||
protected updated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
|
||||
super.updated(_changedProperties);
|
||||
if (_changedProperties.has('value')) {
|
||||
this._observeUser(); // TODO: This works, but it makes the value change twice.
|
||||
}
|
||||
}
|
||||
|
||||
private _observeUser() {
|
||||
if (!this._userStore) return;
|
||||
|
||||
this.observe<Array<UserEntity>>(this._userStore.getByKeys(this.value), (users) => {
|
||||
this._users = users;
|
||||
});
|
||||
}
|
||||
|
||||
selectionUpdated() {
|
||||
this._observeUser();
|
||||
this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true }));
|
||||
}
|
||||
|
||||
private _renderUserList() {
|
||||
if (this._users.length === 0) return nothing;
|
||||
|
||||
return html`<div id="user-list">
|
||||
${this._users.map(
|
||||
(user) => html`
|
||||
<div class="user">
|
||||
<uui-avatar .name=${user.name}></uui-avatar>
|
||||
<div>${user.name}</div>
|
||||
<uui-button @click=${() => this.removeFromSelection(user.key)} label="remove" color="danger"></uui-button>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div> `;
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
return html`${this._renderUserList()}`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-input-user': UmbPickerUserElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
//TODO: Test has been commented out while we figure out how to setup import maps for the test environment
|
||||
// import { UmbPickerUserElement } from './picker-user.element';
|
||||
// import { defaultA11yConfig } from '@umbraco-cms/test-utils';
|
||||
|
||||
// describe('UmbPickerUserElement', () => {
|
||||
// let element: UmbPickerUserElement;
|
||||
// beforeEach(async () => {
|
||||
// element = await fixture(html`<umb-input-user></umb-input-user>`);
|
||||
// });
|
||||
|
||||
// it('is defined with its own instance', () => {
|
||||
// expect(element).to.be.instanceOf(UmbPickerUserElement);
|
||||
// });
|
||||
|
||||
// it('passes the a11y audit', async () => {
|
||||
// await expect(element).shadowDom.to.be.accessible(defaultA11yConfig);
|
||||
// });
|
||||
// });
|
||||
@@ -0,0 +1,53 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
|
||||
/**
|
||||
* A simple styled box for showing code-based error messages.
|
||||
* @slot the full message
|
||||
*
|
||||
*/
|
||||
@customElement('uui-code-block')
|
||||
export class UUICodeBlock extends LitElement {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
#container {
|
||||
border: 1px solid var(--uui-color-divider-emphasis);
|
||||
color: var(--uui-color-text-alt);
|
||||
background-color: var(--uui-color-divider-standalone);
|
||||
padding: var(--uui-size-space-2);
|
||||
border-radius: var(--uui-border-radius);
|
||||
line-height: var(--uui-size-10);
|
||||
}
|
||||
:host uui-scroll-container {
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
render() {
|
||||
return html`<div id="container">
|
||||
<uui-scroll-container>
|
||||
<pre>
|
||||
<code>
|
||||
<slot></slot>
|
||||
</code>
|
||||
</pre>
|
||||
</uui-scroll-container>
|
||||
</div> `;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'uui-code-block': UUICodeBlock;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './table.element';
|
||||
@@ -48,17 +48,24 @@ export class UmbDashboardExamineManagementElement extends UmbContextConsumerMixi
|
||||
@state()
|
||||
private _currentPath?: string;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
private _onRouteChange() {
|
||||
this._currentPath = path();
|
||||
}
|
||||
|
||||
private get backbutton(): boolean {
|
||||
return this._currentPath != '/section/settings/dashboard/examine-management/' || !this._currentPath ? true : false;
|
||||
return !(this._currentPath?.endsWith('examine-management/'));
|
||||
}
|
||||
|
||||
render() {
|
||||
return html` ${this.backbutton
|
||||
? html` <a href="/section/settings/dashboard/examine-management/"> ← Back to overview </a> `
|
||||
? html` <a href="section/settings/dashboard/examine-management"> ← Back to overview </a> `
|
||||
: nothing}
|
||||
<router-slot @changestate="${this._onRouteChange}" .routes=${this._routes}></router-slot>`;
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
export interface SearcherModel {
|
||||
name: string;
|
||||
providerProperties: unknown; //TODO
|
||||
}
|
||||
|
||||
export interface IndexModel {
|
||||
name: string;
|
||||
canRebuild: boolean;
|
||||
healthStatus: string;
|
||||
isHealthy: boolean;
|
||||
providerProperties: ProviderPropertiesModel;
|
||||
}
|
||||
|
||||
export interface ProviderPropertiesModel {
|
||||
CommitCount: number;
|
||||
DefaultAnalyzer: string;
|
||||
DocumentCount: number;
|
||||
FieldCount: number;
|
||||
LuceneDirectory: string;
|
||||
LuceneIndexFolder: string;
|
||||
DirectoryFactory: string;
|
||||
EnableDefaultEventHandler: boolean;
|
||||
PublishedValuesOnly: boolean;
|
||||
SupportProtectedContent: boolean;
|
||||
IncludeFields?: string[];
|
||||
}
|
||||
|
||||
export interface FieldViewModel {
|
||||
name: string;
|
||||
values: string[];
|
||||
}
|
||||
|
||||
export interface SearchResultsModel {
|
||||
id: number;
|
||||
name: string;
|
||||
fields: FieldViewModel[];
|
||||
score: number;
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import { html, css } from 'lit';
|
||||
import { html, css, nothing } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import { UmbModalLayoutElement } from '@umbraco-cms/services';
|
||||
import { SearchResultsModel } from 'src/backoffice/dashboards/examine-management/examine-extension';
|
||||
import { SearchResult } from '@umbraco-cms/backend-api';
|
||||
|
||||
@customElement('umb-modal-layout-fields-viewer')
|
||||
export class UmbModalLayoutFieldsViewerElement extends UmbModalLayoutElement<SearchResultsModel> {
|
||||
export class UmbModalLayoutFieldsViewerElement extends UmbModalLayoutElement<SearchResult & { name: string }> {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
@@ -47,31 +47,31 @@ export class UmbModalLayoutFieldsViewerElement extends UmbModalLayoutElement<Sea
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.data) {
|
||||
return html`
|
||||
<uui-dialog-layout class="uui-text" headline="${this.data.name}">
|
||||
<uui-scroll-container id="field-viewer">
|
||||
<span>
|
||||
<uui-table>
|
||||
<uui-table-head>
|
||||
<uui-table-head-cell> Field </uui-table-head-cell>
|
||||
<uui-table-head-cell> Value </uui-table-head-cell>
|
||||
</uui-table-head>
|
||||
${Object.values(this.data.fields).map((cell) => {
|
||||
return html`<uui-table-row>
|
||||
<uui-table-cell> ${cell.name} </uui-table-cell>
|
||||
<uui-table-cell> ${cell.values.join(', ')} </uui-table-cell>
|
||||
</uui-table-row>`;
|
||||
})}
|
||||
</uui-table>
|
||||
</span>
|
||||
</uui-scroll-container>
|
||||
<div>
|
||||
<uui-button look="primary" @click="${this._handleClose}">Close</uui-button>
|
||||
</div>
|
||||
</uui-dialog-layout>
|
||||
`;
|
||||
} else return html``;
|
||||
if (!this.data) return nothing;
|
||||
|
||||
return html`
|
||||
<uui-dialog-layout class="uui-text" headline="${this.data.name}">
|
||||
<uui-scroll-container id="field-viewer">
|
||||
<span>
|
||||
<uui-table>
|
||||
<uui-table-head>
|
||||
<uui-table-head-cell> Field </uui-table-head-cell>
|
||||
<uui-table-head-cell> Value </uui-table-head-cell>
|
||||
</uui-table-head>
|
||||
${Object.values(this.data.fields ?? []).map((cell) => {
|
||||
return html`<uui-table-row>
|
||||
<uui-table-cell> ${cell.name} </uui-table-cell>
|
||||
<uui-table-cell> ${cell.values?.join(', ')} </uui-table-cell>
|
||||
</uui-table-row>`;
|
||||
})}
|
||||
</uui-table>
|
||||
</span>
|
||||
</uui-scroll-container>
|
||||
<div>
|
||||
<uui-button look="primary" @click="${this._handleClose}">Close</uui-button>
|
||||
</div>
|
||||
</uui-dialog-layout>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { customElement, state, property } from 'lit/decorators.js';
|
||||
import {UUITextStyles} from '@umbraco-ui/uui-css/lib';
|
||||
import {css, html, LitElement, nothing} from 'lit';
|
||||
import {customElement, property, state} from 'lit/decorators.js';
|
||||
|
||||
import { UUIButtonState } from '@umbraco-ui/uui-button';
|
||||
import {UUIButtonState} from '@umbraco-ui/uui-button';
|
||||
|
||||
import { UmbModalService, UmbNotificationService, UmbNotificationDefaultData } from '@umbraco-cms/services';
|
||||
import {UmbModalService, UmbNotificationDefaultData, UmbNotificationService} from '@umbraco-cms/services';
|
||||
|
||||
import { UmbContextConsumerMixin } from '@umbraco-cms/context-api';
|
||||
import {UmbContextConsumerMixin} from '@umbraco-cms/context-api';
|
||||
import './section-view-examine-searchers';
|
||||
|
||||
import { ApiError, ProblemDetails, Index, SearchResource } from '@umbraco-cms/backend-api';
|
||||
import {ApiError, Index, IndexerResource, ProblemDetails} from '@umbraco-cms/backend-api';
|
||||
|
||||
@customElement('umb-dashboard-examine-index')
|
||||
export class UmbDashboardExamineIndexElement extends UmbContextConsumerMixin(LitElement) {
|
||||
@@ -86,24 +86,14 @@ export class UmbDashboardExamineIndexElement extends UmbContextConsumerMixin(Lit
|
||||
private _buttonState?: UUIButtonState = undefined;
|
||||
|
||||
@state()
|
||||
private _indexData!: Index;
|
||||
private _indexData?: Index;
|
||||
|
||||
@state()
|
||||
private _loading = true;
|
||||
|
||||
private _notificationService?: UmbNotificationService;
|
||||
private _modalService?: UmbModalService;
|
||||
|
||||
private async _getIndexData() {
|
||||
try {
|
||||
const index = await SearchResource.getSearchIndexByIndexName({ indexName: this.indexName });
|
||||
this._indexData = index;
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) {
|
||||
const error = e as ProblemDetails;
|
||||
const data: UmbNotificationDefaultData = { message: error.message ?? 'Could not fetch index' };
|
||||
this._notificationService?.peek('danger', { data });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@@ -113,9 +103,25 @@ export class UmbDashboardExamineIndexElement extends UmbContextConsumerMixin(Lit
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
private async _getIndexData() {
|
||||
try {
|
||||
this._indexData = await IndexerResource.getIndexerByIndexName({indexName: this.indexName});
|
||||
if (!this._indexData?.isHealthy) {
|
||||
this._buttonState = 'waiting';
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) {
|
||||
const error = e as ProblemDetails;
|
||||
const data: UmbNotificationDefaultData = { message: error.message ?? 'Could not fetch index' };
|
||||
this._notificationService?.peek('danger', { data });
|
||||
}
|
||||
}
|
||||
this._loading = false;
|
||||
}
|
||||
|
||||
async connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._getIndexData();
|
||||
await this._getIndexData();
|
||||
}
|
||||
|
||||
private async _onRebuildHandler() {
|
||||
@@ -136,45 +142,56 @@ export class UmbDashboardExamineIndexElement extends UmbContextConsumerMixin(Lit
|
||||
}
|
||||
private async _rebuild() {
|
||||
this._buttonState = 'waiting';
|
||||
if (this._indexData.name)
|
||||
try {
|
||||
await SearchResource.postSearchIndexByIndexNameRebuild({ indexName: this._indexData.name });
|
||||
this._buttonState = 'success';
|
||||
} catch (e) {
|
||||
this._buttonState = 'failed';
|
||||
if (e instanceof ApiError) {
|
||||
const error = e as ProblemDetails;
|
||||
const data: UmbNotificationDefaultData = { message: error.message ?? 'Rebuild error' };
|
||||
this._notificationService?.peek('danger', { data });
|
||||
}
|
||||
try {
|
||||
await IndexerResource.postIndexerByIndexNameRebuild({ indexName: this.indexName });
|
||||
this._buttonState = 'success';
|
||||
await this._getIndexData();
|
||||
} catch (e) {
|
||||
this._buttonState = 'failed';
|
||||
if (e instanceof ApiError) {
|
||||
const error = e as ProblemDetails;
|
||||
const data: UmbNotificationDefaultData = { message: error.message ?? 'Rebuild error' };
|
||||
this._notificationService?.peek('danger', { data });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this._indexData) {
|
||||
return html` <uui-box headline="${this.indexName}">
|
||||
<p>
|
||||
<strong>Health Status</strong><br />
|
||||
The health status of the ${this._indexData.name} and if it can be read
|
||||
</p>
|
||||
<div>
|
||||
<uui-icon-essentials>
|
||||
<uui-icon
|
||||
name=${this._indexData.isHealthy ? `check` : `wrong`}
|
||||
class=${this._indexData.isHealthy ? 'positive' : 'danger'}>
|
||||
</uui-icon>
|
||||
</uui-icon-essentials>
|
||||
${this._indexData.healthStatus}
|
||||
</div>
|
||||
</uui-box>
|
||||
<umb-dashboard-examine-searcher searcherName="${this.indexName}"></umb-dashboard-examine-searcher>
|
||||
${this.renderPropertyList()} ${this.renderTools()}`;
|
||||
} else return html``;
|
||||
if (!this._indexData || this._loading) return html`
|
||||
<uui-loader-bar></uui-loader-bar>`;
|
||||
|
||||
return html`
|
||||
<uui-box headline="${this.indexName}">
|
||||
<p>
|
||||
<strong>Health Status</strong><br/>
|
||||
The health status of the ${this.indexName} and if it can be read
|
||||
</p>
|
||||
<div>
|
||||
<uui-icon-essentials>
|
||||
<uui-icon
|
||||
name=${this._indexData.isHealthy ? `check` : `wrong`}
|
||||
class=${this._indexData.isHealthy ? 'positive' : 'danger'}>
|
||||
</uui-icon>
|
||||
</uui-icon-essentials>
|
||||
${this._indexData.healthStatus}
|
||||
</div>
|
||||
</uui-box>
|
||||
${this.renderIndexSearch()}
|
||||
${this.renderPropertyList()}
|
||||
${this.renderTools()}
|
||||
`;
|
||||
}
|
||||
|
||||
private renderIndexSearch() {
|
||||
if (!this._indexData || !this._indexData.isHealthy) return nothing;
|
||||
return html`<umb-dashboard-examine-searcher searcherName="${this.indexName}"></umb-dashboard-examine-searcher>`;
|
||||
}
|
||||
|
||||
private renderPropertyList() {
|
||||
if (!this._indexData) return nothing;
|
||||
|
||||
return html`<uui-box headline="Index info">
|
||||
<p>Lists the properties of the ${this._indexData.name}</p>
|
||||
<p>Lists the properties of the ${this.indexName}</p>
|
||||
<uui-table class="info">
|
||||
<uui-table-row>
|
||||
<uui-table-cell style="width:0px; font-weight: bold;"> documentCount </uui-table-cell>
|
||||
@@ -187,7 +204,7 @@ export class UmbDashboardExamineIndexElement extends UmbContextConsumerMixin(Lit
|
||||
${this._indexData.providerProperties
|
||||
? Object.entries(this._indexData.providerProperties).map((entry) => {
|
||||
return html`<uui-table-row>
|
||||
<uui-table-cell style="width:0px; font-weight: bold;"> ${entry[0]} </uui-table-cell>
|
||||
<uui-table-cell style="width:0; font-weight: bold;"> ${entry[0]} </uui-table-cell>
|
||||
<uui-table-cell clip-text> ${entry[1]} </uui-table-cell>
|
||||
</uui-table-row>`;
|
||||
})
|
||||
@@ -198,13 +215,13 @@ export class UmbDashboardExamineIndexElement extends UmbContextConsumerMixin(Lit
|
||||
|
||||
private renderTools() {
|
||||
return html` <uui-box headline="Tools">
|
||||
<p>Tools to manage the ${this._indexData.name}</p>
|
||||
<p>Tools to manage the ${this.indexName}</p>
|
||||
<uui-button
|
||||
color="danger"
|
||||
look="primary"
|
||||
.state="${this._buttonState}"
|
||||
@click="${this._onRebuildHandler}"
|
||||
.disabled="${!this._indexData?.canRebuild}"
|
||||
.disabled="${!this._indexData?.canRebuild ?? true}"
|
||||
label="Rebuild index">
|
||||
Rebuild
|
||||
</uui-button>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { css, html, LitElement, nothing } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
|
||||
import { UmbModalService, UmbNotificationService, UmbNotificationDefaultData } from '@umbraco-cms/services';
|
||||
import { UmbNotificationService, UmbNotificationDefaultData } from '@umbraco-cms/services';
|
||||
|
||||
import { UmbContextConsumerMixin } from '@umbraco-cms/context-api';
|
||||
import { ApiError, ProblemDetails, Searcher, Index, SearchResource } from '@umbraco-cms/backend-api';
|
||||
import { ApiError, ProblemDetails, Searcher, Index, IndexerResource, SearcherResource } from '@umbraco-cms/backend-api';
|
||||
|
||||
@customElement('umb-dashboard-examine-overview')
|
||||
export class UmbDashboardExamineOverviewElement extends UmbContextConsumerMixin(LitElement) {
|
||||
@@ -58,12 +58,32 @@ export class UmbDashboardExamineOverviewElement extends UmbContextConsumerMixin(
|
||||
@state()
|
||||
private _searchers?: Searcher[];
|
||||
|
||||
@state()
|
||||
private _loadingIndexers = false;
|
||||
|
||||
@state()
|
||||
private _loadingSearchers = false;
|
||||
|
||||
private _notificationService?: UmbNotificationService;
|
||||
private _modalService?: UmbModalService;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.consumeAllContexts(['umbNotificationService'], (instances) => {
|
||||
this._notificationService = instances['umbNotificationService'];
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._getIndexers();
|
||||
this._getSearchers();
|
||||
}
|
||||
|
||||
private async _getIndexers() {
|
||||
this._loadingIndexers = true;
|
||||
try {
|
||||
const indexers = await SearchResource.getSearchIndex({ take: 9999, skip: 0 });
|
||||
const indexers = await IndexerResource.getIndexer({ take: 9999, skip: 0 });
|
||||
this._indexers = indexers.items;
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) {
|
||||
@@ -72,11 +92,13 @@ export class UmbDashboardExamineOverviewElement extends UmbContextConsumerMixin(
|
||||
this._notificationService?.peek('danger', { data });
|
||||
}
|
||||
}
|
||||
this._loadingIndexers = false;
|
||||
}
|
||||
|
||||
private async _getSearchers() {
|
||||
this._loadingSearchers = true;
|
||||
try {
|
||||
const searchers = await SearchResource.getSearchSearcher({ take: 9999, skip: 0 });
|
||||
const searchers = await SearcherResource.getSearcher({ take: 9999, skip: 0 });
|
||||
this._searchers = searchers.items;
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) {
|
||||
@@ -85,17 +107,7 @@ export class UmbDashboardExamineOverviewElement extends UmbContextConsumerMixin(
|
||||
this._notificationService?.peek('danger', { data });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._getIndexers();
|
||||
this._getSearchers();
|
||||
|
||||
this.consumeAllContexts(['umbNotificationService', 'umbModalService'], (instances) => {
|
||||
this._notificationService = instances['umbNotificationService'];
|
||||
this._modalService = instances['umbModalService'];
|
||||
});
|
||||
this._loadingSearchers = false;
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -118,7 +130,8 @@ export class UmbDashboardExamineOverviewElement extends UmbContextConsumerMixin(
|
||||
}
|
||||
|
||||
private renderIndexersList() {
|
||||
if (!this._indexers) return;
|
||||
if (this._loadingIndexers) return html`<uui-loader></uui-loader>`;
|
||||
if (!this._indexers) return nothing;
|
||||
return html` <uui-table class="overview">
|
||||
${this._indexers.map((index) => {
|
||||
return html`
|
||||
@@ -142,7 +155,8 @@ export class UmbDashboardExamineOverviewElement extends UmbContextConsumerMixin(
|
||||
}
|
||||
|
||||
private renderSearchersList() {
|
||||
if (!this._searchers) return html`<span class="not-found-message">No searchers were found</span>`;
|
||||
if (this._loadingSearchers) return html`<uui-loader></uui-loader>`;
|
||||
if (!this._searchers) return nothing;
|
||||
return html`
|
||||
<uui-table class="overview2">
|
||||
${this._searchers.map((searcher) => {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { css, html, LitElement, nothing } from 'lit';
|
||||
import { customElement, state, query, property } from 'lit/decorators.js';
|
||||
|
||||
import { UmbModalService, UmbNotificationService, UmbNotificationDefaultData } from '@umbraco-cms/services';
|
||||
|
||||
import { UmbContextConsumerMixin } from '@umbraco-cms/context-api';
|
||||
|
||||
import { ApiError, ProblemDetails, SearchResult, SearchResource, Field } from '@umbraco-cms/backend-api';
|
||||
import { ApiError, ProblemDetails, SearchResult, SearcherResource, Field } from '@umbraco-cms/backend-api';
|
||||
|
||||
import './modal-views/fields-viewer.element';
|
||||
import './modal-views/fields-settings.element';
|
||||
@@ -113,6 +113,9 @@ export class UmbDashboardExamineSearcherElement extends UmbContextConsumerMixin(
|
||||
@state()
|
||||
private _exposedFields?: ExposedSearchResultField[];
|
||||
|
||||
@state()
|
||||
private _searchLoading = false;
|
||||
|
||||
@query('#search-input')
|
||||
private _searchInput!: HTMLInputElement;
|
||||
|
||||
@@ -125,7 +128,7 @@ export class UmbDashboardExamineSearcherElement extends UmbContextConsumerMixin(
|
||||
}
|
||||
|
||||
private _onNameClick() {
|
||||
const data: UmbNotificationDefaultData = { message: 'TODO: Open editor for this' }; // TODO
|
||||
const data: UmbNotificationDefaultData = { message: 'TODO: Open workspace for this' }; // TODO
|
||||
this._notificationService?.peek('warning', { data });
|
||||
}
|
||||
|
||||
@@ -135,11 +138,12 @@ export class UmbDashboardExamineSearcherElement extends UmbContextConsumerMixin(
|
||||
|
||||
private async _onSearch() {
|
||||
if (!this._searchInput.value.length) return;
|
||||
this._searchLoading = true;
|
||||
try {
|
||||
const res = await SearchResource.getSearchSearcherBySearcherNameSearch({
|
||||
const res = await SearcherResource.getSearcherBySearcherNameQuery({
|
||||
searcherName: this.searcherName,
|
||||
query: this._searchInput.value,
|
||||
take: 9999,
|
||||
term: this._searchInput.value,
|
||||
take: 100,
|
||||
skip: 0,
|
||||
});
|
||||
this._searchResults = res.items;
|
||||
@@ -151,6 +155,7 @@ export class UmbDashboardExamineSearcherElement extends UmbContextConsumerMixin(
|
||||
this._notificationService?.peek('danger', { data });
|
||||
}
|
||||
}
|
||||
this._searchLoading = false;
|
||||
}
|
||||
|
||||
private _updateFieldFilter() {
|
||||
@@ -204,59 +209,64 @@ export class UmbDashboardExamineSearcherElement extends UmbContextConsumerMixin(
|
||||
`;
|
||||
}
|
||||
|
||||
// Find the field named 'nodeName' and return its value if it exists in the fields array
|
||||
private getSearchResultNodeName(searchResult: SearchResult): string {
|
||||
const nodeNameField = searchResult.fields?.find((field) => field.name?.toUpperCase() === 'NODENAME');
|
||||
return nodeNameField?.values?.join(', ') ?? '';
|
||||
}
|
||||
|
||||
private renderSearchResults() {
|
||||
if (this._searchResults?.length) {
|
||||
return html`<div class="table-container">
|
||||
<uui-scroll-container>
|
||||
<uui-table class="search">
|
||||
<uui-table-head>
|
||||
<uui-table-head-cell style="width:0">Score</uui-table-head-cell>
|
||||
<uui-table-head-cell style="width:0">Id</uui-table-head-cell>
|
||||
<uui-table-head-cell>Name</uui-table-head-cell>
|
||||
<uui-table-head-cell>Fields</uui-table-head-cell>
|
||||
${this.renderHeadCells()}
|
||||
</uui-table-head>
|
||||
${this._searchResults?.map((rowData) => {
|
||||
return html`<uui-table-row>
|
||||
<uui-table-cell> ${rowData.score} </uui-table-cell>
|
||||
<uui-table-cell> ${rowData.id} </uui-table-cell>
|
||||
<uui-table-cell>
|
||||
<uui-button look="secondary" label="Open editor for this document" @click="${this._onNameClick}">
|
||||
${rowData.fields?.find((field) => {
|
||||
if (field.name?.toUpperCase() === 'NODENAME') return field.values;
|
||||
else return;
|
||||
})?.values}
|
||||
</uui-button>
|
||||
</uui-table-cell>
|
||||
<uui-table-cell>
|
||||
<uui-button
|
||||
class="bright"
|
||||
look="secondary"
|
||||
label="Open sidebar to see all fields"
|
||||
@click="${() =>
|
||||
this._modalService?.open('umb-modal-layout-fields-viewer', {
|
||||
type: 'sidebar',
|
||||
size: 'medium',
|
||||
data: { ...rowData },
|
||||
})}">
|
||||
${rowData.fields ? Object.keys(rowData.fields).length : ''} fields
|
||||
</uui-button>
|
||||
</uui-table-cell>
|
||||
${rowData.fields ? this.renderBodyCells(rowData.fields) : ''}
|
||||
</uui-table-row>`;
|
||||
})}
|
||||
</uui-table>
|
||||
</uui-scroll-container>
|
||||
<button class="field-adder" @click="${this._onFieldFilterClick}">
|
||||
<uui-icon-registry-essential>
|
||||
<uui-tag look="secondary">
|
||||
<uui-icon name="add"></uui-icon>
|
||||
</uui-tag>
|
||||
</uui-icon-registry-essential>
|
||||
</button>
|
||||
</div>`;
|
||||
if (this._searchLoading) return html`<uui-loader></uui-loader>`;
|
||||
if (!this._searchResults) return nothing;
|
||||
if (!this._searchResults.length) {
|
||||
return html`<p>No results found</p>`;
|
||||
}
|
||||
return;
|
||||
return html`<div class="table-container">
|
||||
<uui-scroll-container>
|
||||
<uui-table class="search">
|
||||
<uui-table-head>
|
||||
<uui-table-head-cell style="width:0">Score</uui-table-head-cell>
|
||||
<uui-table-head-cell style="width:0">Id</uui-table-head-cell>
|
||||
<uui-table-head-cell>Name</uui-table-head-cell>
|
||||
<uui-table-head-cell>Fields</uui-table-head-cell>
|
||||
${this.renderHeadCells()}
|
||||
</uui-table-head>
|
||||
${this._searchResults?.map((rowData) => {
|
||||
return html`<uui-table-row>
|
||||
<uui-table-cell> ${rowData.score} </uui-table-cell>
|
||||
<uui-table-cell> ${rowData.id} </uui-table-cell>
|
||||
<uui-table-cell>
|
||||
<uui-button look="secondary" label="Open workspace for this document" @click="${this._onNameClick}">
|
||||
${this.getSearchResultNodeName(rowData)}
|
||||
</uui-button>
|
||||
</uui-table-cell>
|
||||
<uui-table-cell>
|
||||
<uui-button
|
||||
class="bright"
|
||||
look="secondary"
|
||||
label="Open sidebar to see all fields"
|
||||
@click="${() =>
|
||||
this._modalService?.open('umb-modal-layout-fields-viewer', {
|
||||
type: 'sidebar',
|
||||
size: 'medium',
|
||||
data: { ...rowData, name: this.getSearchResultNodeName(rowData) },
|
||||
})}">
|
||||
${rowData.fields ? Object.keys(rowData.fields).length : ''} fields
|
||||
</uui-button>
|
||||
</uui-table-cell>
|
||||
${rowData.fields ? this.renderBodyCells(rowData.fields) : ''}
|
||||
</uui-table-row>`;
|
||||
})}
|
||||
</uui-table>
|
||||
</uui-scroll-container>
|
||||
<button class="field-adder" @click="${this._onFieldFilterClick}">
|
||||
<uui-icon-registry-essential>
|
||||
<uui-tag look="secondary">
|
||||
<uui-icon name="add"></uui-icon>
|
||||
</uui-tag>
|
||||
</uui-icon-registry-essential>
|
||||
</button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
renderHeadCells() {
|
||||
|
||||
@@ -1,18 +1,181 @@
|
||||
import { UUIButtonState } from '@umbraco-ui/uui';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import { css, html, LitElement, nothing } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
|
||||
import { ApiError, ModelsBuilder, ModelsBuilderResource, ModelsMode, ProblemDetails } from '@umbraco-cms/backend-api';
|
||||
import { UmbContextConsumerMixin } from '@umbraco-cms/context-api';
|
||||
import { UmbNotificationDefaultData, UmbNotificationService } from '@umbraco-cms/services';
|
||||
|
||||
@customElement('umb-dashboard-models-builder')
|
||||
export class UmbDashboardModelsBuilderElement extends LitElement {
|
||||
static styles = [UUITextStyles, css``];
|
||||
export class UmbDashboardModelsBuilderElement extends UmbContextConsumerMixin(LitElement) {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
.headline {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.models-description ul {
|
||||
list-style-type: square;
|
||||
margin: 0;
|
||||
padding-left: var(--uui-size-layout-1);
|
||||
}
|
||||
|
||||
span.out-of-date {
|
||||
display: block;
|
||||
padding-block-end: var(--uui-size-space-4);
|
||||
}
|
||||
|
||||
.error {
|
||||
font-weight: bold;
|
||||
color: var(--uui-color-danger);
|
||||
}
|
||||
|
||||
p.models-actions {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
private _notificationService?: UmbNotificationService;
|
||||
|
||||
@state()
|
||||
private _modelsBuilder?: ModelsBuilder;
|
||||
|
||||
@state()
|
||||
private _buttonStateBuild: UUIButtonState = undefined;
|
||||
|
||||
@state()
|
||||
private _buttonStateReload: UUIButtonState = undefined;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._getDashboardData();
|
||||
this.consumeAllContexts(['umbNotificationService'], (instances) => {
|
||||
this._notificationService = instances['umbNotificationService'];
|
||||
});
|
||||
}
|
||||
|
||||
private async _getDashboardData() {
|
||||
try {
|
||||
const modelsBuilder = await ModelsBuilderResource.getModelsBuilderDashboard();
|
||||
this._modelsBuilder = modelsBuilder;
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) {
|
||||
const error = e as ProblemDetails;
|
||||
const data: UmbNotificationDefaultData = {
|
||||
message: error.message ?? 'Something went wrong',
|
||||
};
|
||||
this._notificationService?.peek('danger', { data });
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async _onGenerateModels() {
|
||||
this._buttonStateBuild = 'waiting';
|
||||
const status = await this._postGenerateModels();
|
||||
this._buttonStateBuild = status ? 'success' : 'failed';
|
||||
}
|
||||
|
||||
private async _postGenerateModels() {
|
||||
try {
|
||||
await ModelsBuilderResource.postModelsBuilderBuild();
|
||||
this._getDashboardData();
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) {
|
||||
const error = e as ProblemDetails;
|
||||
const data: UmbNotificationDefaultData = {
|
||||
message: error.message ?? 'Model generation failed',
|
||||
};
|
||||
this._notificationService?.peek('danger', { data });
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async _onDashboardReload() {
|
||||
this._buttonStateReload = 'waiting';
|
||||
const status = await this._getDashboardData();
|
||||
this._buttonStateReload = status ? 'success' : 'failed';
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-box>
|
||||
<h1>Models Builder</h1>
|
||||
<div class="headline">
|
||||
<h1>Models Builder</h1>
|
||||
<uui-button .state="${this._buttonStateReload}" look="secondary" @click="${this._onDashboardReload}">
|
||||
Reload
|
||||
</uui-button>
|
||||
</div>
|
||||
<p>Version: ${this._modelsBuilder?.version}</p>
|
||||
<div class="models-description">
|
||||
<p>ModelsBuilder is enabled with the following configuration:</p>
|
||||
<ul>
|
||||
${this._modelsBuilder?.mode
|
||||
? html`<li>
|
||||
The <strong>ModelsMode</strong> is '${this._modelsBuilder.mode}'. ${this.renderModelsMode()}
|
||||
</li> `
|
||||
: nothing}
|
||||
${this.renderList()}
|
||||
</ul>
|
||||
</div>
|
||||
<p class="models-actions">
|
||||
${this._modelsBuilder?.outOfDateModels
|
||||
? html`<span class="out-of-date">Models are <strong>out-of-date</strong></span>`
|
||||
: nothing}
|
||||
${this._modelsBuilder?.canGenerate
|
||||
? html` <uui-button
|
||||
.state="${this._buttonStateBuild}"
|
||||
look="primary"
|
||||
label="Generate models"
|
||||
@click="${this._onGenerateModels}">
|
||||
Generate models
|
||||
</uui-button>`
|
||||
: nothing}
|
||||
</p>
|
||||
${this._modelsBuilder?.lastError
|
||||
? html`<p class="error">Last generation failed with the following error:</p>
|
||||
<uui-code-block>${this._modelsBuilder.lastError}</uui-code-block>`
|
||||
: nothing}
|
||||
</uui-box>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderList() {
|
||||
if (this._modelsBuilder?.mode !== ModelsMode.NOTHING) {
|
||||
return html`${this._modelsBuilder?.modelsNamespace
|
||||
? html`<li>The <strong>models namespace</strong> is ${this._modelsBuilder.modelsNamespace}.</li>`
|
||||
: nothing}
|
||||
${this._modelsBuilder?.trackingOutOfDateModels === true
|
||||
? html`<li>Tracking of <strong>out-of-date models</strong> is enabled.</li>`
|
||||
: this._modelsBuilder?.trackingOutOfDateModels === false
|
||||
? html`<li>Tracking of <strong>out-of-date models</strong> is not enabled.</li>`
|
||||
: nothing}`;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
renderModelsMode() {
|
||||
switch (this._modelsBuilder?.mode) {
|
||||
case ModelsMode.IN_MEMORY_AUTO:
|
||||
return 'Strongly typed models are re-generated on startup and anytime schema changes (i.e. Content Type) are made. No recompilation necessary but the generated models are not available to code outside of Razor.';
|
||||
case ModelsMode.SOURCE_CODE_MANUAL:
|
||||
return 'Strongly typed models are generated on demand. Recompilation is necessary and models are available to all CSharp code.';
|
||||
case ModelsMode.SOURCE_CODE_AUTO:
|
||||
return 'Strong typed models are generated on demand and anytime schema changes (i.e. Content Type) are made. Recompilation is necessary and models are available to all CSharp code.';
|
||||
case ModelsMode.NOTHING:
|
||||
return 'Strongly typed models are not generated. All content and cache will operate from instance of IPublishedContent only.';
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -1,15 +1,117 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
|
||||
import { ApiError, ProblemDetails, ProfilingResource } from '@umbraco-cms/backend-api';
|
||||
import { UmbContextConsumerMixin } from '@umbraco-cms/context-api';
|
||||
import { UmbNotificationDefaultData, UmbNotificationService } from '@umbraco-cms/services';
|
||||
|
||||
@customElement('umb-dashboard-performance-profiling')
|
||||
export class UmbDashboardPerformanceProfilingElement extends LitElement {
|
||||
static styles = [UUITextStyles, css``];
|
||||
export class UmbDashboardPerformanceProfilingElement extends UmbContextConsumerMixin(LitElement) {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
uui-toggle {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
h4 + p {
|
||||
margin-top: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@state()
|
||||
private _profilingStatus?: boolean;
|
||||
|
||||
@state()
|
||||
private _profilingPerfomance = false;
|
||||
|
||||
private _notificationService?: UmbNotificationService;
|
||||
|
||||
private async _getProfilingStatus() {
|
||||
try {
|
||||
const status = await ProfilingResource.getProfilingStatus();
|
||||
this._profilingStatus = status.enabled;
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) {
|
||||
const error = e as ProblemDetails;
|
||||
const data: UmbNotificationDefaultData = { message: error.message ?? 'Something went wrong' };
|
||||
this._notificationService?.peek('danger', { data });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.consumeAllContexts(['umbNotificationService'], (instances) => {
|
||||
this._notificationService = instances['umbNotificationService'];
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this._getProfilingStatus();
|
||||
this._profilingPerfomance = localStorage.getItem('profilingPerformance') === 'true';
|
||||
}
|
||||
|
||||
private _changeProfilingPerformance() {
|
||||
this._profilingPerfomance = !this._profilingPerfomance;
|
||||
localStorage.setItem('profilingPerformance', this._profilingPerfomance.toString());
|
||||
}
|
||||
|
||||
private renderProfilingStatus() {
|
||||
return this._profilingStatus
|
||||
? html`
|
||||
<p>
|
||||
Umbraco currently runs in debug mode. This means you can use the built-in performance profiler to assess the
|
||||
performance when rendering pages.
|
||||
</p>
|
||||
<p>
|
||||
If you want to activate the profiler for a specific page rendering, simply add
|
||||
<strong>umbDebug=true</strong> to the querystring when requesting the page.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If you want the profiler to be activated by default for all page renderings, you can use the toggle below.
|
||||
It will set a cookie in your browser, which then activates the profiler automatically. In other words, the
|
||||
profiler will only be active by default in your browser - not everyone else's.
|
||||
</p>
|
||||
|
||||
<uui-toggle
|
||||
label="Activate the profiler by default"
|
||||
label-position="left"
|
||||
.checked="${this._profilingPerfomance}"
|
||||
@change="${this._changeProfilingPerformance}"></uui-toggle>
|
||||
|
||||
<h4>Friendly reminder</h4>
|
||||
<p>
|
||||
You should never let a production site run in debug mode. Debug mode is turned off by setting
|
||||
Umbraco:CMS:Hosting:Debug to false in appsettings.json, appsettings.{Environment}.json or via an environment
|
||||
variable.
|
||||
</p>
|
||||
`
|
||||
: html`
|
||||
<p>
|
||||
Umbraco currently does not run in debug mode, so you can't use the built-in profiler. This is how it should
|
||||
be for a production site.
|
||||
</p>
|
||||
<p>
|
||||
Debug mode is turned on by setting <b>debug="true"</b> on the <b><compilation /></b> element in
|
||||
web.config.
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-box>
|
||||
<h1>Performance Profiling</h1>
|
||||
${typeof this._profilingStatus === 'undefined' ? html`<uui-loader></uui-loader>` : this.renderProfilingStatus()}
|
||||
</uui-box>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { UUIButtonState } from '@umbraco-ui/uui';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { ApiError, ProblemDetails, Telemetry, TelemetryLevel, TelemetryResource } from '@umbraco-cms/backend-api';
|
||||
|
||||
export type SettingOption = 'Minimal' | 'Basic' | 'Detailed';
|
||||
|
||||
@customElement('umb-dashboard-telemetry')
|
||||
export class UmbDashboardTelemetryElement extends LitElement {
|
||||
static styles = [
|
||||
@@ -26,18 +25,21 @@ export class UmbDashboardTelemetryElement extends LitElement {
|
||||
@state()
|
||||
private _errorMessage = '';
|
||||
|
||||
@state()
|
||||
private _buttonState: UUIButtonState | undefined = undefined;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
async connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._setup();
|
||||
await this._setup();
|
||||
}
|
||||
|
||||
private async _setup() {
|
||||
try {
|
||||
const consentLevels = await TelemetryResource.getTelemetry({});
|
||||
const consentLevels = await TelemetryResource.getTelemetry({skip: 0, take: 3});
|
||||
this._telemetryLevels = consentLevels.items ?? [];
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) {
|
||||
@@ -59,11 +61,14 @@ export class UmbDashboardTelemetryElement extends LitElement {
|
||||
|
||||
private _handleSubmit = async (e: CustomEvent<SubmitEvent>) => {
|
||||
e.stopPropagation();
|
||||
this._buttonState = 'waiting';
|
||||
try {
|
||||
await TelemetryResource.postTelemetryLevel({
|
||||
requestBody: { telemetryLevel: this._telemetryFormData },
|
||||
});
|
||||
this._buttonState = 'success';
|
||||
} catch (e) {
|
||||
this._buttonState = 'failed';
|
||||
if (e instanceof ApiError) {
|
||||
const error = e.body as ProblemDetails;
|
||||
if (e.status === 400) {
|
||||
@@ -75,10 +80,6 @@ export class UmbDashboardTelemetryElement extends LitElement {
|
||||
}
|
||||
};
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
private _handleChange(e: InputEvent) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
this._telemetryFormData = this._telemetryLevels[parseInt(target.value) - 1].telemetryLevel ?? TelemetryLevel.BASIC;
|
||||
@@ -145,7 +146,7 @@ export class UmbDashboardTelemetryElement extends LitElement {
|
||||
will be fully anonymized.
|
||||
</p>
|
||||
${this._renderSettingSlider()}
|
||||
<uui-button look="primary" color="positive" label="Save telemetry settings" @click="${this._handleSubmit}">
|
||||
<uui-button look="primary" color="positive" label="Save telemetry settings" @click="${this._handleSubmit}" .state=${this._buttonState}>
|
||||
Save
|
||||
</uui-button>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import './editor-data-type.element';
|
||||
|
||||
import { Meta, Story } from '@storybook/web-components';
|
||||
import { html } from 'lit-html';
|
||||
|
||||
import { data } from '../../../core/mocks/data/data-type.data';
|
||||
|
||||
import type { UmbEditorDataTypeElement } from './editor-data-type.element';
|
||||
|
||||
export default {
|
||||
title: 'Editors/Data Type',
|
||||
component: 'umb-editor-data-type',
|
||||
id: 'umb-editor-data-type',
|
||||
} as Meta;
|
||||
|
||||
export const AAAOverview: Story<UmbEditorDataTypeElement> = () =>
|
||||
html` <umb-editor-data-type id="${data[0].key}"></umb-editor-data-type>`;
|
||||
AAAOverview.storyName = 'Overview';
|
||||
@@ -1,18 +0,0 @@
|
||||
import './editor-document-type.element';
|
||||
|
||||
import { Meta, Story } from '@storybook/web-components';
|
||||
import { html } from 'lit-html';
|
||||
|
||||
import { data } from '../../../core/mocks/data/document-type.data';
|
||||
|
||||
import type { UmbEditorDocumentTypeElement } from './editor-document-type.element';
|
||||
|
||||
export default {
|
||||
title: 'Editors/Document Type',
|
||||
component: 'umb-editor-document-type',
|
||||
id: 'umb-editor-document-type',
|
||||
} as Meta;
|
||||
|
||||
export const AAAOverview: Story<UmbEditorDocumentTypeElement> = () =>
|
||||
html` <umb-editor-document-type id="${data[0].key}"></umb-editor-document-type>`;
|
||||
AAAOverview.storyName = 'Overview';
|
||||
@@ -1,25 +0,0 @@
|
||||
import './editor-view-document-type-design.element';
|
||||
|
||||
import { Meta, Story } from '@storybook/web-components';
|
||||
import { html } from 'lit-html';
|
||||
|
||||
import { data } from '../../../../../core/mocks/data/document-type.data';
|
||||
import { UmbDocumentTypeContext } from '../../document-type.context';
|
||||
|
||||
import type { UmbEditorViewDocumentTypeDesignElement } from './editor-view-document-type-design.element';
|
||||
|
||||
export default {
|
||||
title: 'Editors/Document Type/Views/Design',
|
||||
component: 'umb-editor-view-document-type-design',
|
||||
id: 'umb-editor-view-document-type-design',
|
||||
decorators: [
|
||||
(story) =>
|
||||
html` <umb-context-provider key="umbDocumentTypeContext" .value=${new UmbDocumentTypeContext(data[0])}>
|
||||
${story()}
|
||||
</umb-context-provider>`,
|
||||
],
|
||||
} as Meta;
|
||||
|
||||
export const AAAOverview: Story<UmbEditorViewDocumentTypeDesignElement> = () =>
|
||||
html` <umb-editor-view-document-type-design></umb-editor-view-document-type-design>`;
|
||||
AAAOverview.storyName = 'Overview';
|
||||
@@ -1,78 +0,0 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry';
|
||||
import type { ManifestEditorView, ManifestWithLoader } from '@umbraco-cms/models';
|
||||
|
||||
import '../shared/node/editor-node.element';
|
||||
|
||||
@customElement('umb-editor-document')
|
||||
export class UmbEditorDocumentElement extends LitElement {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@property()
|
||||
entityKey!: string;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._registerEditorViews();
|
||||
}
|
||||
|
||||
private _registerEditorViews() {
|
||||
const dashboards: Array<ManifestWithLoader<ManifestEditorView>> = [
|
||||
{
|
||||
type: 'editorView',
|
||||
alias: 'Umb.EditorView.Document.Edit',
|
||||
name: 'Document Editor Edit View',
|
||||
loader: () => import('../shared/node/views/edit/editor-view-node-edit.element'),
|
||||
weight: 200,
|
||||
meta: {
|
||||
editors: ['Umb.Editor.Document'],
|
||||
label: 'Info',
|
||||
pathname: 'content',
|
||||
icon: 'document',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'editorView',
|
||||
alias: 'Umb.EditorView.Document.Info',
|
||||
name: 'Document Editor Info View',
|
||||
loader: () => import('../shared/node/views/info/editor-view-node-info.element'),
|
||||
weight: 100,
|
||||
meta: {
|
||||
editors: ['Umb.Editor.Document'],
|
||||
label: 'Info',
|
||||
pathname: 'info',
|
||||
icon: 'info',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
dashboards.forEach((dashboard) => {
|
||||
if (umbExtensionsRegistry.isRegistered(dashboard.alias)) return;
|
||||
umbExtensionsRegistry.register(dashboard);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<umb-editor-node .entityKey=${this.entityKey} alias="Umb.Editor.Document"></umb-editor-node>`;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbEditorDocumentElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-editor-document': UmbEditorDocumentElement;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import './editor-document.element';
|
||||
|
||||
import { Meta, Story } from '@storybook/web-components';
|
||||
import { html } from 'lit-html';
|
||||
|
||||
import { data } from '../../../core/mocks/data/node.data';
|
||||
|
||||
import type { UmbEditorDocumentElement } from './editor-document.element';
|
||||
|
||||
export default {
|
||||
title: 'Editors/Document',
|
||||
component: 'umb-editor-document',
|
||||
id: 'umb-editor-document',
|
||||
} as Meta;
|
||||
|
||||
const documentNodes = data.filter((node) => node.type === 'document');
|
||||
|
||||
export const AAAOverview: Story<UmbEditorDocumentElement> = () =>
|
||||
html` <umb-editor-document id="${documentNodes[0].key}"></umb-editor-document>`;
|
||||
AAAOverview.storyName = 'Overview';
|
||||
@@ -1,16 +0,0 @@
|
||||
import './editor-extensions.element';
|
||||
|
||||
import { Meta, Story } from '@storybook/web-components';
|
||||
import { html } from 'lit-html';
|
||||
|
||||
import type { UmbEditorExtensionsElement } from './editor-extensions.element';
|
||||
|
||||
export default {
|
||||
title: 'Editors/Extensions',
|
||||
component: 'umb-editor-extensions',
|
||||
id: 'umb-editor-extensions',
|
||||
} as Meta;
|
||||
|
||||
export const AAAOverview: Story<UmbEditorExtensionsElement> = () =>
|
||||
html` <umb-editor-extensions></umb-editor-extensions>`;
|
||||
AAAOverview.storyName = 'Overview';
|
||||
@@ -1,103 +0,0 @@
|
||||
import type { ManifestEditor, ManifestWithLoader } from '@umbraco-cms/models';
|
||||
|
||||
export const manifests: Array<ManifestWithLoader<ManifestEditor>> = [
|
||||
{
|
||||
type: 'editor',
|
||||
alias: 'Umb.Editor.Member',
|
||||
name: 'Member Editor',
|
||||
loader: () => import('./member/editor-member.element'),
|
||||
meta: {
|
||||
entityType: 'member',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'editor',
|
||||
alias: 'Umb.Editor.MemberGroup',
|
||||
name: 'Member Group Editor',
|
||||
loader: () => import('./member-group/editor-member-group.element'),
|
||||
meta: {
|
||||
entityType: 'memberGroup',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'editor',
|
||||
alias: 'Umb.Editor.DataType',
|
||||
name: 'Data Type Editor',
|
||||
loader: () => import('./data-type/editor-data-type.element'),
|
||||
meta: {
|
||||
entityType: 'dataType',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'editor',
|
||||
alias: 'Umb.Editor.DocumentType',
|
||||
name: 'Document Type Editor',
|
||||
loader: () => import('./document-type/editor-document-type.element'),
|
||||
meta: {
|
||||
entityType: 'documentType',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'editor',
|
||||
alias: 'Umb.Editor.Extensions',
|
||||
name: 'Extensions Editor',
|
||||
loader: () => import('./extensions/editor-extensions.element'),
|
||||
meta: {
|
||||
entityType: 'extensionsList',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'editor',
|
||||
alias: 'Umb.Editor.Media',
|
||||
name: 'Media Editor',
|
||||
loader: () => import('./media/editor-media.element'),
|
||||
meta: {
|
||||
entityType: 'media',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'editor',
|
||||
alias: 'Umb.Editor.Document',
|
||||
name: 'Content Editor',
|
||||
loader: () => import('./document/editor-document.element'),
|
||||
meta: {
|
||||
entityType: 'document',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'editor',
|
||||
alias: 'Umb.Editor.User',
|
||||
name: 'User Editor',
|
||||
loader: () => import('./user/editor-user.element'),
|
||||
meta: {
|
||||
entityType: 'user',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'editor',
|
||||
alias: 'Umb.Editor.UserGroup',
|
||||
name: 'User Group Editor',
|
||||
loader: () => import('./user-group/editor-user-group.element'),
|
||||
meta: {
|
||||
entityType: 'userGroup',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'editor',
|
||||
alias: 'Umb.Editor.Package',
|
||||
name: 'Package Editor',
|
||||
loader: () => import('./package/editor-package.element'),
|
||||
meta: {
|
||||
entityType: 'package',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'editor',
|
||||
alias: 'Umb.Editor.PackageBuilder',
|
||||
name: 'Package Builder Editor',
|
||||
loader: () => import('./package-builder/editor-package-builder.element'),
|
||||
meta: {
|
||||
entityType: 'packageBuilder',
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -1,78 +0,0 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import type { ManifestEditorView, ManifestWithLoader } from '@umbraco-cms/models';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry';
|
||||
|
||||
import '../shared/node/editor-node.element';
|
||||
|
||||
@customElement('umb-editor-media')
|
||||
export class UmbEditorMediaElement extends LitElement {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@property()
|
||||
entityKey!: string;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._registerEditorViews();
|
||||
}
|
||||
|
||||
private _registerEditorViews() {
|
||||
const dashboards: Array<ManifestWithLoader<ManifestEditorView>> = [
|
||||
{
|
||||
type: 'editorView',
|
||||
alias: 'Umb.EditorView.Media.Edit',
|
||||
name: 'Media Editor Edit View',
|
||||
loader: () => import('../shared/node/views/edit/editor-view-node-edit.element'),
|
||||
weight: 200,
|
||||
meta: {
|
||||
editors: ['Umb.Editor.Media'],
|
||||
label: 'Media',
|
||||
pathname: 'media',
|
||||
icon: 'umb:picture',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'editorView',
|
||||
alias: 'Umb.EditorView.Media.Info',
|
||||
name: 'Media Editor Info View',
|
||||
loader: () => import('../shared/node/views/info/editor-view-node-info.element'),
|
||||
weight: 100,
|
||||
meta: {
|
||||
editors: ['Umb.Editor.Media'],
|
||||
label: 'Info',
|
||||
pathname: 'info',
|
||||
icon: 'info',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
dashboards.forEach((dashboard) => {
|
||||
if (umbExtensionsRegistry.isRegistered(dashboard.alias)) return;
|
||||
umbExtensionsRegistry.register(dashboard);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<umb-editor-node .entityKey=${this.entityKey} alias="Umb.Editor.Media"></umb-editor-node>`;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbEditorMediaElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-editor-media': UmbEditorMediaElement;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import './editor-media.element';
|
||||
|
||||
import { Meta, Story } from '@storybook/web-components';
|
||||
import { html } from 'lit-html';
|
||||
|
||||
import { data } from '../../../core/mocks/data/node.data';
|
||||
|
||||
import type { UmbEditorMediaElement } from './editor-media.element';
|
||||
|
||||
export default {
|
||||
title: 'Editors/Media',
|
||||
component: 'umb-editor-media',
|
||||
id: 'umb-editor-media',
|
||||
} as Meta;
|
||||
|
||||
const mediaNodes = data.filter((node) => node.type === 'media');
|
||||
|
||||
export const AAAOverview: Story<UmbEditorMediaElement> = () =>
|
||||
html` <umb-editor-media id="${mediaNodes[0].key}"></umb-editor-media>`;
|
||||
AAAOverview.storyName = 'Overview';
|
||||
@@ -1,33 +0,0 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
|
||||
import '../shared/editor-entity-layout/editor-entity-layout.element';
|
||||
|
||||
@customElement('umb-editor-package-builder')
|
||||
export class UmbEditorPackageBuilderElement extends LitElement {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
render() {
|
||||
return html`<umb-editor-entity-layout alias="Umb.Editor.PackageBuilder"
|
||||
>PACKAGE BUILDER</umb-editor-entity-layout
|
||||
> `;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbEditorPackageBuilderElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-editor-package-builder': UmbEditorPackageBuilderElement;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
|
||||
import '../shared/editor-entity-layout/editor-entity-layout.element';
|
||||
|
||||
@customElement('umb-editor-package')
|
||||
export class UmbEditorPackageElement extends LitElement {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
render() {
|
||||
return html`<umb-editor-entity-layout alias="Umb.Editor.Package">PACKAGE EDITOR</umb-editor-entity-layout> `;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbEditorPackageElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-editor-package': UmbEditorPackageElement;
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui';
|
||||
import { CSSResultGroup, html, LitElement } from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { createExtensionElement } from '@umbraco-cms/extensions-api';
|
||||
import type { ManifestEditorAction } from '@umbraco-cms/models';
|
||||
|
||||
@customElement('umb-editor-action-extension')
|
||||
export class UmbEditorActionExtensionElement extends LitElement {
|
||||
static styles: CSSResultGroup = [UUITextStyles];
|
||||
|
||||
private _editorAction?: ManifestEditorAction;
|
||||
@property({ type: Object })
|
||||
public get editorAction(): ManifestEditorAction | undefined {
|
||||
return this._editorAction;
|
||||
}
|
||||
public set editorAction(value: ManifestEditorAction | undefined) {
|
||||
this._editorAction = value;
|
||||
this._createElement();
|
||||
}
|
||||
|
||||
@state()
|
||||
private _element?: any;
|
||||
|
||||
private async _createElement() {
|
||||
if (!this.editorAction) return;
|
||||
|
||||
try {
|
||||
this._element = await createExtensionElement(this.editorAction);
|
||||
if (!this._element) return;
|
||||
|
||||
this._element.editorAction = this.editorAction;
|
||||
} catch (error) {
|
||||
// TODO: loading JS failed so we should do some nice UI. (This does only happen if extension has a js prop, otherwise we concluded that no source was needed resolved the load.)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`${this._element}`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-editor-action-extension': UmbEditorActionExtensionElement;
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import { UmbObserverMixin } from '@umbraco-cms/observable-api';
|
||||
import { createExtensionElement } from '@umbraco-cms/extensions-api';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry';
|
||||
import { UmbContextConsumerMixin } from '@umbraco-cms/context-api';
|
||||
import type { ManifestEditor } from '@umbraco-cms/models';
|
||||
|
||||
@customElement('umb-editor-entity')
|
||||
export class UmbEditorEntityElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@property()
|
||||
public entityKey!: string;
|
||||
|
||||
private _entityType = '';
|
||||
@property()
|
||||
public get entityType(): string {
|
||||
return this._entityType;
|
||||
}
|
||||
public set entityType(value: string) {
|
||||
this._entityType = value;
|
||||
this._observeEditors();
|
||||
}
|
||||
|
||||
@state()
|
||||
private _element?: any;
|
||||
|
||||
private _currentEditorAlias = '';
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this._observeEditors();
|
||||
}
|
||||
|
||||
private _observeEditors() {
|
||||
this.observe<ManifestEditor>(
|
||||
umbExtensionsRegistry
|
||||
.extensionsOfType('editor')
|
||||
.pipe(map((editors) => editors.find((editor) => editor.meta.entityType === this.entityType))),
|
||||
(editor) => {
|
||||
// don't rerender editor if it's the same
|
||||
if (this._currentEditorAlias === editor.alias) return;
|
||||
this._currentEditorAlias = editor.alias;
|
||||
this._createElement(editor);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private async _createElement(editor?: ManifestEditor) {
|
||||
// TODO: implement fallback editor
|
||||
const fallbackEditor = document.createElement('div');
|
||||
fallbackEditor.innerHTML = '<p>No editor found</p>';
|
||||
|
||||
if (!editor) {
|
||||
this._element = fallbackEditor;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this._element = (await createExtensionElement(editor)) as any;
|
||||
this._element.entityKey = this.entityKey;
|
||||
} catch (error) {
|
||||
this._element = fallbackEditor;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`${this._element}`;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbEditorEntityElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-editor-entity': UmbEditorEntityElement;
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
|
||||
@customElement('umb-editor-layout')
|
||||
export class UmbEditorLayout extends LitElement {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#editor-frame {
|
||||
background-color: var(--uui-color-background);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#header {
|
||||
background-color: var(--uui-color-surface);
|
||||
width: 100%;
|
||||
border-bottom: 1px solid var(--uui-color-border);
|
||||
box-sizing: border-box;
|
||||
padding: 0 var(--uui-size-6);
|
||||
}
|
||||
|
||||
#main {
|
||||
/* padding: 0 var(--uui-size-6); */
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 70px;
|
||||
width: 100%;
|
||||
padding: 0 var(--uui-size-6);
|
||||
border-top: 1px solid var(--uui-color-border);
|
||||
background-color: var(--uui-color-surface);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div id="editor-frame">
|
||||
<div id="header">
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
<uui-scroll-container id="main">
|
||||
<slot></slot>
|
||||
</uui-scroll-container>
|
||||
<div id="footer">
|
||||
<!-- only show footer if slot has elements -->
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-editor-layout': UmbEditorLayout;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Meta, Story } from '@storybook/web-components';
|
||||
import { html } from 'lit-html';
|
||||
|
||||
import type { UmbEditorPropertyLayoutElement } from './editor-property-layout.element';
|
||||
import './editor-property-layout.element';
|
||||
|
||||
export default {
|
||||
title: 'Editors/Shared/Editor Property Layout',
|
||||
component: 'umb-editor-property-layout',
|
||||
id: 'umb-editor-property-layout',
|
||||
} as Meta;
|
||||
|
||||
export const AAAOverview: Story<UmbEditorPropertyLayoutElement> = () => html` <umb-editor-property-layout
|
||||
label="Label"
|
||||
description="Description">
|
||||
<div slot="property-action-menu"><uui-button color="" look="placeholder">Menu</uui-button></div>
|
||||
|
||||
<div slot="editor"><uui-button color="" look="placeholder">Editor</uui-button></div>
|
||||
</umb-editor-property-layout>`;
|
||||
AAAOverview.storyName = 'Overview';
|
||||
@@ -1,18 +0,0 @@
|
||||
import './editor-node.element';
|
||||
|
||||
import { Meta, Story } from '@storybook/web-components';
|
||||
import { html } from 'lit-html';
|
||||
|
||||
import { data } from '../../../../core/mocks/data/node.data';
|
||||
|
||||
import type { UmbEditorNodeElement } from './editor-node.element';
|
||||
|
||||
export default {
|
||||
title: 'Editors/Shared/Node',
|
||||
component: 'umb-editor-node',
|
||||
id: 'umb-editor-node',
|
||||
} as Meta;
|
||||
|
||||
export const AAAOverview: Story<UmbEditorNodeElement> = () =>
|
||||
html` <umb-editor-node id="${data[0].key}"></umb-editor-node>`;
|
||||
AAAOverview.storyName = 'Overview';
|
||||
@@ -1,63 +0,0 @@
|
||||
import { html, LitElement } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { distinctUntilChanged } from 'rxjs';
|
||||
import { NodeEntity, NodeProperty, NodePropertyData } from '../../../../../../core/mocks/data/node.data';
|
||||
import { UmbNodeContext } from '../../node.context';
|
||||
import { UmbContextConsumerMixin } from '@umbraco-cms/context-api';
|
||||
|
||||
import '../../../../../components/node-property/node-property.element';
|
||||
import { UmbObserverMixin } from '@umbraco-cms/observable-api';
|
||||
|
||||
@customElement('umb-editor-view-node-edit')
|
||||
export class UmbEditorViewNodeEditElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) {
|
||||
static styles = [UUITextStyles];
|
||||
|
||||
@state()
|
||||
_properties: NodeProperty[] = [];
|
||||
|
||||
@state()
|
||||
_data: NodePropertyData[] = [];
|
||||
|
||||
private _nodeContext?: UmbNodeContext;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.consumeContext('umbNodeContext', (nodeContext) => {
|
||||
this._nodeContext = nodeContext;
|
||||
this._observeNode();
|
||||
});
|
||||
}
|
||||
|
||||
private _observeNode() {
|
||||
if (!this._nodeContext) return;
|
||||
|
||||
this.observe<NodeEntity>(this._nodeContext.data.pipe(distinctUntilChanged()), (node) => {
|
||||
this._properties = node.properties;
|
||||
this._data = node.data;
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-box>
|
||||
${this._properties.map(
|
||||
(property: NodeProperty) => html`
|
||||
<umb-node-property
|
||||
.property=${property}
|
||||
.value=${this._data.find((data) => data.alias === property.alias)?.value}></umb-node-property>
|
||||
`
|
||||
)}
|
||||
</uui-box>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbEditorViewNodeEditElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-editor-view-node-edit': UmbEditorViewNodeEditElement;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import './editor-view-node-edit.element';
|
||||
|
||||
import { Meta, Story } from '@storybook/web-components';
|
||||
import { html } from 'lit-html';
|
||||
|
||||
import { data } from '../../../../../../core/mocks/data/node.data';
|
||||
import { UmbNodeContext } from '../../node.context';
|
||||
|
||||
import type { UmbEditorViewNodeEditElement } from './editor-view-node-edit.element';
|
||||
|
||||
export default {
|
||||
title: 'Editors/Shared/Node/Views/Edit',
|
||||
component: 'umb-editor-view-node-edit',
|
||||
id: 'umb-editor-view-node-edit',
|
||||
decorators: [
|
||||
(story) =>
|
||||
html` <umb-context-provider key="umbNodeContext" .value=${new UmbNodeContext(data[0])}>
|
||||
${story()}
|
||||
</umb-context-provider>`,
|
||||
],
|
||||
} as Meta;
|
||||
|
||||
export const AAAOverview: Story<UmbEditorViewNodeEditElement> = () =>
|
||||
html` <umb-editor-view-node-edit></umb-editor-view-node-edit>`;
|
||||
AAAOverview.storyName = 'Overview';
|
||||
@@ -1,25 +0,0 @@
|
||||
import './editor-view-node-info.element';
|
||||
|
||||
import { Meta, Story } from '@storybook/web-components';
|
||||
import { html } from 'lit-html';
|
||||
|
||||
import { data } from '../../../../../../core/mocks/data/node.data';
|
||||
import { UmbNodeContext } from '../../node.context';
|
||||
|
||||
import type { UmbEditorViewNodeInfoElement } from './editor-view-node-info.element';
|
||||
|
||||
export default {
|
||||
title: 'Editors/Shared/Node/Views/Info',
|
||||
component: 'umb-editor-view-node-info',
|
||||
id: 'umb-editor-view-node-info',
|
||||
decorators: [
|
||||
(story) =>
|
||||
html` <umb-context-provider key="umbNodeContext" .value=${new UmbNodeContext(data[0])}>
|
||||
${story()}
|
||||
</umb-context-provider>`,
|
||||
],
|
||||
} as Meta;
|
||||
|
||||
export const AAAOverview: Story<UmbEditorViewNodeInfoElement> = () =>
|
||||
html` <umb-editor-view-node-info></umb-editor-view-node-info>`;
|
||||
AAAOverview.storyName = 'Overview';
|
||||
@@ -1,283 +0,0 @@
|
||||
import { UUIInputElement, UUIInputEvent } from '@umbraco-ui/uui';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css';
|
||||
import { css, html, LitElement, nothing } from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
|
||||
@customElement('umb-editor-user-group')
|
||||
export class UmbEditorUserGroupElement extends LitElement {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#main {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 350px;
|
||||
gap: var(--uui-size-space-6);
|
||||
padding: var(--uui-size-space-6);
|
||||
}
|
||||
#left-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--uui-size-space-4);
|
||||
}
|
||||
#right-column > uui-box > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--uui-size-space-2);
|
||||
}
|
||||
hr {
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--uui-color-divider);
|
||||
width: 100%;
|
||||
}
|
||||
uui-input {
|
||||
width: 100%;
|
||||
}
|
||||
.faded-text {
|
||||
color: var(--uui-color-text-alt);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
#default-permissions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--uui-size-space-4);
|
||||
}
|
||||
.default-permission {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--uui-size-space-4);
|
||||
padding: var(--uui-size-space-2);
|
||||
}
|
||||
.default-permission:not(:last-child) {
|
||||
border-bottom: 1px solid var(--uui-color-divider);
|
||||
}
|
||||
.permission-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@state()
|
||||
private _userName = '';
|
||||
|
||||
@property({ type: String })
|
||||
entityKey = '';
|
||||
|
||||
defaultPermissions: Array<{
|
||||
name: string;
|
||||
permissions: Array<{ name: string; description: string; value: boolean }>;
|
||||
}> = [
|
||||
{
|
||||
name: 'Administration',
|
||||
permissions: [
|
||||
{
|
||||
name: 'Culture and Hostnames',
|
||||
description: 'Allow access to assign culture and hostnames',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
name: 'Restrict Public Access',
|
||||
description: 'Allow access to set and change access restrictions for a node',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
name: 'Rollback',
|
||||
description: 'Allow access to roll back a node to a previous state',
|
||||
value: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Content',
|
||||
permissions: [
|
||||
{
|
||||
name: 'Browse Node',
|
||||
description: 'Allow access to view a node',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
name: 'Create Content Template',
|
||||
description: 'Allow access to create a Content Template',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
description: 'Allow access to delete nodes',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
name: 'Create',
|
||||
description: 'Allow access to create nodes',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
name: 'Publish',
|
||||
description: 'Allow access to publish nodes',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
name: 'Permissions',
|
||||
description: 'Allow access to change permissions for a node',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
name: 'Send To Publish',
|
||||
description: 'Allow access to send a node for approval before publishing',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
name: 'Unpublish',
|
||||
description: 'Allow access to unpublish a node',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
description: 'Allow access to save a node',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
name: 'Full restore',
|
||||
description: 'Allow the user to restore items',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
name: 'Partial restore',
|
||||
description: 'Allow the user to partial restore items',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
name: 'Queue for transfer',
|
||||
description: 'Allow the user to queue item(s)',
|
||||
value: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Structure',
|
||||
permissions: [
|
||||
{
|
||||
name: 'Copy',
|
||||
description: 'Allow access to copy a node',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
name: 'Move',
|
||||
description: 'Allow access to move a node',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
name: 'Sort',
|
||||
description: 'Allow access to change the sort order for nodes',
|
||||
value: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
private renderLeftColumn() {
|
||||
return html` <uui-box>
|
||||
<div slot="headline">Assign access</div>
|
||||
<div>
|
||||
<b>Sections</b>
|
||||
<div class="faded-text">Add sections to give users access</div>
|
||||
</div>
|
||||
<div>
|
||||
<b>Content start nodes</b>
|
||||
<div class="faded-text">Limit the content tree to specific start nodes</div>
|
||||
<umb-property-editor-ui-content-picker></umb-property-editor-ui-content-picker>
|
||||
</div>
|
||||
<div>
|
||||
<b>Media start nodes</b>
|
||||
<div class="faded-text">Limit the media library to specific start nodes</div>
|
||||
<umb-property-editor-ui-content-picker></umb-property-editor-ui-content-picker>
|
||||
</div>
|
||||
|
||||
<b>Content</b>
|
||||
<div class="access-content">
|
||||
<uui-icon name="folder"></uui-icon>
|
||||
<span>Content Root</span>
|
||||
</div>
|
||||
|
||||
<b>Media</b>
|
||||
<div class="access-content">
|
||||
<uui-icon name="folder"></uui-icon>
|
||||
<span>Media Root</span>
|
||||
</div>
|
||||
</uui-box>
|
||||
|
||||
<uui-box>
|
||||
<div slot="headline">Default Permissions</div>
|
||||
<div id="default-permissions">
|
||||
${repeat(
|
||||
this.defaultPermissions,
|
||||
(defaultPermission) => html`
|
||||
<div>
|
||||
<b>${defaultPermission.name}</b>
|
||||
${repeat(
|
||||
defaultPermission.permissions,
|
||||
(permission) => html`
|
||||
<div class="default-permission">
|
||||
<uui-toggle
|
||||
.checked=${permission.value}
|
||||
@change=${(e: Event) => {
|
||||
permission.value = (e.target as HTMLInputElement).checked;
|
||||
}}></uui-toggle>
|
||||
<div class="permission-info">
|
||||
<b>${permission.name}</b>
|
||||
<span class="faded-text">${permission.description}</span>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</uui-box>
|
||||
|
||||
<uui-box>
|
||||
<div slot="headline">Granular permissions</div>
|
||||
</uui-box>`;
|
||||
}
|
||||
|
||||
private renderRightColumn() {
|
||||
return html`<uui-box>
|
||||
<div slot="headline">Users</div>
|
||||
</uui-box>`;
|
||||
}
|
||||
|
||||
// TODO. find a way where we don't have to do this for all editors.
|
||||
private _handleInput(event: UUIInputEvent) {
|
||||
if (event instanceof UUIInputEvent) {
|
||||
const target = event.composedPath()[0] as UUIInputElement;
|
||||
|
||||
console.log('input', target.value);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<umb-editor-entity-layout alias="Umb.Editor.UserGroup">
|
||||
<uui-input id="name" slot="name" .value=${this._userName} @input="${this._handleInput}"></uui-input>
|
||||
<div id="main">
|
||||
<div id="left-column">${this.renderLeftColumn()}</div>
|
||||
<div id="right-column">${this.renderRightColumn()}</div>
|
||||
</div>
|
||||
</umb-editor-entity-layout>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbEditorUserGroupElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-editor-user-group': UmbEditorUserGroupElement;
|
||||
}
|
||||
}
|
||||
@@ -1,335 +0,0 @@
|
||||
import { UUIInputElement, UUIInputEvent } from '@umbraco-ui/uui';
|
||||
import { css, html, LitElement, nothing } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { ifDefined } from 'lit-html/directives/if-defined.js';
|
||||
|
||||
import { UmbUserStore } from '../../../core/stores/user/user.store';
|
||||
import { getTagLookAndColor } from '../../sections/users/user-extensions';
|
||||
import { UmbUserContext } from './user.context';
|
||||
import { UmbContextProviderMixin, UmbContextConsumerMixin } from '@umbraco-cms/context-api';
|
||||
import type { ManifestEditorAction, ManifestWithLoader, UserDetails } from '@umbraco-cms/models';
|
||||
|
||||
import '../../property-editor-uis/content-picker/property-editor-ui-content-picker.element';
|
||||
import '../shared/editor-entity-layout/editor-entity-layout.element';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry';
|
||||
|
||||
@customElement('umb-editor-user')
|
||||
export class UmbEditorUserElement extends UmbContextProviderMixin(UmbContextConsumerMixin(LitElement)) {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#main {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 350px;
|
||||
gap: var(--uui-size-space-6);
|
||||
padding: var(--uui-size-space-6);
|
||||
}
|
||||
|
||||
#left-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--uui-size-space-4);
|
||||
}
|
||||
#right-column > uui-box > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--uui-size-space-2);
|
||||
}
|
||||
uui-avatar {
|
||||
font-size: var(--uui-size-16);
|
||||
place-self: center;
|
||||
}
|
||||
hr {
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--uui-color-divider);
|
||||
width: 100%;
|
||||
}
|
||||
uui-input {
|
||||
width: 100%;
|
||||
}
|
||||
.faded-text {
|
||||
color: var(--uui-color-text-alt);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
uui-tag {
|
||||
width: fit-content;
|
||||
}
|
||||
#user-info {
|
||||
display: flex;
|
||||
gap: var(--uui-size-space-6);
|
||||
}
|
||||
#user-info > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
#assign-access {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--uui-size-space-4);
|
||||
}
|
||||
.access-content {
|
||||
margin-top: var(--uui-size-space-1);
|
||||
margin-bottom: var(--uui-size-space-4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
gap: var(--uui-size-space-3);
|
||||
}
|
||||
.access-content > span {
|
||||
align-self: end;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@state()
|
||||
private _user?: UserDetails | null;
|
||||
|
||||
@state()
|
||||
private _userName = '';
|
||||
|
||||
@property({ type: String })
|
||||
entityKey = '';
|
||||
|
||||
protected _userStore?: UmbUserStore;
|
||||
protected _usersSubscription?: Subscription;
|
||||
private _userContext?: UmbUserContext;
|
||||
|
||||
private _userNameSubscription?: Subscription;
|
||||
|
||||
private _languages = []; //TODO Add languages
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._registerEditorActions();
|
||||
}
|
||||
|
||||
private _registerEditorActions() {
|
||||
const manifests: Array<ManifestWithLoader<ManifestEditorAction>> = [
|
||||
{
|
||||
type: 'editorAction',
|
||||
alias: 'Umb.EditorAction.User.Save',
|
||||
name: 'EditorActionUserSave',
|
||||
loader: () => import('./actions/editor-action-user-save.element'),
|
||||
meta: {
|
||||
editors: ['Umb.Editor.User'],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
manifests.forEach((manifest) => {
|
||||
if (umbExtensionsRegistry.isRegistered(manifest.alias)) return;
|
||||
umbExtensionsRegistry.register(manifest);
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
|
||||
this.consumeContext('umbUserStore', (usersContext: UmbUserStore) => {
|
||||
this._userStore = usersContext;
|
||||
this._observeUser();
|
||||
});
|
||||
}
|
||||
|
||||
private _observeUser() {
|
||||
this._usersSubscription?.unsubscribe();
|
||||
|
||||
this._usersSubscription = this._userStore?.getByKey(this.entityKey).subscribe((user) => {
|
||||
this._user = user;
|
||||
if (!this._user) return;
|
||||
|
||||
if (!this._userContext) {
|
||||
this._userContext = new UmbUserContext(this._user);
|
||||
this.provideContext('umbUserContext', this._userContext);
|
||||
} else {
|
||||
this._userContext.update(this._user);
|
||||
}
|
||||
|
||||
this._userNameSubscription = this._userContext.data.subscribe((user) => {
|
||||
if (user && user.name !== this._userName) {
|
||||
this._userName = user.name;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
|
||||
this._usersSubscription?.unsubscribe();
|
||||
this._userNameSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
private _updateUserStatus() {
|
||||
if (!this._user || !this._userStore) return;
|
||||
|
||||
const isDisabled = this._user.status === 'disabled';
|
||||
isDisabled ? this._userStore.enableUsers([this._user.key]) : this._userStore.disableUsers([this._user.key]);
|
||||
}
|
||||
|
||||
private _deleteUser() {
|
||||
if (!this._user || !this._userStore) return;
|
||||
|
||||
this._userStore.deleteUsers([this._user.key]);
|
||||
|
||||
history.pushState(null, '', '/section/users/view/users/overview');
|
||||
}
|
||||
|
||||
private renderLeftColumn() {
|
||||
if (!this._user) return nothing;
|
||||
|
||||
return html` <uui-box>
|
||||
<div slot="headline">Profile</div>
|
||||
<uui-form-layout-item style="margin-top: 0">
|
||||
<uui-label for="email">Email</uui-label>
|
||||
<uui-input name="email" label="email" readonly value=${this._user.email}></uui-input>
|
||||
</uui-form-layout-item>
|
||||
<uui-form-layout-item style="margin-bottom: 0">
|
||||
<uui-label for="language">Language</uui-label>
|
||||
<uui-select name="language" label="language" .options=${this._languages}> </uui-select>
|
||||
</uui-form-layout-item>
|
||||
</uui-box>
|
||||
<uui-box>
|
||||
<div id="assign-access">
|
||||
<div slot="headline">Assign access</div>
|
||||
<div>
|
||||
<b>Groups</b>
|
||||
<div class="faded-text">Add groups to assign access and permissions</div>
|
||||
</div>
|
||||
<div>
|
||||
<b>Content start nodes</b>
|
||||
<div class="faded-text">Limit the content tree to specific start nodes</div>
|
||||
<umb-property-editor-ui-content-picker></umb-property-editor-ui-content-picker>
|
||||
</div>
|
||||
<div>
|
||||
<b>Media start nodes</b>
|
||||
<div class="faded-text">Limit the media library to specific start nodes</div>
|
||||
<umb-property-editor-ui-content-picker></umb-property-editor-ui-content-picker>
|
||||
</div>
|
||||
</div>
|
||||
</uui-box>
|
||||
<uui-box>
|
||||
<div slot="headline">Access</div>
|
||||
<div slot="header" class="faded-text">
|
||||
Based on the assigned groups and start nodes, the user has access to the following nodes
|
||||
</div>
|
||||
|
||||
<b>Content</b>
|
||||
<div class="access-content">
|
||||
<uui-icon name="folder"></uui-icon>
|
||||
<span>Content Root</span>
|
||||
</div>
|
||||
|
||||
<b>Media</b>
|
||||
<div class="access-content">
|
||||
<uui-icon name="folder"></uui-icon>
|
||||
<span>Media Root</span>
|
||||
</div>
|
||||
</uui-box>`;
|
||||
}
|
||||
|
||||
private renderRightColumn() {
|
||||
if (!this._user || !this._userStore) return nothing;
|
||||
|
||||
const statusLook = getTagLookAndColor(this._user.status);
|
||||
|
||||
return html` <uui-box>
|
||||
<div id="user-info">
|
||||
<uui-avatar .name=${this._user?.name || ''}></uui-avatar>
|
||||
<uui-button label="Change photo"></uui-button>
|
||||
<hr />
|
||||
${this._user?.status !== 'invited'
|
||||
? html`
|
||||
<uui-button
|
||||
@click=${this._updateUserStatus}
|
||||
look="primary"
|
||||
color="${this._user.status === 'disabled' ? 'positive' : 'warning'}"
|
||||
label="${this._user.status === 'disabled' ? 'Enable' : 'Disable'}"></uui-button>
|
||||
`
|
||||
: nothing}
|
||||
<uui-button @click=${this._deleteUser} look="primary" color="danger" label="Delete User"></uui-button>
|
||||
<div>
|
||||
<b>Status:</b>
|
||||
<uui-tag look="${ifDefined(statusLook?.look)}" color="${ifDefined(statusLook?.color)}">
|
||||
${this._user.status}
|
||||
</uui-tag>
|
||||
</div>
|
||||
${this._user?.status === 'invited'
|
||||
? html`
|
||||
<uui-textarea placeholder="Enter a message..."> </uui-textarea>
|
||||
<uui-button look="primary" label="Resend invitation"></uui-button>
|
||||
`
|
||||
: nothing}
|
||||
<div>
|
||||
<b>Last login:</b>
|
||||
<span>${this._user.lastLoginDate || `${this._user.name} has not logged in yet`}</span>
|
||||
</div>
|
||||
<div>
|
||||
<b>Failed login attempts</b>
|
||||
<span>${this._user.failedLoginAttempts}</span>
|
||||
</div>
|
||||
<div>
|
||||
<b>Last lockout date:</b>
|
||||
<span>${this._user.lastLockoutDate || `${this._user.name} has not been locked out`}</span>
|
||||
</div>
|
||||
<div>
|
||||
<b>Password last changed:</b>
|
||||
<span>${this._user.lastLoginDate || `${this._user.name} has not changed password`}</span>
|
||||
</div>
|
||||
<div>
|
||||
<b>User created:</b>
|
||||
<span>${this._user.createDate}</span>
|
||||
</div>
|
||||
<div>
|
||||
<b>User last updated:</b>
|
||||
<span>${this._user.updateDate}</span>
|
||||
</div>
|
||||
<div>
|
||||
<b>Key:</b>
|
||||
<span>${this._user.key}</span>
|
||||
</div>
|
||||
</div>
|
||||
</uui-box>`;
|
||||
}
|
||||
|
||||
// TODO. find a way where we don't have to do this for all editors.
|
||||
private _handleInput(event: UUIInputEvent) {
|
||||
if (event instanceof UUIInputEvent) {
|
||||
const target = event.composedPath()[0] as UUIInputElement;
|
||||
|
||||
if (typeof target?.value === 'string') {
|
||||
this._userContext?.update({ name: target.value });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this._user) return html`User not found`;
|
||||
|
||||
return html`
|
||||
<umb-editor-entity-layout alias="Umb.Editor.User">
|
||||
<uui-input id="name" slot="name" .value=${this._userName} @input="${this._handleInput}"></uui-input>
|
||||
<div id="main">
|
||||
<div id="left-column">${this.renderLeftColumn()}</div>
|
||||
<div id="right-column">${this.renderRightColumn()}</div>
|
||||
</div>
|
||||
</umb-editor-entity-layout>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbEditorUserElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-editor-view-users-user-details': UmbEditorUserElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { CSSResultGroup, html, LitElement } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { createExtensionElement } from '@umbraco-cms/extensions-api';
|
||||
import type { ManifestExternalLoginProvider } from '@umbraco-cms/models';
|
||||
|
||||
@customElement('umb-external-login-provider-extension')
|
||||
export class UmbExternalLoginProviderExtensionElement extends LitElement {
|
||||
static styles: CSSResultGroup = [UUITextStyles];
|
||||
|
||||
private _externalLoginProvider?: ManifestExternalLoginProvider;
|
||||
|
||||
@property({ type: Object })
|
||||
public get externalLoginProvider(): ManifestExternalLoginProvider | undefined {
|
||||
return this._externalLoginProvider;
|
||||
}
|
||||
public set externalLoginProvider(value: ManifestExternalLoginProvider | undefined) {
|
||||
this._externalLoginProvider = value;
|
||||
this._createElement();
|
||||
}
|
||||
|
||||
@state()
|
||||
private _element?: any;
|
||||
|
||||
private async _createElement() {
|
||||
if (!this.externalLoginProvider) return;
|
||||
|
||||
try {
|
||||
this._element = (await createExtensionElement(this.externalLoginProvider)) as any | undefined;
|
||||
} catch (error) {
|
||||
// TODO: loading JS failed so we should do some nice UI. (This does only happen if extension has a js prop, otherwise we concluded that no source was needed resolved the load.)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`${this._element}`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-external-login-provider-extension': UmbExternalLoginProviderExtensionElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import { UmbContextProviderMixin, UmbContextConsumerMixin } from '@umbraco-cms/context-api';
|
||||
import { UmbObserverMixin } from '@umbraco-cms/observable-api';
|
||||
|
||||
@customElement('umb-external-login-provider-test')
|
||||
export class UmbExternalLoginProviderTestElement extends UmbContextProviderMixin(
|
||||
UmbContextConsumerMixin(UmbObserverMixin(LitElement))
|
||||
) {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--uui-size-space-4);
|
||||
padding: var(--uui-size-space-5);
|
||||
border: 1px solid var(--uui-color-border);
|
||||
background: var(--uui-color-surface-alt);
|
||||
border-radius: var(--uui-border-radius);
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<b>Custom External Login Provider</b>
|
||||
<p>This is an example of a custom external login provider using the external login provider extension point</p>
|
||||
<uui-button label="My custom login provider" look="primary"></uui-button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbExternalLoginProviderTestElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-external-login-provider-test': UmbExternalLoginProviderTestElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import { UmbContextProviderMixin, UmbContextConsumerMixin } from '@umbraco-cms/context-api';
|
||||
import { UmbObserverMixin } from '@umbraco-cms/observable-api';
|
||||
|
||||
@customElement('umb-external-login-provider-test2')
|
||||
export class UmbExternalLoginProviderTest2Element extends UmbContextProviderMixin(
|
||||
UmbContextConsumerMixin(UmbObserverMixin(LitElement))
|
||||
) {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--uui-size-space-4);
|
||||
padding: var(--uui-size-space-5);
|
||||
border: 1px solid var(--uui-color-border);
|
||||
background: var(--uui-color-surface-alt);
|
||||
border-radius: var(--uui-border-radius);
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
uui-input {
|
||||
width: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<b>Another Custom External Login Provider</b>
|
||||
<p>This is an example of another custom external login provider</p>
|
||||
<uui-form-layout-item>
|
||||
<uui-label id="emailLabel" for="email" slot="label" required>Email</uui-label>
|
||||
<uui-input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder="Enter your email..."
|
||||
required
|
||||
required-message="Email is required"></uui-input>
|
||||
</uui-form-layout-item>
|
||||
<uui-button label="Custom login" look="primary"></uui-button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbExternalLoginProviderTest2Element;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-external-login-provider-test2': UmbExternalLoginProviderTest2Element;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { ManifestExternalLoginProvider } from '@umbraco-cms/models';
|
||||
|
||||
export const manifests: Array<ManifestExternalLoginProvider> = [
|
||||
{
|
||||
type: 'externalLoginProvider',
|
||||
alias: 'Umb.ExternalLoginProvider.Test',
|
||||
name: 'Test External Login Provider',
|
||||
elementName: 'umb-external-login-provider-test',
|
||||
loader: () => import('./external-login-provider-test.element'),
|
||||
weight: 2,
|
||||
meta: {
|
||||
label: 'Test External Login Provider',
|
||||
pathname: 'test/test/test',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'externalLoginProvider',
|
||||
alias: 'Umb.ExternalLoginProvider.Test2',
|
||||
name: 'Test External Login Provider 2',
|
||||
elementName: 'umb-external-login-provider-test2',
|
||||
loader: () => import('./external-login-provider-test2.element'),
|
||||
weight: 1,
|
||||
meta: {
|
||||
label: 'Test External Login Provider 2',
|
||||
pathname: 'test/test/test',
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,36 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, CSSResultGroup, html, LitElement } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { ManifestHeaderApp } from '@umbraco-cms/extensions-registry';
|
||||
|
||||
@customElement('umb-header-app-button')
|
||||
export class UmbHeaderAppButton extends LitElement {
|
||||
|
||||
static styles: CSSResultGroup = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
uui-button {
|
||||
font-size: 18px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public manifest?: ManifestHeaderApp;
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-button look="primary" label="${ifDefined(this.manifest?.meta.label)}" compact>
|
||||
<uui-icon name="${ifDefined(this.manifest?.meta.icon)}"></uui-icon>
|
||||
</uui-button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbHeaderAppButton;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-header-app-button': UmbHeaderAppButton;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, CSSResultGroup, html, LitElement } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { UmbContextConsumerMixin } from '@umbraco-cms/context-api';
|
||||
import { UmbObserverMixin } from '@umbraco-cms/observable-api';
|
||||
import type { UserDetails } from '@umbraco-cms/models';
|
||||
import { UmbModalService } from '@umbraco-cms/services';
|
||||
import { umbCurrentUserService } from 'src/core/services/current-user';
|
||||
|
||||
@customElement('umb-header-app-current-user')
|
||||
export class UmbHeaderAppCurrentUser extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) {
|
||||
|
||||
static styles: CSSResultGroup = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
uui-button {
|
||||
font-size: 14px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@state()
|
||||
private _currentUser?: UserDetails;
|
||||
|
||||
private _modalService?: UmbModalService;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.consumeAllContexts(['umbUserStore', 'umbModalService'], (instances) => {
|
||||
this._modalService = instances['umbModalService'];
|
||||
this._observeCurrentUser();
|
||||
});
|
||||
}
|
||||
|
||||
private async _observeCurrentUser() {
|
||||
this.observe<UserDetails>(umbCurrentUserService.currentUser, (currentUser) => {
|
||||
this._currentUser = currentUser;
|
||||
});
|
||||
}
|
||||
|
||||
private _handleUserClick() {
|
||||
this._modalService?.userSettings();
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-button @click=${this._handleUserClick} look="primary" label="${this._currentUser?.name || ''}" compact>
|
||||
<uui-avatar name="${this._currentUser?.name || ''}"></uui-avatar>
|
||||
</uui-button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbHeaderAppCurrentUser;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-header-app-current-user': UmbHeaderAppCurrentUser;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ManifestPropertyAction, ManifestWithLoader } from '@umbraco-cms/models';
|
||||
import type { ManifestPropertyAction } from '@umbraco-cms/models';
|
||||
|
||||
export const manifests: Array<ManifestWithLoader<ManifestPropertyAction>> = [
|
||||
export const manifests: Array<ManifestPropertyAction> = [
|
||||
{
|
||||
type: 'propertyAction',
|
||||
alias: 'Umb.PropertyAction.Copy',
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { css, html, LitElement, nothing } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
|
||||
import { ifDefined } from 'lit-html/directives/if-defined.js';
|
||||
import type { UmbModalService } from '../../../core/services/modal';
|
||||
import type { UmbEntityStore } from '../../../core/stores/entity.store';
|
||||
import type { Entity } from '../../../core/mocks/data/entities';
|
||||
import { UmbObserverMixin } from '@umbraco-cms/observable-api';
|
||||
import { UmbContextConsumerMixin } from '@umbraco-cms/context-api';
|
||||
import { UmbDocumentStore } from 'src/core/stores/document/document.store';
|
||||
import { FolderTreeItem } from '@umbraco-cms/backend-api';
|
||||
|
||||
// TODO: rename to Document Picker
|
||||
@customElement('umb-property-editor-ui-content-picker')
|
||||
export class UmbPropertyEditorUIContentPickerElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) {
|
||||
static styles = [
|
||||
@@ -41,36 +42,37 @@ export class UmbPropertyEditorUIContentPickerElement extends UmbContextConsumerM
|
||||
public config = [];
|
||||
|
||||
@state()
|
||||
private _items: Array<Entity> = [];
|
||||
private _items: Array<FolderTreeItem> = [];
|
||||
|
||||
private _modalService?: UmbModalService;
|
||||
private _entityStore?: UmbEntityStore;
|
||||
private _documentStore?: UmbDocumentStore;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.consumeAllContexts(['umbEntityStore', 'umbModalService'], (instances) => {
|
||||
this._entityStore = instances['umbEntityStore'];
|
||||
this.consumeAllContexts(['umbDocumentStore', 'umbModalService'], (instances) => {
|
||||
this._documentStore = instances['umbDocumentStore'];
|
||||
this._modalService = instances['umbModalService'];
|
||||
this._observePickedEntities();
|
||||
this._observePickedDocuments();
|
||||
});
|
||||
}
|
||||
|
||||
private _observePickedEntities() {
|
||||
if (!this._entityStore) return;
|
||||
this.observe<Entity[]>(this._entityStore.getByKeys(this.value), (entities) => {
|
||||
this._items = entities;
|
||||
private _observePickedDocuments() {
|
||||
if (!this._documentStore) return;
|
||||
// TODO: consider changing this to the list data endpoint when it is available
|
||||
this.observe<FolderTreeItem[]>(this._documentStore.getTreeItems(this.value), (items) => {
|
||||
this._items = items;
|
||||
});
|
||||
}
|
||||
|
||||
private _openPicker() {
|
||||
const modalHandler = this._modalService?.contentPicker({ multiple: true, selection: this.value });
|
||||
modalHandler?.onClose().then(({ selection }: any) => {
|
||||
this._setValue([...this.value, ...selection]);
|
||||
this._setValue([...selection]);
|
||||
});
|
||||
}
|
||||
|
||||
private _removeItem(item: Entity) {
|
||||
private _removeItem(item: FolderTreeItem) {
|
||||
const modalHandler = this._modalService?.confirm({
|
||||
color: 'danger',
|
||||
headline: `Remove ${item.name}?`,
|
||||
@@ -88,14 +90,17 @@ export class UmbPropertyEditorUIContentPickerElement extends UmbContextConsumerM
|
||||
|
||||
private _setValue(newValue: Array<string>) {
|
||||
this.value = newValue;
|
||||
this._observePickedEntities();
|
||||
this._observePickedDocuments();
|
||||
this.dispatchEvent(new CustomEvent('property-editor-change', { bubbles: true, composed: true }));
|
||||
}
|
||||
|
||||
private _renderItem(item: Entity) {
|
||||
private _renderItem(item: FolderTreeItem) {
|
||||
// TODO: remove when we have a way to handle trashed items
|
||||
const tempItem = item as FolderTreeItem & { isTrashed: boolean };
|
||||
|
||||
return html`
|
||||
<uui-ref-node name=${item.name} detail=${item.key}>
|
||||
${item.isTrashed ? html` <uui-tag size="s" slot="tag" color="danger">Trashed</uui-tag> ` : nothing}
|
||||
<uui-ref-node name=${ifDefined(item.name === null ? undefined : item.name)} detail=${ifDefined(item.key)}>
|
||||
${tempItem.isTrashed ? html` <uui-tag size="s" slot="tag" color="danger">Trashed</uui-tag> ` : nothing}
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button @click=${() => this._removeItem(item)}>Remove</uui-button>
|
||||
</uui-action-bar>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Meta, Story } from '@storybook/web-components';
|
||||
import { html } from 'lit-html';
|
||||
|
||||
import { UmbModalService } from '../../../core/services/modal';
|
||||
import type { UmbPropertyEditorUIContentPickerElement } from './property-editor-ui-content-picker.element';
|
||||
import './property-editor-ui-content-picker.element';
|
||||
import { UmbModalService } from '../../../core/services/modal';
|
||||
import '../../components/backoffice-modal-container.element';
|
||||
import '../../components/backoffice-frame/backoffice-modal-container.element';
|
||||
|
||||
export default {
|
||||
title: 'Property Editor UIs/Content Picker',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ManifestPropertyEditorUI, ManifestWithLoader } from '@umbraco-cms/models';
|
||||
import type { ManifestPropertyEditorUI } from '@umbraco-cms/models';
|
||||
|
||||
export const manifests: Array<ManifestWithLoader<ManifestPropertyEditorUI>> = [
|
||||
export const manifests: Array<ManifestPropertyEditorUI> = [
|
||||
{
|
||||
type: 'propertyEditorUI',
|
||||
alias: 'Umb.PropertyEditorUI.BlockList',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { html, LitElement } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import type { ManifestDashboard, ManifestWithLoader } from '@umbraco-cms/models';
|
||||
import type { ManifestDashboard } from '@umbraco-cms/models';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry';
|
||||
|
||||
@customElement('umb-content-section')
|
||||
@@ -15,7 +15,7 @@ export class UmbContentSection extends LitElement {
|
||||
}
|
||||
|
||||
private _registerDashboards() {
|
||||
const dashboards: Array<ManifestWithLoader<ManifestDashboard>> = [
|
||||
const dashboards: Array<ManifestDashboard> = [
|
||||
{
|
||||
type: 'dashboard',
|
||||
alias: 'Umb.Dashboard.Welcome',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ManifestSection, ManifestWithLoader } from '@umbraco-cms/models';
|
||||
import type { ManifestSection } from '@umbraco-cms/models';
|
||||
|
||||
export const manifests: Array<ManifestWithLoader<ManifestSection>> = [
|
||||
export const manifests: Array<ManifestSection> = [
|
||||
{
|
||||
type: 'section',
|
||||
alias: 'Umb.Section.Content',
|
||||
|
||||
@@ -2,7 +2,7 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { html, LitElement } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry';
|
||||
import type { ManifestDashboard, ManifestWithLoader } from '@umbraco-cms/models';
|
||||
import type { ManifestDashboard } from '@umbraco-cms/models';
|
||||
|
||||
@customElement('umb-media-section')
|
||||
export class UmbMediaSection extends LitElement {
|
||||
@@ -14,7 +14,7 @@ export class UmbMediaSection extends LitElement {
|
||||
}
|
||||
|
||||
private _registerDashboards() {
|
||||
const dashboards: Array<ManifestWithLoader<ManifestDashboard>> = [
|
||||
const dashboards: Array<ManifestDashboard> = [
|
||||
{
|
||||
type: 'dashboard',
|
||||
alias: 'Umb.Dashboard.MediaManagement',
|
||||
|
||||
@@ -2,7 +2,7 @@ import { html, LitElement } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry';
|
||||
import { UmbContextConsumerMixin } from '@umbraco-cms/context-api';
|
||||
import type { ManifestSectionView, ManifestWithLoader } from '@umbraco-cms/models';
|
||||
import type { ManifestSectionView } from '@umbraco-cms/models';
|
||||
|
||||
@customElement('umb-section-packages')
|
||||
export class UmbSectionPackages extends UmbContextConsumerMixin(LitElement) {
|
||||
@@ -13,7 +13,7 @@ export class UmbSectionPackages extends UmbContextConsumerMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _registerSectionViews() {
|
||||
const manifests: Array<ManifestWithLoader<ManifestSectionView>> = [
|
||||
const manifests: Array<ManifestSectionView> = [
|
||||
{
|
||||
type: 'sectionView',
|
||||
alias: 'Umb.SectionView.Packages.Repo',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { html, LitElement } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { IRoute, IRoutingInfo } from 'router-slot';
|
||||
import { UmbEditorEntityElement } from '../../../../editors/shared/editor-entity/editor-entity.element';
|
||||
import { UmbWorkspaceEntityElement } from '../../../../workspaces/shared/workspace-entity/workspace-entity.element';
|
||||
|
||||
@customElement('umb-section-view-packages-created')
|
||||
export class UmbSectionViewPackagesCreatedElement extends LitElement {
|
||||
@@ -13,16 +13,16 @@ export class UmbSectionViewPackagesCreatedElement extends LitElement {
|
||||
},
|
||||
{
|
||||
path: `:entityType/:key`,
|
||||
component: () => import('../../../../editors/shared/editor-entity/editor-entity.element'),
|
||||
component: () => import('../../../../workspaces/shared/workspace-entity/workspace-entity.element'),
|
||||
setup: (component: HTMLElement, info: IRoutingInfo) => {
|
||||
const element = component as UmbEditorEntityElement;
|
||||
const element = component as UmbWorkspaceEntityElement;
|
||||
element.entityKey = info.match.params.key;
|
||||
element.entityType = info.match.params.entityType;
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: '/section/packages/view/created/overview', //TODO: this should be dynamic
|
||||
redirectTo: 'section/packages/view/created/overview', //TODO: this should be dynamic
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { html, LitElement } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { IRoute, IRoutingInfo } from 'router-slot';
|
||||
import { UmbEditorEntityElement } from '../../../../editors/shared/editor-entity/editor-entity.element';
|
||||
import { UmbWorkspaceEntityElement } from '../../../../workspaces/shared/workspace-entity/workspace-entity.element';
|
||||
|
||||
@customElement('umb-section-view-packages-installed')
|
||||
export class UmbSectionViewPackagesInstalledElement extends LitElement {
|
||||
@@ -13,16 +13,16 @@ export class UmbSectionViewPackagesInstalledElement extends LitElement {
|
||||
},
|
||||
{
|
||||
path: `:entityType/:key`,
|
||||
component: () => import('../../../../editors/shared/editor-entity/editor-entity.element'),
|
||||
component: () => import('../../../../workspaces/shared/workspace-entity/workspace-entity.element'),
|
||||
setup: (component: HTMLElement, info: IRoutingInfo) => {
|
||||
const element = component as UmbEditorEntityElement;
|
||||
const element = component as UmbWorkspaceEntityElement;
|
||||
element.entityKey = info.match.params.key;
|
||||
element.entityType = info.match.params.entityType;
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: '/section/packages/view/installed/overview', //TODO: this should be dynamic
|
||||
redirectTo: 'section/packages/view/installed/overview', //TODO: this should be dynamic
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { customElement } from 'lit/decorators.js';
|
||||
|
||||
@customElement('umb-section-view-packages-repo')
|
||||
export class UmbSectionViewPackagesRepoElement extends LitElement {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-box headline="Popular"></uui-box>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { BehaviorSubject, ReplaySubject } from 'rxjs';
|
||||
|
||||
import { Entity } from '../../core/mocks/data/entities';
|
||||
import type { ManifestSection, ManifestSectionView, ManifestTree } from '@umbraco-cms/models';
|
||||
import type { Entity, ManifestSection, ManifestSectionView, ManifestTree } from '@umbraco-cms/models';
|
||||
|
||||
export class UmbSectionContext {
|
||||
// TODO: figure out how fine grained we want to make our observables.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { html, LitElement } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry';
|
||||
import type { ManifestDashboard, ManifestWithLoader } from '@umbraco-cms/models';
|
||||
import type { ManifestDashboard } from '@umbraco-cms/models';
|
||||
|
||||
@customElement('umb-section-settings')
|
||||
export class UmbSectionSettingsElement extends LitElement {
|
||||
@@ -12,7 +12,7 @@ export class UmbSectionSettingsElement extends LitElement {
|
||||
}
|
||||
|
||||
private _registerDashboards() {
|
||||
const dashboards: Array<ManifestWithLoader<ManifestDashboard>> = [
|
||||
const dashboards: Array<ManifestDashboard> = [
|
||||
{
|
||||
type: 'dashboard',
|
||||
alias: 'Umb.Dashboard.SettingsWelcome',
|
||||
@@ -65,6 +65,19 @@ export class UmbSectionSettingsElement extends LitElement {
|
||||
pathname: 'published-status',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'dashboard',
|
||||
alias: 'Umb.Dashboard.Profiling',
|
||||
name: 'Profiling',
|
||||
elementName: 'umb-dashboard-performance-profiling',
|
||||
loader: () => import('../../dashboards/performance-profiling/dashboard-performance-profiling.element'),
|
||||
weight: 101,
|
||||
meta: {
|
||||
label: 'Profiling',
|
||||
sections: ['Umb.Section.Settings'],
|
||||
pathname: 'profiling',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'dashboard',
|
||||
alias: 'Umb.Dashboard.Telemetry',
|
||||
|
||||
@@ -122,7 +122,7 @@ export class UmbSectionDashboardsElement extends UmbContextConsumerMixin(UmbObse
|
||||
${this._dashboards.map(
|
||||
(dashboard) => html`
|
||||
<uui-tab
|
||||
href="${`/section/${this._currentSectionPathname}/dashboard/${dashboard.meta.pathname}`}"
|
||||
href="${`section/${this._currentSectionPathname}/dashboard/${dashboard.meta.pathname}`}"
|
||||
label=${dashboard.meta.label || dashboard.name}
|
||||
?active="${dashboard.meta.pathname === this._currentDashboardPathname}"></uui-tab>
|
||||
`
|
||||
|
||||
@@ -59,7 +59,7 @@ export class UmbSectionSidebarElement extends UmbContextConsumerMixin(UmbObserve
|
||||
return html`
|
||||
<umb-tree-context-menu-service>
|
||||
<uui-scroll-container>
|
||||
<a href="${`/section/${this._sectionPathname}`}">
|
||||
<a href="${`section/${this._sectionPathname}`}">
|
||||
<h3>${this._sectionLabel}</h3>
|
||||
</a>
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user