Merge branch 'main' into chore/move-member-group-picker-property-editor-ui
This commit is contained in:
@@ -20,6 +20,7 @@ import { UmbIconRegistry } from '../src/packages/core/icon-registry/icon.registr
|
||||
import { UmbLitElement } from '../src/packages/core/lit-element';
|
||||
import { umbLocalizationRegistry } from '../src/packages/core/localization';
|
||||
import customElementManifests from '../dist-cms/custom-elements.json';
|
||||
import icons from '../src/packages/core/icon-registry/icons/icons';
|
||||
|
||||
import '../src/libs/context-api/provide/context-provider.element';
|
||||
import '../src/packages/core/components';
|
||||
@@ -36,6 +37,7 @@ class UmbStoryBookElement extends UmbLitElement {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._umbIconRegistry.setIcons(icons);
|
||||
this._umbIconRegistry.attach(this);
|
||||
this._registerExtensions(documentManifests);
|
||||
new UmbModalManagerContext(this);
|
||||
|
||||
@@ -18,11 +18,10 @@ const run = async () => {
|
||||
var icons = await collectDictionaryIcons();
|
||||
icons = await collectDiskIcons(icons);
|
||||
writeIconsToDisk(icons);
|
||||
generateJSON(icons);
|
||||
generateJS(icons);
|
||||
};
|
||||
|
||||
const collectDictionaryIcons = async () => {
|
||||
|
||||
const rawData = readFileSync(iconMapJson);
|
||||
const fileRaw = rawData.toString();
|
||||
const fileJSON = JSON.parse(fileRaw);
|
||||
@@ -32,11 +31,11 @@ const collectDictionaryIcons = async () => {
|
||||
// Lucide:
|
||||
fileJSON.lucide.forEach((iconDef) => {
|
||||
if (iconDef.file && iconDef.name) {
|
||||
const path = lucideSvgDirectory + "/" + iconDef.file;
|
||||
const path = lucideSvgDirectory + '/' + iconDef.file;
|
||||
|
||||
try {
|
||||
const rawData = readFileSync(path);
|
||||
// For Lucide icons specially we adjust the icons a bit for them to work in our case:
|
||||
// For Lucide icons specially we adjust the icons a bit for them to work in our case: [NL]
|
||||
let svg = rawData.toString().replace(' width="24"\n', '');
|
||||
svg = svg.replace(' height="24"\n', '');
|
||||
svg = svg.replace('stroke-width="2"', 'stroke-width="1.75"');
|
||||
@@ -60,11 +59,11 @@ const collectDictionaryIcons = async () => {
|
||||
// SimpleIcons:
|
||||
fileJSON.simpleIcons.forEach((iconDef) => {
|
||||
if (iconDef.file && iconDef.name) {
|
||||
const path = simpleIconsSvgDirectory + "/" + iconDef.file;
|
||||
const path = simpleIconsSvgDirectory + '/' + iconDef.file;
|
||||
|
||||
try {
|
||||
const rawData = readFileSync(path);
|
||||
let svg = rawData.toString()
|
||||
let svg = rawData.toString();
|
||||
const iconFileName = iconDef.name;
|
||||
|
||||
// SimpleIcons need to use fill="currentColor"
|
||||
@@ -91,11 +90,11 @@ const collectDictionaryIcons = async () => {
|
||||
// Umbraco:
|
||||
fileJSON.umbraco.forEach((iconDef) => {
|
||||
if (iconDef.file && iconDef.name) {
|
||||
const path = umbracoSvgDirectory + "/" + iconDef.file;
|
||||
const path = umbracoSvgDirectory + '/' + iconDef.file;
|
||||
|
||||
try {
|
||||
const rawData = readFileSync(path);
|
||||
const svg = rawData.toString()
|
||||
const svg = rawData.toString();
|
||||
const iconFileName = iconDef.name;
|
||||
|
||||
const icon = {
|
||||
@@ -136,8 +135,7 @@ const collectDiskIcons = async (icons) => {
|
||||
const iconName = iconFileName;
|
||||
|
||||
// Only append not already defined icons:
|
||||
if (!icons.find(x => x.name === iconName)) {
|
||||
|
||||
if (!icons.find((x) => x.name === iconName)) {
|
||||
const icon = {
|
||||
name: iconName,
|
||||
legacy: true,
|
||||
@@ -169,20 +167,20 @@ const writeIconsToDisk = (icons) => {
|
||||
});
|
||||
};
|
||||
|
||||
const generateJSON = (icons) => {
|
||||
const JSONPath = `${iconsOutputDirectory}/icons.json`;
|
||||
const generateJS = (icons) => {
|
||||
const JSPath = `${iconsOutputDirectory}/icons.ts`;
|
||||
|
||||
const iconDescriptors = icons.map((icon) => {
|
||||
return {
|
||||
name: icon.name,
|
||||
legacy: icon.legacy,
|
||||
path: `./icons/${icon.fileName}.js`,
|
||||
};
|
||||
return `{
|
||||
name: "${icon.name}",
|
||||
${icon.legacy ? 'legacy: true,' : ''}
|
||||
path: "./icons/${icon.fileName}.js",
|
||||
}`.replace(/\t/g, ''); // Regex removes white space [NL]
|
||||
});
|
||||
|
||||
const content = `${JSON.stringify(iconDescriptors)}`;
|
||||
const content = `export default [${iconDescriptors.join(',')}];`;
|
||||
|
||||
writeFileWithDir(JSONPath, content, (err) => {
|
||||
writeFileWithDir(JSPath, content, (err) => {
|
||||
if (err) {
|
||||
// eslint-disable-next-line no-undef
|
||||
console.log(err);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
4
src/Umbraco.Web.UI.Client/package-lock.json
generated
4
src/Umbraco.Web.UI.Client/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@umbraco-cms/backoffice",
|
||||
"version": "14.0.0-rc1",
|
||||
"version": "14.0.0-rc2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@umbraco-cms/backoffice",
|
||||
"version": "14.0.0-rc1",
|
||||
"version": "14.0.0-rc2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/diff": "^5.0.9",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@umbraco-cms/backoffice",
|
||||
"license": "MIT",
|
||||
"version": "14.0.0-rc1",
|
||||
"version": "14.0.0-rc2",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": null,
|
||||
|
||||
@@ -1,19 +1,28 @@
|
||||
import { css, html, nothing, customElement, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { ProblemDetails } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
|
||||
/**
|
||||
* A full page error element that can be used either solo or for instance as the error 500 page and BootFailed
|
||||
*/
|
||||
@customElement('umb-app-error')
|
||||
export class UmbAppErrorElement extends UmbLitElement {
|
||||
/**
|
||||
* The headline to display
|
||||
*
|
||||
* @attr
|
||||
*/
|
||||
@property()
|
||||
errorHeadline?: string | null;
|
||||
|
||||
/**
|
||||
* The error message to display
|
||||
*
|
||||
* @attr
|
||||
*/
|
||||
@property()
|
||||
errorMessage?: string;
|
||||
errorMessage?: string | null;
|
||||
|
||||
/**
|
||||
* The error to display
|
||||
@@ -23,31 +32,128 @@ export class UmbAppErrorElement extends UmbLitElement {
|
||||
@property()
|
||||
error?: unknown;
|
||||
|
||||
private renderProblemDetails = (problemDetails: ProblemDetails) => html`
|
||||
<h2>${problemDetails.title}</h2>
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.#generateErrorFromSearchParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an error from the search params before the properties are set
|
||||
*/
|
||||
#generateErrorFromSearchParams() {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
|
||||
const flow = searchParams.get('flow');
|
||||
|
||||
if (flow === 'external-login-callback') {
|
||||
this.errorHeadline = this.localize.term('errors_externalLoginError');
|
||||
console.log('External login error', searchParams.get('error'));
|
||||
|
||||
const status = searchParams.get('status');
|
||||
|
||||
// "Status" is controlled by Umbraco and is a string
|
||||
if (status) {
|
||||
switch (status) {
|
||||
case 'unauthorized':
|
||||
this.errorMessage = this.localize.term('errors_unauthorized');
|
||||
break;
|
||||
case 'user-not-found':
|
||||
this.errorMessage = this.localize.term('errors_userNotFound');
|
||||
break;
|
||||
case 'external-info-not-found':
|
||||
this.errorMessage = this.localize.term('errors_externalInfoNotFound');
|
||||
break;
|
||||
case 'failed':
|
||||
this.errorMessage = this.localize.term('errors_externalLoginFailed');
|
||||
break;
|
||||
default:
|
||||
this.errorMessage = this.localize.term('errors_defaultError');
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (flow === 'external-login') {
|
||||
/**
|
||||
* "Error" is controlled by OpenID and is a string
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1
|
||||
*/
|
||||
const error = searchParams.get('error');
|
||||
|
||||
this.errorHeadline = this.localize.term('errors_externalLoginError');
|
||||
|
||||
switch (error) {
|
||||
case 'access_denied':
|
||||
this.errorMessage = this.localize.term('openidErrors_accessDenied');
|
||||
break;
|
||||
case 'invalid_request':
|
||||
this.errorMessage = this.localize.term('openidErrors_invalidRequest');
|
||||
break;
|
||||
case 'invalid_client':
|
||||
this.errorMessage = this.localize.term('openidErrors_invalidClient');
|
||||
break;
|
||||
case 'invalid_grant':
|
||||
this.errorMessage = this.localize.term('openidErrors_invalidGrant');
|
||||
break;
|
||||
case 'unauthorized_client':
|
||||
this.errorMessage = this.localize.term('openidErrors_unauthorizedClient');
|
||||
break;
|
||||
case 'unsupported_grant_type':
|
||||
this.errorMessage = this.localize.term('openidErrors_unsupportedGrantType');
|
||||
break;
|
||||
case 'invalid_scope':
|
||||
this.errorMessage = this.localize.term('openidErrors_invalidScope');
|
||||
break;
|
||||
case 'server_error':
|
||||
this.errorMessage = this.localize.term('openidErrors_serverError');
|
||||
break;
|
||||
case 'temporarily_unavailable':
|
||||
this.errorMessage = this.localize.term('openidErrors_temporarilyUnavailable');
|
||||
break;
|
||||
default:
|
||||
this.errorMessage = this.localize.term('errors_defaultError');
|
||||
break;
|
||||
}
|
||||
|
||||
// Set the error object with the original error parameters from the search params
|
||||
let detail = searchParams.get('error_description');
|
||||
const errorUri = searchParams.get('error_uri');
|
||||
if (errorUri) {
|
||||
detail = `${detail} (${errorUri})`;
|
||||
}
|
||||
this.error = { title: `External error code: ${error}`, detail };
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#renderProblemDetails = (problemDetails: ProblemDetails) => html`
|
||||
<p><strong>${problemDetails.title}</strong></p>
|
||||
<p>${problemDetails.detail}</p>
|
||||
<pre>${problemDetails.stack}</pre>
|
||||
`;
|
||||
|
||||
private renderErrorObj = (error: Error) => html`
|
||||
<h2>${error.name}</h2>
|
||||
#renderErrorObj = (error: Error) => html`
|
||||
<p><strong>${error.name}</strong></p>
|
||||
<p>${error.message}</p>
|
||||
<pre>${error.stack}</pre>
|
||||
`;
|
||||
|
||||
private isProblemDetails(error: unknown): error is ProblemDetails {
|
||||
#isProblemDetails(error: unknown): error is ProblemDetails {
|
||||
return typeof error === 'object' && error !== null && 'detail' in error && 'title' in error;
|
||||
}
|
||||
|
||||
private isError(error: unknown): error is Error {
|
||||
#isError(error: unknown): error is Error {
|
||||
return typeof error === 'object' && error !== null && error instanceof Error;
|
||||
}
|
||||
|
||||
private renderError(error: unknown) {
|
||||
if (this.isProblemDetails(error)) {
|
||||
return this.renderProblemDetails(error);
|
||||
} else if (this.isError(error)) {
|
||||
return this.renderErrorObj(error);
|
||||
#renderError(error: unknown) {
|
||||
if (this.#isProblemDetails(error)) {
|
||||
return this.#renderProblemDetails(error);
|
||||
} else if (this.#isError(error)) {
|
||||
return this.#renderErrorObj(error);
|
||||
}
|
||||
|
||||
return nothing;
|
||||
@@ -56,73 +162,93 @@ export class UmbAppErrorElement extends UmbLitElement {
|
||||
render = () => html`
|
||||
<div id="background"></div>
|
||||
|
||||
<div id="logo">
|
||||
<img src="/umbraco/backoffice/assets/umbraco_logomark_white.svg'" alt="Umbraco" />
|
||||
<div id="logo" aria-hidden="true">
|
||||
<img src="/umbraco/backoffice/assets/umbraco_logomark_white.svg" alt="Umbraco" />
|
||||
</div>
|
||||
|
||||
<div id="container">
|
||||
<uui-box id="box">
|
||||
<h1>Something went wrong</h1>
|
||||
<p>${this.errorMessage}</p>
|
||||
<div id="container" class="uui-text">
|
||||
<uui-box id="box" headline-variant="h1">
|
||||
<uui-button
|
||||
slot="header-actions"
|
||||
label=${this.localize.term('general_back')}
|
||||
look="secondary"
|
||||
@click=${() => (location.href = '')}></uui-button>
|
||||
<div slot="headline">
|
||||
${this.errorHeadline
|
||||
? this.errorHeadline
|
||||
: html` <umb-localize key="errors_defaultError">An unknown failure has occured</umb-localize> `}
|
||||
</div>
|
||||
<div id="message">${this.errorMessage}</div>
|
||||
${this.error
|
||||
? html`
|
||||
<details>
|
||||
<summary>Details</summary>
|
||||
${this.renderError(this.error)}
|
||||
<summary><umb-localize key="general_details">Details</umb-localize></summary>
|
||||
${this.#renderError(this.error)}
|
||||
</details>
|
||||
`
|
||||
`
|
||||
: nothing}
|
||||
</uui-box>
|
||||
</div>
|
||||
`;
|
||||
|
||||
static styles = css`
|
||||
#background {
|
||||
position: fixed;
|
||||
overflow: hidden;
|
||||
background-position: 50%;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-image: url('/umbraco/backoffice/assets/umbraco_background.jpg');
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
static styles = [
|
||||
UmbTextStyles,
|
||||
css`
|
||||
#background {
|
||||
position: fixed;
|
||||
overflow: hidden;
|
||||
background-position: 50%;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-image: url('/umbraco/backoffice/assets/installer-illustration.svg');
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
#logo {
|
||||
position: fixed;
|
||||
top: var(--uui-size-space-5);
|
||||
left: var(--uui-size-space-5);
|
||||
height: 30px;
|
||||
}
|
||||
#logo {
|
||||
position: fixed;
|
||||
top: var(--uui-size-space-5);
|
||||
left: var(--uui-size-space-5);
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
#logo img {
|
||||
height: 100%;
|
||||
}
|
||||
#logo img {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
#container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
#box {
|
||||
width: 50vw;
|
||||
padding: var(--uui-size-space-6) var(--uui-size-space-5) var(--uui-size-space-5) var(--uui-size-space-5);
|
||||
}
|
||||
#box {
|
||||
width: 400px;
|
||||
max-width: 80vw;
|
||||
}
|
||||
|
||||
details {
|
||||
padding: var(--uui-size-space-2) var(--uui-size-space-3);
|
||||
background: var(--uui-color-surface-alt);
|
||||
}
|
||||
#message {
|
||||
margin-bottom: var(--uui-size-space-3);
|
||||
}
|
||||
|
||||
pre {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
`;
|
||||
details {
|
||||
padding: var(--uui-size-space-2) var(--uui-size-space-3);
|
||||
background: var(--uui-color-surface-alt);
|
||||
}
|
||||
|
||||
details summary {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
pre {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
export default UmbAppErrorElement;
|
||||
|
||||
@@ -7,7 +7,6 @@ import type { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth';
|
||||
import { UmbAuthContext } from '@umbraco-cms/backoffice/auth';
|
||||
import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UUIIconRegistryEssential } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UmbIconRegistry } from '@umbraco-cms/backoffice/icon';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import type { Guard, UmbRoute } from '@umbraco-cms/backoffice/router';
|
||||
import { pathWithoutBasePath } from '@umbraco-cms/backoffice/router';
|
||||
@@ -50,6 +49,10 @@ export class UmbAppElement extends UmbLitElement {
|
||||
bypassAuth = false;
|
||||
|
||||
private _routes: UmbRoute[] = [
|
||||
{
|
||||
path: 'error',
|
||||
component: () => import('./app-error.element.js'),
|
||||
},
|
||||
{
|
||||
path: 'install',
|
||||
component: () => import('../installer/installer.element.js'),
|
||||
@@ -85,7 +88,6 @@ export class UmbAppElement extends UmbLitElement {
|
||||
new UmbBundleExtensionInitializer(this, umbExtensionsRegistry);
|
||||
new UmbAppEntryPointExtensionInitializer(this, umbExtensionsRegistry);
|
||||
|
||||
new UmbIconRegistry().attach(this);
|
||||
new UUIIconRegistryEssential().attach(this);
|
||||
|
||||
new UmbContextDebugController(this);
|
||||
|
||||
@@ -10,33 +10,31 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
|
||||
import './components/index.js';
|
||||
|
||||
// TODO: temp solution to load core packages
|
||||
const CORE_PACKAGES = [
|
||||
import('../../packages/audit-log/umbraco-package.js'),
|
||||
import('../../packages/block/umbraco-package.js'),
|
||||
import('../../packages/data-type/umbraco-package.js'),
|
||||
import('../../packages/dictionary/umbraco-package.js'),
|
||||
import('../../packages/umbraco-news/umbraco-package.js'),
|
||||
import('../../packages/documents/umbraco-package.js'),
|
||||
import('../../packages/dynamic-root/umbraco-package.js'),
|
||||
import('../../packages/health-check/umbraco-package.js'),
|
||||
import('../../packages/language/umbraco-package.js'),
|
||||
import('../../packages/log-viewer/umbraco-package.js'),
|
||||
import('../../packages/markdown-editor/umbraco-package.js'),
|
||||
import('../../packages/data-type/umbraco-package.js'),
|
||||
import('../../packages/media/umbraco-package.js'),
|
||||
import('../../packages/members/umbraco-package.js'),
|
||||
import('../../packages/models-builder/umbraco-package.js'),
|
||||
//import('../../packages/object-type/umbraco-package.js'),// This had nothing to register.
|
||||
import('../../packages/packages/umbraco-package.js'),
|
||||
import('../../packages/relations/umbraco-package.js'),
|
||||
import('../../packages/search/umbraco-package.js'),
|
||||
import('../../packages/settings/umbraco-package.js'),
|
||||
import('../../packages/language/umbraco-package.js'),
|
||||
import('../../packages/static-file/umbraco-package.js'),
|
||||
import('../../packages/dynamic-root/umbraco-package.js'),
|
||||
import('../../packages/block/umbraco-package.js'),
|
||||
import('../../packages/tags/umbraco-package.js'),
|
||||
import('../../packages/templating/umbraco-package.js'),
|
||||
import('../../packages/tiny-mce/umbraco-package.js'),
|
||||
import('../../packages/umbraco-news/umbraco-package.js'),
|
||||
import('../../packages/markdown-editor/umbraco-package.js'),
|
||||
import('../../packages/templating/umbraco-package.js'),
|
||||
import('../../packages/dictionary/umbraco-package.js'),
|
||||
import('../../packages/user/umbraco-package.js'),
|
||||
import('../../packages/health-check/umbraco-package.js'),
|
||||
import('../../packages/audit-log/umbraco-package.js'),
|
||||
import('../../packages/webhook/umbraco-package.js'),
|
||||
import('../../packages/relations/umbraco-package.js'),
|
||||
import('../../packages/models-builder/umbraco-package.js'),
|
||||
import('../../packages/log-viewer/umbraco-package.js'),
|
||||
import('../../packages/packages/umbraco-package.js'),
|
||||
];
|
||||
|
||||
@customElement('umb-backoffice')
|
||||
@@ -55,13 +53,13 @@ export class UmbBackofficeElement extends UmbLitElement {
|
||||
new UmbBackofficeEntryPointExtensionInitializer(this, umbExtensionsRegistry);
|
||||
new UmbEntryPointExtensionInitializer(this, umbExtensionsRegistry);
|
||||
|
||||
new UmbServerExtensionRegistrator(this, umbExtensionsRegistry).registerPrivateExtensions();
|
||||
|
||||
// So far local packages are this simple to registerer, so no need for a manager to do that:
|
||||
CORE_PACKAGES.forEach(async (packageImport) => {
|
||||
const packageModule = await packageImport;
|
||||
umbExtensionsRegistry.registerMany(packageModule.extensions);
|
||||
});
|
||||
|
||||
new UmbServerExtensionRegistrator(this, umbExtensionsRegistry).registerPrivateExtensions();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -81,7 +81,7 @@ export class UmbBackofficeHeaderSectionsElement extends UmbLitElement {
|
||||
#tabs {
|
||||
height: 60px;
|
||||
flex-basis: 100%;
|
||||
font-size: 16px;
|
||||
font-size: 16px; /* specific for the header */
|
||||
--uui-tab-text: var(--uui-color-header-contrast);
|
||||
--uui-tab-text-hover: var(--uui-color-header-contrast-emphasis);
|
||||
--uui-tab-text-active: var(--uui-color-header-contrast-emphasis);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { UmbBackofficeContext } from '../backoffice.context.js';
|
||||
import { UMB_BACKOFFICE_CONTEXT } from '../backoffice.context.js';
|
||||
import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, html, customElement, state, nothing } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbSectionContext, UMB_SECTION_CONTEXT } from '@umbraco-cms/backoffice/section';
|
||||
import type { UmbRoute, UmbRouterSlotChangeEvent } from '@umbraco-cms/backoffice/router';
|
||||
import type { ManifestSection, UmbSectionElement } from '@umbraco-cms/backoffice/extension-registry';
|
||||
@@ -99,17 +99,18 @@ export class UmbBackofficeMainElement extends UmbLitElement {
|
||||
render() {
|
||||
return this._routes.length > 0
|
||||
? html`<umb-router-slot .routes=${this._routes} @change=${this._onRouteChange}></umb-router-slot>`
|
||||
: '';
|
||||
: nothing;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
css`
|
||||
:host {
|
||||
background-color: var(--uui-color-background);
|
||||
display: block;
|
||||
background-color: var(--uui-color-background);
|
||||
width: 100%;
|
||||
height: calc(
|
||||
100% - 60px
|
||||
); // 60 => top header height, TODO: Make sure this comes from somewhere so it is maintainable and eventually responsive.
|
||||
); /* 60 => top header height, TODO: Make sure this comes from somewhere so it is maintainable and eventually responsive. */
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -690,6 +690,8 @@ export default {
|
||||
errorRegExpWithoutTab: '%0% er ikke i et korrekt format',
|
||||
},
|
||||
errors: {
|
||||
defaultError: 'Der er sket en ukendt fejl',
|
||||
concurrencyError: 'Optimistisk samtidighedsfejl, objektet er blevet ændret',
|
||||
receivedErrorFromServer: 'Der skete en fejl på severen',
|
||||
dissallowedMediaType: 'Denne filttype er blevet deaktiveret af administratoren',
|
||||
codemirroriewarning:
|
||||
@@ -707,8 +709,22 @@ export default {
|
||||
tableColMergeLeft: 'Du skal stå til venstre for de 2 celler du ønsker at samle!',
|
||||
tableSplitNotSplittable: 'Du kan ikke opdele en celle, som ikke allerede er delt.',
|
||||
propertyHasErrors: 'Denne egenskab er ugyldig',
|
||||
defaultError: 'An unknown failure has occurred',
|
||||
concurrencyError: 'Optimistic concurrency failure, object has been modified',
|
||||
externalLoginError: 'Der opstod en fejl under login med eksternt login',
|
||||
unauthorized: 'Du har ikke tilladelse til at udføre denne handling',
|
||||
userNotFound: 'Den angivne bruger blev ikke fundet i databasen',
|
||||
externalInfoNotFound: 'Serveren kunne ikke kommunikere med den eksterne loginudbyder',
|
||||
externalLoginFailed: 'Serveren mislykkedes i at logge ind med den eksterne loginudbyder',
|
||||
},
|
||||
openidErrors: {
|
||||
accessDenied: 'Access denied',
|
||||
invalidRequest: 'Ugyldig forespørgsel',
|
||||
invalidClient: 'Ugyldig klient',
|
||||
invalidGrant: 'Ugyldig tildeling',
|
||||
unauthorizedClient: 'Uautoriseret klient',
|
||||
unsupportedGrantType: 'Ikke understøttet tildelingstype',
|
||||
invalidScope: 'Ugyldigt område',
|
||||
serverError: 'Serverfejl',
|
||||
temporarilyUnavailable: 'Servicen er midlertidigt utilgængelig',
|
||||
},
|
||||
general: {
|
||||
options: 'Valgmuligheder',
|
||||
|
||||
@@ -714,6 +714,22 @@ export default {
|
||||
tableColMergeLeft: 'Please place cursor at the left of the two cells you wish to merge',
|
||||
tableSplitNotSplittable: "You cannot split a cell that hasn't been merged.",
|
||||
propertyHasErrors: 'This property is invalid',
|
||||
externalLoginError: 'External login',
|
||||
unauthorized: 'You were not authorized before performing this action',
|
||||
userNotFound: 'The local user was not found in the database',
|
||||
externalInfoNotFound: 'The server did not succeed in communicating with the external login provider',
|
||||
externalLoginFailed: 'The server failed to authorize you against the external login provider',
|
||||
},
|
||||
openidErrors: {
|
||||
accessDenied: 'Access denied',
|
||||
invalidRequest: 'Invalid request',
|
||||
invalidClient: 'Invalid client',
|
||||
invalidGrant: 'Invalid grant',
|
||||
unauthorizedClient: 'Unauthorized client',
|
||||
unsupportedGrantType: 'Unsupported grant type',
|
||||
invalidScope: 'Invalid scope',
|
||||
serverError: 'Server error',
|
||||
temporarilyUnavailable: 'The service is temporarily unavailable',
|
||||
},
|
||||
general: {
|
||||
options: 'Options',
|
||||
|
||||
@@ -3,151 +3,164 @@ const { rest } = window.MockServiceWorker;
|
||||
import type { PackageManifestResponse } from '../../packages/packages/types.js';
|
||||
import { umbracoPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
const privateManifests: PackageManifestResponse = [
|
||||
{
|
||||
name: 'My Package Name',
|
||||
version: '1.0.0',
|
||||
extensions: [
|
||||
{
|
||||
type: 'bundle',
|
||||
alias: 'My.Package.Bundle',
|
||||
name: 'My Package Bundle',
|
||||
js: '/App_Plugins/custom-bundle-package/index.js',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Named Package',
|
||||
version: '1.0.0',
|
||||
extensions: [
|
||||
{
|
||||
type: 'section',
|
||||
alias: 'My.Section.Custom',
|
||||
name: 'Custom Section',
|
||||
js: '/App_Plugins/section.js',
|
||||
elementName: 'my-section-custom',
|
||||
weight: 1,
|
||||
meta: {
|
||||
label: 'Custom',
|
||||
pathname: 'my-custom',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'propertyEditorUi',
|
||||
alias: 'My.PropertyEditorUI.Custom',
|
||||
name: 'My Custom Property Editor UI',
|
||||
js: '/App_Plugins/property-editor.js',
|
||||
elementName: 'my-property-editor-ui-custom',
|
||||
meta: {
|
||||
label: 'My Custom Property',
|
||||
icon: 'document',
|
||||
group: 'Common',
|
||||
propertyEditorSchema: 'Umbraco.TextBox',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Package with an entry point',
|
||||
extensions: [
|
||||
{
|
||||
type: 'backofficeEntryPoint',
|
||||
name: 'My Custom Entry Point',
|
||||
alias: 'My.Entrypoint.Custom',
|
||||
js: '/App_Plugins/custom-entrypoint.js',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'My MFA Package',
|
||||
extensions: [
|
||||
{
|
||||
type: 'mfaLoginProvider',
|
||||
alias: 'My.MfaLoginProvider.Custom.Google',
|
||||
name: 'My Custom Google MFA Provider',
|
||||
forProviderName: 'Google Authenticator',
|
||||
},
|
||||
{
|
||||
type: 'mfaLoginProvider',
|
||||
alias: 'My.MfaLoginProvider.Custom.SMS',
|
||||
name: 'My Custom SMS MFA Provider',
|
||||
forProviderName: 'sms',
|
||||
meta: {
|
||||
label: 'Setup SMS Verification',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Package with a view',
|
||||
extensions: [
|
||||
{
|
||||
type: 'packageView',
|
||||
alias: 'My.PackageView.Custom',
|
||||
name: 'My Custom Package View',
|
||||
js: '/App_Plugins/package-view.js',
|
||||
meta: {
|
||||
packageName: 'Package with a view',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'My MFA Package',
|
||||
extensions: [
|
||||
{
|
||||
type: 'mfaLoginProvider',
|
||||
alias: 'My.MfaLoginProvider.Custom',
|
||||
name: 'My Custom MFA Provider',
|
||||
forProviderName: 'sms',
|
||||
meta: {
|
||||
label: 'Setup SMS Verification',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const publicManifests: PackageManifestResponse = [
|
||||
{
|
||||
name: 'My Auth Package',
|
||||
extensions: [
|
||||
{
|
||||
type: 'authProvider',
|
||||
alias: 'My.AuthProvider.Google',
|
||||
name: 'My Custom Auth Provider',
|
||||
forProviderName: 'Umbraco.Google',
|
||||
meta: {
|
||||
label: 'Google',
|
||||
defaultView: {
|
||||
icon: 'icon-google',
|
||||
},
|
||||
linking: {
|
||||
allowManualLinking: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'authProvider',
|
||||
alias: 'My.AuthProvider.Github',
|
||||
name: 'My Github Auth Provider',
|
||||
forProviderName: 'Umbraco.Github',
|
||||
meta: {
|
||||
label: 'GitHub',
|
||||
defaultView: {
|
||||
look: 'primary',
|
||||
icon: 'icon-github',
|
||||
color: 'success',
|
||||
},
|
||||
linking: {
|
||||
allowManualLinking: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const manifestDevelopmentHandlers = [
|
||||
rest.get(umbracoPath('/manifest/manifest/private'), (_req, res, ctx) => {
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(200),
|
||||
ctx.json<PackageManifestResponse>([
|
||||
{
|
||||
name: 'My Package Name',
|
||||
version: '1.0.0',
|
||||
extensions: [
|
||||
{
|
||||
type: 'bundle',
|
||||
alias: 'My.Package.Bundle',
|
||||
name: 'My Package Bundle',
|
||||
js: '/App_Plugins/custom-bundle-package/index.js',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Named Package',
|
||||
version: '1.0.0',
|
||||
extensions: [
|
||||
{
|
||||
type: 'section',
|
||||
alias: 'My.Section.Custom',
|
||||
name: 'Custom Section',
|
||||
js: '/App_Plugins/section.js',
|
||||
elementName: 'my-section-custom',
|
||||
weight: 1,
|
||||
meta: {
|
||||
label: 'Custom',
|
||||
pathname: 'my-custom',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'propertyEditorUi',
|
||||
alias: 'My.PropertyEditorUI.Custom',
|
||||
name: 'My Custom Property Editor UI',
|
||||
js: '/App_Plugins/property-editor.js',
|
||||
elementName: 'my-property-editor-ui-custom',
|
||||
meta: {
|
||||
label: 'My Custom Property',
|
||||
icon: 'document',
|
||||
group: 'Common',
|
||||
propertyEditorSchema: 'Umbraco.TextBox',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Package with an entry point',
|
||||
extensions: [
|
||||
{
|
||||
type: 'backofficeEntryPoint',
|
||||
name: 'My Custom Entry Point',
|
||||
alias: 'My.Entrypoint.Custom',
|
||||
js: '/App_Plugins/custom-entrypoint.js',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'My MFA Package',
|
||||
extensions: [
|
||||
{
|
||||
type: 'mfaLoginProvider',
|
||||
alias: 'My.MfaLoginProvider.Custom.Google',
|
||||
name: 'My Custom Google MFA Provider',
|
||||
forProviderName: 'Google Authenticator',
|
||||
},
|
||||
{
|
||||
type: 'mfaLoginProvider',
|
||||
alias: 'My.MfaLoginProvider.Custom.SMS',
|
||||
name: 'My Custom SMS MFA Provider',
|
||||
forProviderName: 'sms',
|
||||
meta: {
|
||||
label: 'Setup SMS Verification',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Package with a view',
|
||||
extensions: [
|
||||
{
|
||||
type: 'packageView',
|
||||
alias: 'My.PackageView.Custom',
|
||||
name: 'My Custom Package View',
|
||||
js: '/App_Plugins/package-view.js',
|
||||
meta: {
|
||||
packageName: 'Package with a view',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'My MFA Package',
|
||||
extensions: [
|
||||
{
|
||||
type: 'mfaLoginProvider',
|
||||
alias: 'My.MfaLoginProvider.Custom',
|
||||
name: 'My Custom MFA Provider',
|
||||
forProviderName: 'sms',
|
||||
meta: {
|
||||
label: 'Setup SMS Verification',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
ctx.json<PackageManifestResponse>(privateManifests),
|
||||
);
|
||||
}),
|
||||
rest.get(umbracoPath('/manifest/manifest/public'), (_req, res, ctx) => {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json<PackageManifestResponse>([
|
||||
{
|
||||
name: 'My Auth Package',
|
||||
extensions: [
|
||||
{
|
||||
type: 'authProvider',
|
||||
alias: 'My.AuthProvider.Google',
|
||||
name: 'My Custom Auth Provider',
|
||||
forProviderName: 'Umbraco.Google',
|
||||
meta: {
|
||||
label: 'Sign in with Google',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'authProvider',
|
||||
alias: 'My.AuthProvider.Github',
|
||||
name: 'My Github Auth Provider',
|
||||
forProviderName: 'Umbraco.Github',
|
||||
meta: {
|
||||
label: 'GitHub',
|
||||
defaultView: {
|
||||
look: 'primary',
|
||||
icon: 'icon-github',
|
||||
color: 'success',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
);
|
||||
return res(ctx.status(200), ctx.json<PackageManifestResponse>(publicManifests));
|
||||
}),
|
||||
rest.get(umbracoPath('/manifest/manifest'), (_req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json<PackageManifestResponse>([...privateManifests, ...publicManifests]));
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -158,4 +171,7 @@ export const manifestEmptyHandlers = [
|
||||
rest.get(umbracoPath('/manifest/manifest/public'), (_req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json<PackageManifestResponse>([]));
|
||||
}),
|
||||
rest.get(umbracoPath('/manifest/manifest'), (_req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json<PackageManifestResponse>([]));
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const { rest } = window.MockServiceWorker;
|
||||
import { umbUserMockDb } from '../../data/user/user.db.js';
|
||||
import { UMB_SLUG } from './slug.js';
|
||||
import type { LinkedLoginsRequestModel } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import { umbracoPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
export const handlers = [
|
||||
@@ -8,6 +9,19 @@ export const handlers = [
|
||||
const loggedInUser = umbUserMockDb.getCurrentUser();
|
||||
return res(ctx.status(200), ctx.json(loggedInUser));
|
||||
}),
|
||||
rest.get<LinkedLoginsRequestModel>(umbracoPath(`${UMB_SLUG}/current/logins`), (_req, res, ctx) => {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json<LinkedLoginsRequestModel>({
|
||||
linkedLogins: [
|
||||
{
|
||||
providerKey: 'google',
|
||||
providerName: 'Umbraco.Google',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
}),
|
||||
rest.get(umbracoPath(`${UMB_SLUG}/current/2fa`), (_req, res, ctx) => {
|
||||
const mfaLoginProviders = umbUserMockDb.getMfaLoginProviders();
|
||||
return res(ctx.status(200), ctx.json(mfaLoginProviders));
|
||||
|
||||
@@ -92,7 +92,10 @@ export class UmbExtensionSlotElement extends UmbLitElement {
|
||||
public defaultElement?: string;
|
||||
|
||||
@property()
|
||||
public renderMethod?: (extension: UmbExtensionElementInitializer) => TemplateResult | HTMLElement | null | undefined;
|
||||
public renderMethod?: (
|
||||
extension: UmbExtensionElementInitializer,
|
||||
index: number,
|
||||
) => TemplateResult | HTMLElement | null | undefined;
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
@@ -130,7 +133,7 @@ export class UmbExtensionSlotElement extends UmbLitElement {
|
||||
? repeat(
|
||||
this._permitted,
|
||||
(ext) => ext.alias,
|
||||
(ext) => (this.renderMethod ? this.renderMethod(ext) : ext.component),
|
||||
(ext, i) => (this.renderMethod ? this.renderMethod(ext, i) : ext.component),
|
||||
)
|
||||
: html`<slot></slot>`;
|
||||
}
|
||||
|
||||
@@ -139,6 +139,7 @@ export class UmbExtensionWithApiSlotElement extends UmbLitElement {
|
||||
@property()
|
||||
public renderMethod?: (
|
||||
extension: UmbExtensionElementAndApiInitializer,
|
||||
index: number,
|
||||
) => TemplateResult | HTMLElement | null | undefined;
|
||||
|
||||
connectedCallback(): void {
|
||||
@@ -181,7 +182,7 @@ export class UmbExtensionWithApiSlotElement extends UmbLitElement {
|
||||
? repeat(
|
||||
this._permitted,
|
||||
(ext) => ext.alias,
|
||||
(ext) => (this.renderMethod ? this.renderMethod(ext) : ext.component),
|
||||
(ext, i) => (this.renderMethod ? this.renderMethod(ext, i) : ext.component),
|
||||
)
|
||||
: html`<slot></slot>`;
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeMode
|
||||
|
||||
/** Manipulate methods: */
|
||||
|
||||
async insertContainer(container: UmbPropertyTypeContainerModel, sortOrder = 0) {
|
||||
/*async insertContainer(container: UmbPropertyTypeContainerModel, sortOrder = 0) {
|
||||
await this.#init;
|
||||
if (!this.#structure) return false;
|
||||
|
||||
@@ -254,7 +254,7 @@ export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeMode
|
||||
|
||||
await this.#structure.insertContainer(null, newContainer);
|
||||
return true;
|
||||
}
|
||||
}*/
|
||||
|
||||
async addContainer(parentContainerId?: string | null, sortOrder?: number) {
|
||||
if (!this.#structure) return;
|
||||
|
||||
@@ -278,6 +278,22 @@ export class UmbContentTypeStructureManager<
|
||||
return clonedContainer;
|
||||
}
|
||||
|
||||
ensureContainerNames(
|
||||
contentTypeUnique: string | null,
|
||||
type: UmbPropertyContainerTypes,
|
||||
parentId: string | null = null,
|
||||
) {
|
||||
contentTypeUnique = contentTypeUnique ?? this.#ownerContentTypeUnique!;
|
||||
this.getOwnerContainers(type, parentId)?.forEach((container) => {
|
||||
if (container.name === '') {
|
||||
const newName = 'Unnamed';
|
||||
this.updateContainer(null, container.id, {
|
||||
name: this.makeContainerNameUniqueForOwnerContentType(container.id, newName, type, parentId) ?? newName,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async createContainer(
|
||||
contentTypeUnique: string | null,
|
||||
parentId: string | null = null,
|
||||
@@ -295,9 +311,11 @@ export class UmbContentTypeStructureManager<
|
||||
sortOrder: sortOrder ?? 0,
|
||||
};
|
||||
|
||||
const containers = [
|
||||
...(this.#contentTypes.getValue().find((x) => x.unique === contentTypeUnique)?.containers ?? []),
|
||||
];
|
||||
// Ensure
|
||||
this.ensureContainerNames(contentTypeUnique, type, parentId);
|
||||
|
||||
const contentTypes = this.#contentTypes.getValue();
|
||||
const containers = [...(contentTypes.find((x) => x.unique === contentTypeUnique)?.containers ?? [])];
|
||||
containers.push(container);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
@@ -308,7 +326,7 @@ export class UmbContentTypeStructureManager<
|
||||
return container;
|
||||
}
|
||||
|
||||
async insertContainer(contentTypeUnique: string | null, container: UmbPropertyTypeContainerModel) {
|
||||
/*async insertContainer(contentTypeUnique: string | null, container: UmbPropertyTypeContainerModel) {
|
||||
await this.#init;
|
||||
contentTypeUnique = contentTypeUnique ?? this.#ownerContentTypeUnique!;
|
||||
|
||||
@@ -330,24 +348,34 @@ export class UmbContentTypeStructureManager<
|
||||
// @ts-ignore
|
||||
// TODO: fix TS partial complaint
|
||||
this.#contentTypes.updateOne(contentTypeUnique, { containers });
|
||||
}
|
||||
}*/
|
||||
|
||||
makeEmptyContainerName(
|
||||
containerId: string,
|
||||
containerType: UmbPropertyContainerTypes,
|
||||
parentId: string | null = null,
|
||||
) {
|
||||
return (
|
||||
this.makeContainerNameUniqueForOwnerContentType(containerId, 'Unnamed', containerType, parentId) ?? 'Unnamed'
|
||||
);
|
||||
}
|
||||
makeContainerNameUniqueForOwnerContentType(
|
||||
containerId: string,
|
||||
newName: string,
|
||||
containerType: UmbPropertyContainerTypes = 'Tab',
|
||||
containerType: UmbPropertyContainerTypes,
|
||||
parentId: string | null = null,
|
||||
) {
|
||||
const ownerRootContainers = this.getOwnerContainers(containerType, parentId); //getRootContainers() can't differentiates between compositions and locals
|
||||
if (!ownerRootContainers) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let changedName = newName;
|
||||
if (ownerRootContainers) {
|
||||
while (ownerRootContainers.find((tab) => tab.name === changedName && tab.id !== parentId)) {
|
||||
changedName = incrementString(changedName);
|
||||
}
|
||||
|
||||
return changedName === newName ? null : changedName;
|
||||
while (ownerRootContainers.find((con) => con.name === changedName && con.id !== containerId)) {
|
||||
changedName = incrementString(changedName);
|
||||
}
|
||||
return null;
|
||||
|
||||
return changedName === newName ? null : changedName;
|
||||
}
|
||||
|
||||
async updateContainer(
|
||||
|
||||
@@ -6,7 +6,7 @@ export type UmbPropertyContainerTypes = 'Group' | 'Tab';
|
||||
export interface UmbPropertyTypeContainerModel {
|
||||
id: string;
|
||||
parent: { id: string } | null; // TODO: change to unique
|
||||
name: string | null;
|
||||
name: string;
|
||||
type: UmbPropertyContainerTypes;
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { css, html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement, umbFocus } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { css, html, customElement, property, state, nothing } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type {
|
||||
UmbContentTypeContainerStructureHelper,
|
||||
UmbContentTypeModel,
|
||||
@@ -92,11 +92,24 @@ export class UmbContentTypeWorkspaceViewEditGroupElement extends UmbLitElement {
|
||||
let newName = (e.target as HTMLInputElement).value;
|
||||
const changedName = this.groupStructureHelper
|
||||
.getStructureManager()!
|
||||
.makeContainerNameUniqueForOwnerContentType(newName, 'Group', this._group.parent?.id ?? null);
|
||||
.makeContainerNameUniqueForOwnerContentType(this._group.id, newName, 'Group', this._group.parent?.id ?? null);
|
||||
if (changedName) {
|
||||
newName = changedName;
|
||||
}
|
||||
this._singleValueUpdate('name', newName);
|
||||
(e.target as HTMLInputElement).value = newName;
|
||||
}
|
||||
|
||||
#blurGroup(e: InputEvent) {
|
||||
if (!this.groupStructureHelper || !this._group) return;
|
||||
const newName = (e.target as HTMLInputElement).value;
|
||||
if (newName === '') {
|
||||
const changedName = this.groupStructureHelper
|
||||
.getStructureManager()!
|
||||
.makeEmptyContainerName(this._group.id, 'Group', this._group.parent?.id ?? null);
|
||||
this._singleValueUpdate('name', changedName);
|
||||
(e.target as HTMLInputElement).value = changedName;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -118,9 +131,11 @@ export class UmbContentTypeWorkspaceViewEditGroupElement extends UmbLitElement {
|
||||
<uui-input
|
||||
label=${this.localize.term('contentTypeEditor_group')}
|
||||
placeholder=${this.localize.term('placeholders_entername')}
|
||||
.value=${this.group!.name}
|
||||
.value=${this._group!.name}
|
||||
?disabled=${!this._hasOwnerContainer}
|
||||
@change=${this.#renameGroup}></uui-input>
|
||||
@change=${this.#renameGroup}
|
||||
@blur=${this.#blurGroup}
|
||||
${this._group!.name === '' ? umbFocus() : nothing}></uui-input>
|
||||
</div>
|
||||
${this.sortModeActive
|
||||
? html` <uui-input
|
||||
|
||||
@@ -116,14 +116,22 @@ export class UmbContentTypeDesignEditorTabElement extends UmbLitElement {
|
||||
'_observeIsSorting',
|
||||
);
|
||||
});
|
||||
this.observe(this.#groupStructureHelper.mergedContainers, (groups) => {
|
||||
this._groups = groups;
|
||||
this.#sorter.setModel(this._groups);
|
||||
});
|
||||
this.observe(this.#groupStructureHelper.hasProperties, (hasProperties) => {
|
||||
this._hasProperties = hasProperties;
|
||||
this.requestUpdate('_hasProperties');
|
||||
});
|
||||
this.observe(
|
||||
this.#groupStructureHelper.mergedContainers,
|
||||
(groups) => {
|
||||
this._groups = groups;
|
||||
this.#sorter.setModel(this._groups);
|
||||
},
|
||||
null,
|
||||
);
|
||||
this.observe(
|
||||
this.#groupStructureHelper.hasProperties,
|
||||
(hasProperties) => {
|
||||
this._hasProperties = hasProperties;
|
||||
this.requestUpdate('_hasProperties');
|
||||
},
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
#onAddGroup = () => {
|
||||
|
||||
@@ -292,9 +292,9 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
|
||||
let newName = (event.target as HTMLInputElement).value;
|
||||
|
||||
const changedName = this.#workspaceContext?.structure.makeContainerNameUniqueForOwnerContentType(
|
||||
tab.id,
|
||||
newName,
|
||||
'Tab',
|
||||
tab.id,
|
||||
);
|
||||
|
||||
// Check if it collides with another tab name of this same content-type, if so adjust name:
|
||||
@@ -309,7 +309,19 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
|
||||
});
|
||||
}
|
||||
|
||||
async #tabNameBlur() {
|
||||
async #tabNameBlur(event: FocusEvent, tab: UmbPropertyTypeContainerModel) {
|
||||
if (!this._activeTabId) return;
|
||||
const newName = (event.target as HTMLInputElement | undefined)?.value;
|
||||
if (newName === '') {
|
||||
const changedName = this.#workspaceContext!.structure.makeEmptyContainerName(this._activeTabId, 'Tab');
|
||||
|
||||
(event.target as HTMLInputElement).value = changedName;
|
||||
|
||||
this.#tabsStructureHelper.partialUpdateContainer(tab.id!, {
|
||||
name: changedName,
|
||||
});
|
||||
}
|
||||
|
||||
this._activeTabId = undefined;
|
||||
}
|
||||
|
||||
@@ -476,7 +488,7 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
|
||||
auto-width
|
||||
@change=${(e: InputEvent) => this.#tabNameChanged(e, tab)}
|
||||
@input=${(e: InputEvent) => this.#tabNameChanged(e, tab)}
|
||||
@blur=${() => this.#tabNameBlur()}>
|
||||
@blur=${(e: FocusEvent) => this.#tabNameBlur(e, tab)}>
|
||||
${this.renderDeleteFor(tab)}
|
||||
</uui-input>
|
||||
</div>`;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { UmbEntityAction } from '../entity-action.interface.js';
|
||||
import type { UmbEntityActionElement } from '../entity-action-element.interface.js';
|
||||
import { UmbActionExecutedEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { html, nothing, ifDefined, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UUIMenuItemEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
@@ -6,10 +7,13 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import type { ManifestEntityAction, MetaEntityActionDefaultKind } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
@customElement('umb-entity-action')
|
||||
export class UmbEntityActionElement<
|
||||
MetaType extends MetaEntityActionDefaultKind = MetaEntityActionDefaultKind,
|
||||
ApiType extends UmbEntityAction<MetaType> = UmbEntityAction<MetaType>,
|
||||
> extends UmbLitElement {
|
||||
export class UmbEntityActionDefaultElement<
|
||||
MetaType extends MetaEntityActionDefaultKind = MetaEntityActionDefaultKind,
|
||||
ApiType extends UmbEntityAction<MetaType> = UmbEntityAction<MetaType>,
|
||||
>
|
||||
extends UmbLitElement
|
||||
implements UmbEntityActionElement
|
||||
{
|
||||
#api?: ApiType;
|
||||
|
||||
// TODO: Do these need to be properties? [NL]
|
||||
@@ -36,6 +40,11 @@ export class UmbEntityActionElement<
|
||||
@state()
|
||||
_href?: string;
|
||||
|
||||
async focus() {
|
||||
await this.updateComplete;
|
||||
this.shadowRoot?.querySelector('uui-menu-item')?.focus();
|
||||
}
|
||||
|
||||
async #onClickLabel(event: UUIMenuItemEvent) {
|
||||
if (!this._href) {
|
||||
event.stopPropagation();
|
||||
@@ -66,10 +75,10 @@ export class UmbEntityActionElement<
|
||||
`;
|
||||
}
|
||||
}
|
||||
export default UmbEntityActionElement;
|
||||
export default UmbEntityActionDefaultElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-entity-action': UmbEntityActionElement;
|
||||
'umb-entity-action': UmbEntityActionDefaultElement;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
export interface UmbEntityActionElement extends UmbControllerHostElement {}
|
||||
@@ -30,6 +30,7 @@ export class UmbEntityActionListElement extends UmbLitElement {
|
||||
return this._props.unique;
|
||||
}
|
||||
public set unique(value: string | null | undefined) {
|
||||
if (value === this._props.unique) return;
|
||||
this._props.unique = value;
|
||||
this.#generateApiArgs();
|
||||
this.requestUpdate('_props');
|
||||
@@ -51,12 +52,14 @@ export class UmbEntityActionListElement extends UmbLitElement {
|
||||
|
||||
this.#entityContext.setEntityType(this._props.entityType);
|
||||
this.#entityContext.setUnique(this._props.unique);
|
||||
this.#hasRenderedOnce = false;
|
||||
|
||||
this._apiArgs = (manifest: ManifestEntityAction<MetaEntityAction>) => {
|
||||
return [{ entityType: this._props.entityType!, unique: this._props.unique!, meta: manifest.meta }];
|
||||
};
|
||||
}
|
||||
|
||||
#hasRenderedOnce?: boolean;
|
||||
render() {
|
||||
return this._filter
|
||||
? html`
|
||||
@@ -64,7 +67,22 @@ export class UmbEntityActionListElement extends UmbLitElement {
|
||||
type="entityAction"
|
||||
.filter=${this._filter}
|
||||
.elementProps=${this._props}
|
||||
.apiArgs=${this._apiArgs}></umb-extension-with-api-slot>
|
||||
.apiArgs=${this._apiArgs}
|
||||
.renderMethod=${(ext: any, i: number) => {
|
||||
if (!this.#hasRenderedOnce && i === 0) {
|
||||
// TODO: Replace this block:
|
||||
ext.component?.updateComplete.then(async () => {
|
||||
const menuitem = ext.component?.shadowRoot?.querySelector('uui-menu-item');
|
||||
menuitem?.updateComplete.then(async () => {
|
||||
menuitem?.shadowRoot?.querySelector('#label-button')?.focus?.();
|
||||
});
|
||||
});
|
||||
// end of block, with this, when this PR is part of UI Lib: https://github.com/umbraco/Umbraco.UI/pull/789
|
||||
// ext.component?.focus();
|
||||
this.#hasRenderedOnce = true;
|
||||
}
|
||||
return ext.component;
|
||||
}}></umb-extension-with-api-slot>
|
||||
`
|
||||
: '';
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ export * from './entity-action-list.element.js';
|
||||
export * from './entity-action.event.js';
|
||||
export * from './entity-action.interface.js';
|
||||
export * from './types.js';
|
||||
export type * from './entity-action-element.interface.js';
|
||||
|
||||
export { UmbRequestReloadStructureForEntityEvent } from './request-reload-structure-for-entity.event.js';
|
||||
export { UMB_ENTITY_ACTION_DEFAULT_KIND_MANIFEST } from './default/default.action.kind.js';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { ConditionTypes } from '../conditions/types.js';
|
||||
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { UmbEntityAction } from '@umbraco-cms/backoffice/entity-action';
|
||||
import type { UmbEntityAction, UmbEntityActionElement } from '@umbraco-cms/backoffice/entity-action';
|
||||
import type { ManifestElementAndApi, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api';
|
||||
import type { UmbModalToken, UmbPickerModalData, UmbPickerModalValue } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
@@ -9,7 +8,7 @@ import type { UmbModalToken, UmbPickerModalData, UmbPickerModalValue } from '@um
|
||||
* For example for content you may wish to create a new document etc
|
||||
*/
|
||||
export interface ManifestEntityAction<MetaType extends MetaEntityAction = MetaEntityAction>
|
||||
extends ManifestElementAndApi<UmbControllerHostElement, UmbEntityAction<MetaType>>,
|
||||
extends ManifestElementAndApi<UmbEntityActionElement, UmbEntityAction<MetaType>>,
|
||||
ManifestWithDynamicConditions<ConditionTypes> {
|
||||
type: 'entityAction';
|
||||
forEntityTypes: Array<string>;
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { UmbIconDictionary } from '@umbraco-cms/backoffice/icon';
|
||||
import type { ManifestPlainJs } from '@umbraco-cms/backoffice/extension-api';
|
||||
|
||||
export interface ManifestIcons extends ManifestPlainJs<{ default: UmbIconDictionary }> {
|
||||
type: 'icons';
|
||||
}
|
||||
@@ -27,6 +27,8 @@ import type { ManifestExternalLoginProvider } from './external-login-provider.mo
|
||||
import type { ManifestGlobalContext } from './global-context.model.js';
|
||||
import type { ManifestHeaderApp, ManifestHeaderAppButtonKind } from './header-app.model.js';
|
||||
import type { ManifestHealthCheck } from './health-check.model.js';
|
||||
import type { ManifestIcons } from './icons.model.js';
|
||||
import type { ManifestLocalization } from './localization.model.js';
|
||||
import type { ManifestMenu } from './menu.model.js';
|
||||
import type { ManifestMenuItem, ManifestMenuItemTreeKind } from './menu-item.model.js';
|
||||
import type { ManifestModal } from './modal.model.js';
|
||||
@@ -40,7 +42,6 @@ import type { ManifestSectionView } from './section-view.model.js';
|
||||
import type { ManifestStore, ManifestTreeStore, ManifestItemStore } from './store.model.js';
|
||||
import type { ManifestTheme } from './theme.model.js';
|
||||
import type { ManifestTinyMcePlugin } from './tinymce-plugin.model.js';
|
||||
import type { ManifestLocalization } from './localization.model.js';
|
||||
import type { ManifestTree } from './tree.model.js';
|
||||
import type { ManifestTreeItem } from './tree-item.model.js';
|
||||
import type { ManifestUserProfileApp } from './user-profile-app.model.js';
|
||||
@@ -66,6 +67,7 @@ import type { ManifestBackofficeEntryPoint } from './backoffice-entry-point.mode
|
||||
import type { ManifestEntryPoint } from './entry-point.model.js';
|
||||
import type { ManifestBase, ManifestBundle, ManifestCondition } from '@umbraco-cms/backoffice/extension-api';
|
||||
|
||||
export type * from './app-entry-point.model.js';
|
||||
export type * from './auth-provider.model.js';
|
||||
export type * from './backoffice-entry-point.model.js';
|
||||
export type * from './block-editor-custom-view.model.js';
|
||||
@@ -84,6 +86,7 @@ export type * from './external-login-provider.model.js';
|
||||
export type * from './global-context.model.js';
|
||||
export type * from './header-app.model.js';
|
||||
export type * from './health-check.model.js';
|
||||
export type * from './icons.model.js';
|
||||
export type * from './localization.model.js';
|
||||
export type * from './menu-item.model.js';
|
||||
export type * from './menu.model.js';
|
||||
@@ -109,7 +112,6 @@ export type * from './workspace-context.model.js';
|
||||
export type * from './workspace-footer-app.model.js';
|
||||
export type * from './workspace-view.model.js';
|
||||
export type * from './workspace.model.js';
|
||||
export type * from './app-entry-point.model.js';
|
||||
|
||||
export type ManifestEntityActions =
|
||||
| ManifestEntityAction
|
||||
@@ -163,6 +165,7 @@ export type ManifestTypes =
|
||||
| ManifestHeaderApp
|
||||
| ManifestHeaderAppButtonKind
|
||||
| ManifestHealthCheck
|
||||
| ManifestIcons
|
||||
| ManifestItemStore
|
||||
| ManifestMenu
|
||||
| ManifestMenuItem
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { UmbIconRegistryContext } from './icon-registry.context.js';
|
||||
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
||||
|
||||
export const UMB_ICON_REGISTRY_CONTEXT = new UmbContextToken<UmbIconRegistryContext>('UmbIconRegistryContext');
|
||||
@@ -0,0 +1,49 @@
|
||||
import { UmbIconRegistry } from './icon.registry.js';
|
||||
import type { UmbIconDefinition } from './types.js';
|
||||
import { UMB_ICON_REGISTRY_CONTEXT } from './icon-registry.context-token.js';
|
||||
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { loadManifestPlainJs } from '@umbraco-cms/backoffice/extension-api';
|
||||
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { type ManifestIcons, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export class UmbIconRegistryContext extends UmbContextBase<UmbIconRegistryContext> {
|
||||
#registry: UmbIconRegistry;
|
||||
#manifestMap = new Map();
|
||||
#icons = new UmbArrayState<UmbIconDefinition>([], (x) => x.name);
|
||||
readonly icons = this.#icons.asObservable();
|
||||
readonly approvedIcons = this.#icons.asObservablePart((icons) => icons.filter((x) => x.legacy !== true));
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host, UMB_ICON_REGISTRY_CONTEXT);
|
||||
this.#registry = new UmbIconRegistry();
|
||||
this.#registry.attach(host.getHostElement());
|
||||
|
||||
this.observe(this.icons, (icons) => {
|
||||
//if (icons.length > 0) {
|
||||
this.#registry.setIcons(icons);
|
||||
//}
|
||||
});
|
||||
|
||||
this.observe(umbExtensionsRegistry.byType('icons'), (manifests) => {
|
||||
manifests.forEach((manifest) => {
|
||||
if (this.#manifestMap.has(manifest.alias)) return;
|
||||
this.#manifestMap.set(manifest.alias, manifest);
|
||||
// TODO: Should we unInit a entry point if is removed?
|
||||
this.instantiateEntryPoint(manifest);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async instantiateEntryPoint(manifest: ManifestIcons) {
|
||||
if (manifest.js) {
|
||||
const js = await loadManifestPlainJs<{ default?: any }>(manifest.js);
|
||||
if (!js || !js.default || !Array.isArray(js.default)) {
|
||||
throw new Error('Icon manifest JS-file must export an array of icons as the default export.');
|
||||
}
|
||||
this.#icons.append(js.default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { UmbIconRegistryContext as api };
|
||||
@@ -1,5 +1,4 @@
|
||||
import icons from './icons/icons.json' assert { type: 'json' };
|
||||
import { UUIIconRegistry } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { type UUIIconHost, UUIIconRegistry } from '@umbraco-cms/backoffice/external/uui';
|
||||
|
||||
interface UmbIconDescriptor {
|
||||
name: string;
|
||||
@@ -13,26 +12,66 @@ interface UmbIconDescriptor {
|
||||
* @description - Icon Registry. Provides icons from the icon manifest. Icons are loaded on demand. All icons are prefixed with 'icon-'
|
||||
*/
|
||||
export class UmbIconRegistry extends UUIIconRegistry {
|
||||
#initResolve?: () => void;
|
||||
#init: Promise<void> = new Promise((resolve) => {
|
||||
this.#initResolve = resolve;
|
||||
});
|
||||
|
||||
#icons: UmbIconDescriptor[] = [];
|
||||
#unhandledProviders: Map<string, UUIIconHost> = new Map();
|
||||
|
||||
setIcons(icons: UmbIconDescriptor[]) {
|
||||
const oldIcons = this.#icons;
|
||||
this.#icons = icons;
|
||||
if (this.#initResolve) {
|
||||
this.#initResolve();
|
||||
this.#initResolve = undefined;
|
||||
}
|
||||
// Go figure out which of the icons are new.
|
||||
const newIcons = this.#icons.filter((i) => !oldIcons.find((o) => o.name === i.name));
|
||||
newIcons.forEach((icon) => {
|
||||
// Do we already have a request for this one, then lets initiate the load for those:
|
||||
const unhandled = this.#unhandledProviders.get(icon.name);
|
||||
if (unhandled) {
|
||||
this.#loadIcon(icon.name, unhandled).then(() => {
|
||||
this.#unhandledProviders.delete(icon.name);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
appendIcons(icons: UmbIconDescriptor[]) {
|
||||
this.#icons = [...this.#icons, ...icons];
|
||||
}
|
||||
/**
|
||||
* @param {string} iconName
|
||||
* @return {*} {boolean}
|
||||
* @memberof UmbIconStore
|
||||
*/
|
||||
acceptIcon(iconName: string): boolean {
|
||||
const iconManifest = icons.find((i: UmbIconDescriptor) => i.name === iconName);
|
||||
if (!iconManifest) return false;
|
||||
const iconProvider = this.provideIcon(iconName);
|
||||
this.#loadIcon(iconName, iconProvider);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async #loadIcon(iconName: string, iconProvider: UUIIconHost): Promise<boolean> {
|
||||
await this.#init;
|
||||
const iconManifest = this.#icons.find((i: UmbIconDescriptor) => i.name === iconName);
|
||||
// Icon not found, so lets add it to a list of unhandled requests.
|
||||
if (!iconManifest) {
|
||||
this.#unhandledProviders.set(iconName, iconProvider);
|
||||
return false;
|
||||
}
|
||||
|
||||
const icon = this.provideIcon(iconName);
|
||||
const iconPath = iconManifest.path;
|
||||
|
||||
import(/* @vite-ignore */ iconPath)
|
||||
.then((iconModule) => {
|
||||
icon.svg = iconModule.default;
|
||||
iconProvider.svg = iconModule.default;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(`Failed to load icon ${iconName} on path ${iconPath}`, err.message);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Meta, Story } from '@storybook/web-components';
|
||||
import icons from './icons/icons.json';
|
||||
import icons from './icons/icons.js';
|
||||
import { html, repeat } from '@umbraco-cms/backoffice/external/lit';
|
||||
|
||||
export default {
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -1 +1,4 @@
|
||||
export * from './icon-registry.context-token.js';
|
||||
export * from './icon-registry.context.js';
|
||||
export * from './icon.registry.js';
|
||||
export * from './types.js';
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
export const manifests = [
|
||||
{
|
||||
type: 'icons',
|
||||
alias: 'Umb.Icons.Backoffice',
|
||||
name: 'Backoffice Icons',
|
||||
js: () => import('./icons/icons.js'),
|
||||
},
|
||||
{
|
||||
type: 'globalContext',
|
||||
alias: 'Umb.GlobalContext.Icons',
|
||||
name: 'Icons Context',
|
||||
api: () => import('./icon-registry.context.js'),
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface UmbIconDefinition {
|
||||
name: string;
|
||||
path: string;
|
||||
legacy?: boolean;
|
||||
}
|
||||
|
||||
export type UmbIconDictionary = Array<UmbIconDefinition>;
|
||||
@@ -6,6 +6,7 @@ import { manifests as cultureManifests } from './culture/manifests.js';
|
||||
import { manifests as debugManifests } from './debug/manifests.js';
|
||||
import { manifests as entityActionManifests } from './entity-action/manifests.js';
|
||||
import { manifests as extensionManifests } from './extension-registry/manifests.js';
|
||||
import { manifests as iconRegistryManifests } from './icon-registry/manifests.js';
|
||||
import { manifests as localizationManifests } from './localization/manifests.js';
|
||||
import { manifests as modalManifests } from './modal/common/manifests.js';
|
||||
import { manifests as propertyActionManifests } from './property-action/manifests.js';
|
||||
@@ -23,6 +24,7 @@ import type { ManifestTypes, UmbBackofficeManifestKind } from './extension-regis
|
||||
export const manifests: Array<ManifestTypes | UmbBackofficeManifestKind> = [
|
||||
...authManifests,
|
||||
...extensionManifests,
|
||||
...iconRegistryManifests,
|
||||
...cultureManifests,
|
||||
...localizationManifests,
|
||||
...themeManifests,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type { UmbConfirmModalData, UmbConfirmModalValue, UmbModalContext } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbLitElement, umbFocus } from '@umbraco-cms/backoffice/lit-element';
|
||||
|
||||
@customElement('umb-confirm-modal')
|
||||
export class UmbConfirmModalElement extends UmbLitElement {
|
||||
@@ -31,7 +31,8 @@ export class UmbConfirmModalElement extends UmbLitElement {
|
||||
color="${this.data?.color || 'positive'}"
|
||||
look="primary"
|
||||
label="${this.data?.confirmLabel || 'Confirm'}"
|
||||
@click=${this._handleConfirm}></uui-button>
|
||||
@click=${this._handleConfirm}
|
||||
${umbFocus()}></uui-button>
|
||||
</uui-dialog-layout>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1,81 +1,97 @@
|
||||
import icons from '../../../icon-registry/icons/icons.json' assert { type: 'json' };
|
||||
import type { UUIColorSwatchesEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
import type { UUIColorSwatchesEvent, UUIIconElement } from '@umbraco-cms/backoffice/external/uui';
|
||||
|
||||
import { css, html, customElement, state, repeat } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, html, customElement, state, repeat, query, nothing } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
|
||||
import type { UmbIconPickerModalData, UmbIconPickerModalValue } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
|
||||
import { extractUmbColorVariable, umbracoColors } from '@umbraco-cms/backoffice/resources';
|
||||
import { umbFocus } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UMB_ICON_REGISTRY_CONTEXT, type UmbIconDefinition } from '@umbraco-cms/backoffice/icon';
|
||||
|
||||
// TODO: Make use of UmbPickerLayoutBase
|
||||
// TODO: to prevent element extension we need to move the Picker logic into a separate class we can reuse across all pickers
|
||||
@customElement('umb-icon-picker-modal')
|
||||
export class UmbIconPickerModalElement extends UmbModalBaseElement<UmbIconPickerModalData, UmbIconPickerModalValue> {
|
||||
private _iconList = icons.filter((icon) => !icon.legacy);
|
||||
#icons?: Array<UmbIconDefinition>;
|
||||
|
||||
@query('#search')
|
||||
private _searchInput?: HTMLInputElement;
|
||||
|
||||
@state()
|
||||
private _iconListFiltered: Array<(typeof icons)[0]> = [];
|
||||
private _iconsFiltered?: Array<UmbIconDefinition>;
|
||||
|
||||
@state()
|
||||
private _colorList = umbracoColors;
|
||||
private _colorList = umbracoColors.filter((color) => !color.legacy);
|
||||
|
||||
@state()
|
||||
private _modalValue?: UmbIconPickerModalValue;
|
||||
private _currentIcon?: string;
|
||||
|
||||
@state()
|
||||
private _currentAlias = 'text';
|
||||
private _currentColor = 'text';
|
||||
|
||||
#changeIcon(e: { target: HTMLInputElement; type: string; key: unknown }) {
|
||||
if (e.type == 'click' || (e.type == 'keyup' && e.key == 'Enter')) {
|
||||
this.modalContext?.updateValue({ icon: e.target.id });
|
||||
}
|
||||
constructor() {
|
||||
super();
|
||||
this.consumeContext(UMB_ICON_REGISTRY_CONTEXT, (context) => {
|
||||
this.observe(context.approvedIcons, (icons) => {
|
||||
this.#icons = icons;
|
||||
this.#filterIcons();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#filterIcons(e: { target: HTMLInputElement }) {
|
||||
if (e.target.value) {
|
||||
this._iconListFiltered = this._iconList.filter((icon) =>
|
||||
icon.name.toLowerCase().includes(e.target.value.toLowerCase()),
|
||||
);
|
||||
#filterIcons() {
|
||||
if (!this.#icons) return;
|
||||
const value = this._searchInput?.value;
|
||||
if (value) {
|
||||
this._iconsFiltered = this.#icons.filter((icon) => icon.name.toLowerCase().includes(value.toLowerCase()));
|
||||
} else {
|
||||
this._iconListFiltered = this._iconList;
|
||||
this._iconsFiltered = this.#icons;
|
||||
}
|
||||
}
|
||||
|
||||
#onColorChange(e: UUIColorSwatchesEvent) {
|
||||
this.modalContext?.updateValue({ color: e.target.value });
|
||||
this._currentAlias = e.target.value;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._iconListFiltered = this._iconList;
|
||||
this._iconsFiltered = this.#icons;
|
||||
|
||||
if (this.modalContext) {
|
||||
this.observe(
|
||||
this.modalContext?.value,
|
||||
(newValue) => {
|
||||
this._modalValue = newValue;
|
||||
this._currentAlias = newValue?.color ?? 'text';
|
||||
this._currentIcon = newValue?.icon;
|
||||
this._currentColor = newValue?.color ?? 'text';
|
||||
},
|
||||
'_observeModalContextValue',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#changeIcon(e: InputEvent | KeyboardEvent) {
|
||||
if (e.type == 'click' || (e.type == 'keyup' && (e as KeyboardEvent).key == 'Enter')) {
|
||||
const iconName = (e.target as UUIIconElement).name;
|
||||
if (iconName) {
|
||||
this.modalContext?.updateValue({ icon: iconName });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#onColorChange(e: UUIColorSwatchesEvent) {
|
||||
const colorAlias = e.target.value;
|
||||
this.modalContext?.updateValue({ color: colorAlias });
|
||||
this._currentColor = colorAlias;
|
||||
}
|
||||
|
||||
render() {
|
||||
// TODO: Missing localization in general. [NL]
|
||||
return html`
|
||||
<umb-body-layout headline="Select Icon">
|
||||
<div id="container">
|
||||
${this.renderSearchbar()}
|
||||
${this.renderSearch()}
|
||||
<hr />
|
||||
<uui-color-swatches
|
||||
.value=${this._currentAlias}
|
||||
.value=${this._currentColor}
|
||||
label="Color switcher for icons"
|
||||
@change=${this.#onColorChange}>
|
||||
${
|
||||
// TODO: Missing translation for the color aliases.
|
||||
// TODO: Missing localization for the color aliases. [NL]
|
||||
this._colorList.map(
|
||||
(color) => html`
|
||||
<uui-color-swatch
|
||||
@@ -88,7 +104,7 @@ export class UmbIconPickerModalElement extends UmbModalBaseElement<UmbIconPicker
|
||||
}
|
||||
</uui-color-swatches>
|
||||
<hr />
|
||||
<uui-scroll-container id="icon-selection">${this.renderIconSelection()}</uui-scroll-container>
|
||||
<uui-scroll-container id="icons">${this.renderIcons()}</uui-scroll-container>
|
||||
</div>
|
||||
<uui-button
|
||||
slot="actions"
|
||||
@@ -104,36 +120,37 @@ export class UmbIconPickerModalElement extends UmbModalBaseElement<UmbIconPicker
|
||||
`;
|
||||
}
|
||||
|
||||
renderSearchbar() {
|
||||
renderSearch() {
|
||||
return html` <uui-input
|
||||
type="search"
|
||||
placeholder=${this.localize.term('placeholders_filter')}
|
||||
label=${this.localize.term('placeholders_filter')}
|
||||
id="searchbar"
|
||||
id="search"
|
||||
@keyup="${this.#filterIcons}"
|
||||
${umbFocus()}>
|
||||
<uui-icon name="search" slot="prepend" id="searchbar_icon"></uui-icon>
|
||||
<uui-icon name="search" slot="prepend" id="search_icon"></uui-icon>
|
||||
</uui-input>`;
|
||||
}
|
||||
|
||||
renderIconSelection() {
|
||||
return repeat(
|
||||
this._iconListFiltered,
|
||||
(icon) => icon.name,
|
||||
(icon) => html`
|
||||
<uui-icon
|
||||
tabindex="0"
|
||||
style="--uui-icon-color: var(${extractUmbColorVariable(this._currentAlias)})"
|
||||
class="icon ${icon.name === this._modalValue?.icon ? 'selected' : ''}"
|
||||
title="${icon.name}"
|
||||
name="${icon.name}"
|
||||
label="${icon.name}"
|
||||
id="${icon.name}"
|
||||
@click="${this.#changeIcon}"
|
||||
@keyup="${this.#changeIcon}">
|
||||
</uui-icon>
|
||||
`,
|
||||
);
|
||||
renderIcons() {
|
||||
return this._iconsFiltered
|
||||
? repeat(
|
||||
this._iconsFiltered,
|
||||
(icon) => icon.name,
|
||||
(icon) => html`
|
||||
<uui-button
|
||||
label="${icon.name}"
|
||||
class="${icon.name === this._currentIcon ? 'selected' : ''}"
|
||||
@click="${this.#changeIcon}"
|
||||
@keyup="${this.#changeIcon}">
|
||||
<uui-icon
|
||||
style="--uui-icon-color: var(${extractUmbColorVariable(this._currentColor)})"
|
||||
name="${icon.name}">
|
||||
</uui-icon>
|
||||
</uui-button>
|
||||
`,
|
||||
)
|
||||
: nothing;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
@@ -160,15 +177,15 @@ export class UmbIconPickerModalElement extends UmbModalBaseElement<UmbIconPicker
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
#searchbar {
|
||||
#search {
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
#searchbar_icon {
|
||||
#search_icon {
|
||||
padding-left: var(--uui-size-space-2);
|
||||
}
|
||||
|
||||
#icon-selection {
|
||||
#icons {
|
||||
line-height: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(40px, calc((100% / 12) - 10px)));
|
||||
@@ -179,27 +196,17 @@ export class UmbIconPickerModalElement extends UmbModalBaseElement<UmbIconPicker
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#icon-selection .icon {
|
||||
display: inline-block;
|
||||
#icons uui-button {
|
||||
border-radius: var(--uui-border-radius);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: var(--uui-size-space-3);
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
font-size: 16px; /* specific for icons */
|
||||
}
|
||||
|
||||
#icon-selection .icon-container {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#icon-selection .icon:focus,
|
||||
#icon-selection .icon:hover,
|
||||
#icon-selection .icon.selected {
|
||||
#icons uui-button:focus,
|
||||
#icons uui-button:hover,
|
||||
#icons uui-button.selected {
|
||||
outline: 2px solid var(--uui-color-selected);
|
||||
}
|
||||
|
||||
uui-button {
|
||||
uui-button[slot='actions'] {
|
||||
margin-left: var(--uui-size-space-4);
|
||||
}
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ export class UmbModalContext<ModalPreset extends object = object, ModalValue = a
|
||||
* @memberof UmbModalContext
|
||||
*/
|
||||
public setValue(value: ModalValue) {
|
||||
this.#value.update(value);
|
||||
this.#value.setValue(value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,7 +18,7 @@ export const manifest: ManifestPropertyEditorSchema = {
|
||||
alias: 'storageType',
|
||||
label: 'Storage Type',
|
||||
description: '',
|
||||
propertyEditorUiAlias: 'Umb.PropertyEditorUi.Dropdown',
|
||||
propertyEditorUiAlias: 'Umb.PropertyEditorUi.Select',
|
||||
config: [
|
||||
{
|
||||
alias: 'items',
|
||||
|
||||
@@ -6,18 +6,17 @@ import { manifest as dropdown } from './dropdown/manifests.js';
|
||||
import { manifest as eyeDropper } from './eye-dropper/manifests.js';
|
||||
import { manifest as iconPicker } from './icon-picker/manifests.js';
|
||||
import { manifest as label } from './label/manifests.js';
|
||||
import { manifest as memberPicker } from './member-picker/manifests.js';
|
||||
import { manifest as multipleTextString } from './multiple-text-string/manifests.js';
|
||||
import { manifest as multiUrlPicker } from './multi-url-picker/manifests.js';
|
||||
import { manifest as numberRange } from './number-range/manifests.js';
|
||||
import { manifest as orderDirection } from './order-direction/manifests.js';
|
||||
import { manifest as overlaySize } from './overlay-size/manifests.js';
|
||||
import { manifest as radioButtonList } from './radio-button-list/manifests.js';
|
||||
import { manifest as select } from './select/manifests.js';
|
||||
import { manifest as slider } from './slider/manifests.js';
|
||||
import { manifest as textArea } from './textarea/manifests.js';
|
||||
import { manifest as toggle } from './toggle/manifests.js';
|
||||
import { manifest as uploadField } from './upload-field/manifests.js';
|
||||
import { manifest as userPicker } from './user-picker/manifests.js';
|
||||
import { manifest as valueType } from './value-type/manifests.js';
|
||||
import { manifests as collectionView } from './collection-view/manifests.js';
|
||||
import { manifests as numbers } from './number/manifests.js';
|
||||
@@ -34,18 +33,17 @@ export const manifests: Array<ManifestPropertyEditorUi> = [
|
||||
eyeDropper,
|
||||
iconPicker,
|
||||
label,
|
||||
memberPicker,
|
||||
multipleTextString,
|
||||
multiUrlPicker,
|
||||
numberRange,
|
||||
orderDirection,
|
||||
overlaySize,
|
||||
radioButtonList,
|
||||
select,
|
||||
slider,
|
||||
textArea,
|
||||
toggle,
|
||||
uploadField,
|
||||
userPicker,
|
||||
valueType,
|
||||
...collectionView,
|
||||
...numbers,
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const manifest: ManifestPropertyEditorUi = {
|
||||
type: 'propertyEditorUi',
|
||||
alias: 'Umb.PropertyEditorUi.MemberPicker',
|
||||
name: 'Member Picker Property Editor UI',
|
||||
element: () => import('./property-editor-ui-member-picker.element.js'),
|
||||
meta: {
|
||||
label: 'Member Picker',
|
||||
propertyEditorSchemaAlias: 'Umbraco.MemberPicker',
|
||||
icon: 'icon-user',
|
||||
group: 'people',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const manifest: ManifestPropertyEditorUi = {
|
||||
type: 'propertyEditorUi',
|
||||
alias: 'Umb.PropertyEditorUi.Select',
|
||||
name: 'Select Property Editor UI',
|
||||
element: () => import('./property-editor-ui-select.element.js'),
|
||||
meta: {
|
||||
label: 'Select',
|
||||
icon: 'icon-list',
|
||||
group: 'pickers',
|
||||
settings: {
|
||||
properties: [
|
||||
{
|
||||
alias: 'items',
|
||||
label: 'Add options',
|
||||
propertyEditorUiAlias: 'Umb.PropertyEditorUi.MultipleTextString',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor';
|
||||
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
|
||||
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type { UUISelectEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
|
||||
/**
|
||||
* @element umb-property-editor-ui-select
|
||||
*/
|
||||
@customElement('umb-property-editor-ui-select')
|
||||
export class UmbPropertyEditorUISelectElement extends UmbLitElement implements UmbPropertyEditorUiElement {
|
||||
@property()
|
||||
value?: string = '';
|
||||
|
||||
@state()
|
||||
private _list: Array<Option> = [];
|
||||
|
||||
public set config(config: UmbPropertyEditorConfigCollection | undefined) {
|
||||
if (!config) return;
|
||||
|
||||
const listData = config.getValueByAlias<string[]>('items');
|
||||
this._list = listData?.map((option) => ({ value: option, name: option, selected: option === this.value })) ?? [];
|
||||
}
|
||||
|
||||
#onChange(event: UUISelectEvent) {
|
||||
this.value = event.target.value as string;
|
||||
this.dispatchEvent(new UmbPropertyValueChangeEvent());
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<uui-select .options=${this._list} @change=${this.#onChange}></uui-select>`;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbPropertyEditorUISelectElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-property-editor-ui-select': UmbPropertyEditorUISelectElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { Meta, Story } from '@storybook/web-components';
|
||||
import type { UmbPropertyEditorUISelectElement } from './property-editor-ui-select.element.js';
|
||||
import { html } from '@umbraco-cms/backoffice/external/lit';
|
||||
|
||||
import './property-editor-ui-select.element.js';
|
||||
|
||||
export default {
|
||||
title: 'Property Editor UIs/Select',
|
||||
component: 'umb-property-editor-ui-select',
|
||||
id: 'umb-property-editor-ui-select',
|
||||
} as Meta;
|
||||
|
||||
export const AAAOverview: Story<UmbPropertyEditorUISelectElement> = () =>
|
||||
html`<umb-property-editor-ui-select></umb-property-editor-ui-select>`;
|
||||
AAAOverview.storyName = 'Overview';
|
||||
@@ -0,0 +1,21 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { UmbPropertyEditorUISelectElement } from './property-editor-ui-select.element.js';
|
||||
import { type UmbTestRunnerWindow, defaultA11yConfig } from '@umbraco-cms/internal/test-utils';
|
||||
|
||||
describe('UmbPropertyEditorUISelectElement', () => {
|
||||
let element: UmbPropertyEditorUISelectElement;
|
||||
|
||||
beforeEach(async () => {
|
||||
element = await fixture(html` <umb-property-editor-ui-select></umb-property-editor-ui-select> `);
|
||||
});
|
||||
|
||||
it('is defined with its own instance', () => {
|
||||
expect(element).to.be.instanceOf(UmbPropertyEditorUISelectElement);
|
||||
});
|
||||
|
||||
if ((window as UmbTestRunnerWindow).__UMBRACO_TEST_RUN_A11Y_TEST) {
|
||||
it('passes the a11y audit', async () => {
|
||||
await expect(element).shadowDom.to.be.accessible(defaultA11yConfig);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const manifest: ManifestPropertyEditorUi = {
|
||||
type: 'propertyEditorUi',
|
||||
alias: 'Umb.PropertyEditorUi.UserPicker',
|
||||
name: 'User Picker Property Editor UI',
|
||||
element: () => import('./property-editor-ui-user-picker.element.js'),
|
||||
meta: {
|
||||
label: 'User Picker',
|
||||
propertyEditorSchemaAlias: 'Umbraco.UserPicker',
|
||||
icon: 'icon-user',
|
||||
group: 'people',
|
||||
},
|
||||
};
|
||||
@@ -1,13 +1,25 @@
|
||||
export const umbracoColors = [
|
||||
{ alias: 'text', varName: '--uui-color-text' },
|
||||
{ alias: 'black', varName: '--uui-color-text' },
|
||||
{ alias: 'yellow', varName: '--uui-palette-sunglow' },
|
||||
{ alias: 'pink', varName: '--uui-palette-spanish-pink' },
|
||||
{ alias: 'dark', varName: '--uui-palette-gunmetal' },
|
||||
{ alias: 'darkblue', varName: '--uui-palette-space-cadet' },
|
||||
{ alias: 'blue', varName: '--uui-palette-violet-blue' },
|
||||
{ alias: 'light-blue', varName: '--uui-palette-malibu' },
|
||||
{ alias: 'red', varName: '--uui-palette-maroon-flush' },
|
||||
{ alias: 'green', varName: '--uui-palette-jungle-green' },
|
||||
{ alias: 'brown', varName: '--uui-palette-chamoisee' },
|
||||
{ alias: 'grey', varName: '--uui-palette-dusty-grey' },
|
||||
|
||||
{ alias: 'blue-grey', legacy: true, varName: '--uui-palette-dusty-grey' },
|
||||
{ alias: 'indigo', legacy: true, varName: '--uui-palette-malibu' },
|
||||
{ alias: 'purple', legacy: true, varName: '--uui-palette-space-cadet' },
|
||||
{ alias: 'deep-purple', legacy: true, varName: '--uui-palette-space-cadet' },
|
||||
{ alias: 'cyan', legacy: true, varName: '-uui-palette-jungle-green' },
|
||||
{ alias: 'light-green', legacy: true, varName: '-uui-palette-jungle-green' },
|
||||
{ alias: 'lime', legacy: true, varName: '-uui-palette-jungle-green' },
|
||||
{ alias: 'amber', legacy: true, varName: '--uui-palette-chamoisee' },
|
||||
{ alias: 'orange', legacy: true, varName: '--uui-palette-chamoisee' },
|
||||
{ alias: 'deep-orange', legacy: true, varName: '--uui-palette-cocoa-brown' },
|
||||
];
|
||||
|
||||
export function extractUmbColorVariable(colorAlias: string): string | undefined {
|
||||
|
||||
@@ -86,7 +86,7 @@ export class UmbSectionDefaultElement extends UmbLitElement implements UmbSectio
|
||||
(app) => app.component,
|
||||
)}
|
||||
</umb-section-sidebar>
|
||||
`
|
||||
`
|
||||
: nothing}
|
||||
<umb-section-main>
|
||||
${this._routes && this._routes.length > 0
|
||||
@@ -105,10 +105,6 @@ export class UmbSectionDefaultElement extends UmbLitElement implements UmbSectio
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
h3 {
|
||||
padding: var(--uui-size-4) var(--uui-size-8);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ export class UmbSectionMainViewElement extends UmbLitElement {
|
||||
</umb-router-slot>
|
||||
</umb-body-layout>
|
||||
`
|
||||
: html`${nothing}`;
|
||||
: nothing;
|
||||
}
|
||||
|
||||
#renderDashboards() {
|
||||
@@ -117,7 +117,7 @@ export class UmbSectionMainViewElement extends UmbLitElement {
|
||||
})}
|
||||
</uui-tab-group>
|
||||
`
|
||||
: '';
|
||||
: nothing;
|
||||
}
|
||||
|
||||
#renderViews() {
|
||||
@@ -140,7 +140,7 @@ export class UmbSectionMainViewElement extends UmbLitElement {
|
||||
})}
|
||||
</uui-tab-group>
|
||||
`
|
||||
: '';
|
||||
: nothing;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
|
||||
@@ -23,6 +23,8 @@ export class UmbSubmitWorkspaceAction extends UmbWorkspaceActionBase<UmbSubmitta
|
||||
// We can't save if we don't have a unique
|
||||
if (unique === undefined) {
|
||||
this.disable();
|
||||
} else {
|
||||
this.enable();
|
||||
}
|
||||
},
|
||||
'saveWorkspaceActionUniqueObserver',
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { UmbApi } from '@umbraco-cms/backoffice/extension-api';
|
||||
import type { UmbDataTypeDetailModel } from '../../types.js';
|
||||
import { UmbDataTypeServerDataSource } from './data-type-detail.server.data-source.js';
|
||||
import type { UmbDataTypeDetailStore } from './data-type-detail.store.js';
|
||||
import { UMB_DATA_TYPE_DETAIL_STORE_CONTEXT } from './data-type-detail.store.js';
|
||||
import { UmbApi } from '@umbraco-cms/backoffice/extension-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbDetailRepositoryBase } from '@umbraco-cms/backoffice/repository';
|
||||
export class UmbDataTypeDetailRepository extends UmbDetailRepositoryBase<UmbDataTypeDetailModel> {
|
||||
|
||||
@@ -9,7 +9,7 @@ import type {
|
||||
import { DocumentTypeService } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
|
||||
import type { UmbPropertyTypeContainerModel } from '@umbraco-cms/backoffice/content-type';
|
||||
import type { UmbPropertyContainerTypes, UmbPropertyTypeContainerModel } from '@umbraco-cms/backoffice/content-type';
|
||||
|
||||
/**
|
||||
* A data source for the Document Type that fetches data from the server
|
||||
@@ -236,7 +236,15 @@ export class UmbDocumentTypeDetailServerDataSource implements UmbDetailDataSourc
|
||||
appearance: property.appearance,
|
||||
};
|
||||
}),
|
||||
containers: model.containers,
|
||||
containers: model.containers.map((container) => {
|
||||
return {
|
||||
id: container.id,
|
||||
parent: container.parent ? { id: container.parent.id } : null,
|
||||
name: container.name ?? '',
|
||||
type: container.type as UmbPropertyContainerTypes, // TODO: check if the value is valid
|
||||
sortOrder: container.sortOrder,
|
||||
};
|
||||
}),
|
||||
allowedDocumentTypes: model.allowedContentTypes.map((allowedContentType) => {
|
||||
return {
|
||||
documentType: { id: allowedContentType.contentType.unique },
|
||||
|
||||
@@ -9,7 +9,7 @@ import type {
|
||||
import { MediaTypeService } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
|
||||
import type { UmbPropertyTypeContainerModel } from '@umbraco-cms/backoffice/content-type';
|
||||
import type { UmbPropertyContainerTypes, UmbPropertyTypeContainerModel } from '@umbraco-cms/backoffice/content-type';
|
||||
|
||||
/**
|
||||
* A data source for the Media Type that fetches data from the server
|
||||
@@ -100,7 +100,15 @@ export class UmbMediaTypeServerDataSource implements UmbDetailDataSource<UmbMedi
|
||||
appearance: property.appearance,
|
||||
};
|
||||
}),
|
||||
containers: data.containers as UmbPropertyTypeContainerModel[],
|
||||
containers: data.containers.map((container) => {
|
||||
return {
|
||||
id: container.id,
|
||||
parent: container.parent ? { id: container.parent.id } : null,
|
||||
name: container.name ?? '',
|
||||
type: container.type as UmbPropertyContainerTypes, // TODO: check if the value is valid
|
||||
sortOrder: container.sortOrder,
|
||||
};
|
||||
}),
|
||||
allowedContentTypes: data.allowedMediaTypes.map((allowedMediaType) => {
|
||||
return {
|
||||
contentType: { unique: allowedMediaType.mediaType.id },
|
||||
|
||||
@@ -104,7 +104,7 @@ export class UmbMemberTypeServerDataSource implements UmbDetailDataSource<UmbMem
|
||||
return {
|
||||
id: container.id,
|
||||
parent: container.parent ? { id: container.parent.id } : null,
|
||||
name: container.name || null,
|
||||
name: container.name ?? '',
|
||||
type: container.type as UmbPropertyContainerTypes, // TODO: check if the value is valid
|
||||
sortOrder: container.sortOrder,
|
||||
};
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { manifests as collectionManifests } from './collection/manifests.js';
|
||||
import { manifests as entityActionManifests } from './entity-actions/manifests.js';
|
||||
import { manifests as memberPickerModalManifests } from './components/member-picker-modal/manifests.js';
|
||||
import { manifests as propertyEditorManifests } from './property-editor/manifests.js';
|
||||
import { manifests as repositoryManifests } from './repository/manifests.js';
|
||||
import { manifests as sectionViewManifests } from './section-view/manifests.js';
|
||||
import { manifests as workspaceManifests } from './workspace/manifests.js';
|
||||
import { manifests as collectionManifests } from './collection/manifests.js';
|
||||
import { manifests as memberPickerModalManifests } from './components/member-picker-modal/manifests.js';
|
||||
|
||||
export const manifests = [
|
||||
...collectionManifests,
|
||||
...entityActionManifests,
|
||||
...memberPickerModalManifests,
|
||||
...propertyEditorManifests,
|
||||
...repositoryManifests,
|
||||
...sectionViewManifests,
|
||||
...workspaceManifests,
|
||||
...collectionManifests,
|
||||
...memberPickerModalManifests,
|
||||
];
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { manifests as propertyEditorManifests } from './member-picker/manifests.js';
|
||||
|
||||
export const manifests = [...propertyEditorManifests];
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const manifests: Array<ManifestTypes> = [
|
||||
{
|
||||
type: 'propertyEditorUi',
|
||||
alias: 'Umb.PropertyEditorUi.MemberPicker',
|
||||
name: 'Member Picker Property Editor UI',
|
||||
element: () => import('./property-editor-ui-member-picker.element.js'),
|
||||
meta: {
|
||||
label: 'Member Picker',
|
||||
propertyEditorSchemaAlias: 'Umbraco.MemberPicker',
|
||||
icon: 'icon-user',
|
||||
group: 'people',
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -1,54 +0,0 @@
|
||||
import { UMB_CURRENT_USER_MFA_MODAL } from '../modals/current-user-mfa/current-user-mfa-modal.token.js';
|
||||
import { html, customElement, state, nothing } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
|
||||
@customElement('umb-mfa-providers-current-user-app')
|
||||
export class UmbMfaProvidersCurrentUserAppElement extends UmbLitElement {
|
||||
@state()
|
||||
_hasProviders = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.#init();
|
||||
}
|
||||
|
||||
async #init() {
|
||||
this._hasProviders = (await firstValueFrom(umbExtensionsRegistry.byType('mfaLoginProvider'))).length > 0;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this._hasProviders) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<uui-button
|
||||
type="button"
|
||||
look="primary"
|
||||
label="${this.localize.term('user_configureTwoFactor')}"
|
||||
@click=${this.#onClick}>
|
||||
<uui-icon name="icon-rectangle-ellipsis"></uui-icon>
|
||||
<umb-localize key="user_configureTwoFactor">Configure Two Factor</umb-localize>
|
||||
</uui-button>
|
||||
`;
|
||||
}
|
||||
|
||||
async #onClick() {
|
||||
const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
|
||||
await modalManagerContext.open(this, UMB_CURRENT_USER_MFA_MODAL).onSubmit();
|
||||
}
|
||||
|
||||
static styles = [UmbTextStyles];
|
||||
}
|
||||
|
||||
export default UmbMfaProvidersCurrentUserAppElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-mfa-providers-current-user-app': UmbMfaProvidersCurrentUserAppElement;
|
||||
}
|
||||
}
|
||||
@@ -79,9 +79,7 @@ export class UmbCurrentUserMfaModalElement extends UmbLitElement {
|
||||
)}
|
||||
</div>
|
||||
<div slot="actions">
|
||||
<uui-button @click=${this.#close} look="secondary" .label=${this.localize.term('general_close')}>
|
||||
${this.localize.term('general_close')}
|
||||
</uui-button>
|
||||
<uui-button @click=${this.#close} look="secondary" .label=${this.localize.term('general_close')}></uui-button>
|
||||
</div>
|
||||
</umb-body-layout>
|
||||
`;
|
||||
@@ -98,7 +96,6 @@ export class UmbCurrentUserMfaModalElement extends UmbLitElement {
|
||||
() => html`
|
||||
<p style="margin-top:0">
|
||||
<umb-localize key="user_2faProviderIsEnabled">This two-factor provider is enabled</umb-localize>
|
||||
<uui-icon icon="check"></uui-icon>
|
||||
</p>
|
||||
<uui-button
|
||||
type="button"
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
import { manifests as collectionManifests } from './collection/manifests.js';
|
||||
import { manifests as inviteManifests } from './invite/manifests.js';
|
||||
import { manifests as repositoryManifests } from './repository/manifests.js';
|
||||
import { manifests as workspaceManifests } from './workspace/manifests.js';
|
||||
import { manifests as modalManifests } from './modals/manifests.js';
|
||||
import { manifests as sectionViewManifests } from './section-view/manifests.js';
|
||||
import { manifests as conditionsManifests } from './conditions/manifests.js';
|
||||
import { manifests as entityActionsManifests } from './entity-actions/manifests.js';
|
||||
import { manifests as entityBulkActionManifests } from './entity-bulk-actions/manifests.js';
|
||||
import { manifests as conditionsManifests } from './conditions/manifests.js';
|
||||
import { manifests as inviteManifests } from './invite/manifests.js';
|
||||
import { manifests as modalManifests } from './modals/manifests.js';
|
||||
import { manifests as repositoryManifests } from './repository/manifests.js';
|
||||
import { manifests as sectionViewManifests } from './section-view/manifests.js';
|
||||
import { manifests as propertyEditorManifests } from './property-editor/manifests.js';
|
||||
import { manifests as workspaceManifests } from './workspace/manifests.js';
|
||||
|
||||
export const manifests = [
|
||||
...collectionManifests,
|
||||
...inviteManifests,
|
||||
...repositoryManifests,
|
||||
...workspaceManifests,
|
||||
...modalManifests,
|
||||
...sectionViewManifests,
|
||||
...conditionsManifests,
|
||||
...entityActionsManifests,
|
||||
...entityBulkActionManifests,
|
||||
...conditionsManifests,
|
||||
...inviteManifests,
|
||||
...modalManifests,
|
||||
...repositoryManifests,
|
||||
...sectionViewManifests,
|
||||
...propertyEditorManifests,
|
||||
...workspaceManifests,
|
||||
];
|
||||
|
||||
@@ -79,9 +79,7 @@ export class UmbUserMfaModalElement extends UmbLitElement {
|
||||
)}
|
||||
</div>
|
||||
<div slot="actions">
|
||||
<uui-button @click=${this.#close} look="secondary" .label=${this.localize.term('general_close')}>
|
||||
${this.localize.term('general_close')}
|
||||
</uui-button>
|
||||
<uui-button @click=${this.#close} look="secondary" .label=${this.localize.term('general_close')}></uui-button>
|
||||
</div>
|
||||
</umb-body-layout>
|
||||
`;
|
||||
@@ -98,7 +96,6 @@ export class UmbUserMfaModalElement extends UmbLitElement {
|
||||
() => html`
|
||||
<p style="margin-top:0">
|
||||
<umb-localize key="user_2faProviderIsEnabled">This two-factor provider is enabled</umb-localize>
|
||||
<uui-icon icon="check"></uui-icon>
|
||||
</p>
|
||||
<uui-button
|
||||
type="button"
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { manifests as userPickerManifests } from './user-picker/manifests.js';
|
||||
|
||||
export const manifests = [...userPickerManifests];
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const manifests: Array<ManifestTypes> = [
|
||||
{
|
||||
type: 'propertyEditorUi',
|
||||
alias: 'Umb.PropertyEditorUi.UserPicker',
|
||||
name: 'User Picker Property Editor UI',
|
||||
element: () => import('./property-editor-ui-user-picker.element.js'),
|
||||
meta: {
|
||||
label: 'User Picker',
|
||||
propertyEditorSchemaAlias: 'Umbraco.UserPicker',
|
||||
icon: 'icon-user',
|
||||
group: 'people',
|
||||
},
|
||||
},
|
||||
];
|
||||
Reference in New Issue
Block a user