Merge remote-tracking branch 'origin/main' into bugfix/block-grid-row-span-corrections
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en-us" dir="ltr">
|
||||
<head>
|
||||
<base href="/" />
|
||||
@@ -7,6 +7,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Umbraco</title>
|
||||
<script src="node_modules/msw/lib/iife/index.js"></script>
|
||||
<link rel="stylesheet" href="src/css/user-defined.css" />
|
||||
<link rel="stylesheet" href="node_modules/@umbraco-ui/uui-css/dist/uui-css.css" />
|
||||
<link rel="stylesheet" href="src/css/umb-css.css" />
|
||||
<script type="module" src="index.ts"></script>
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { UMB_AUTH_CONTEXT, UMB_MODAL_APP_AUTH, type UmbUserLoginState } from '@umbraco-cms/backoffice/auth';
|
||||
import {
|
||||
UMB_AUTH_CONTEXT,
|
||||
UMB_MODAL_APP_AUTH,
|
||||
UMB_STORAGE_REDIRECT_URL,
|
||||
type UmbUserLoginState,
|
||||
} from '@umbraco-cms/backoffice/auth';
|
||||
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
@@ -65,6 +70,9 @@ export class UmbAppAuthController extends UmbControllerBase {
|
||||
throw new Error('[Fatal] Auth context is not available');
|
||||
}
|
||||
|
||||
// Save the current state
|
||||
sessionStorage.setItem(UMB_STORAGE_REDIRECT_URL, window.location.href);
|
||||
|
||||
// Figure out which providers are available
|
||||
const availableProviders = await firstValueFrom(this.#authContext.getAuthProviders(umbExtensionsRegistry));
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { UmbAppContext } from './app.context.js';
|
||||
import { UmbServerConnection } from './server-connection.js';
|
||||
import { UmbAppAuthController } from './app-auth.controller.js';
|
||||
import type { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth';
|
||||
import { UmbAuthContext } from '@umbraco-cms/backoffice/auth';
|
||||
import { UMB_STORAGE_REDIRECT_URL, 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 { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
@@ -86,7 +86,14 @@ export class UmbAppElement extends UmbLitElement {
|
||||
: this.localize.term('errors_externalLoginFailed');
|
||||
|
||||
this.observe(this.#authContext.authorizationSignal, () => {
|
||||
history.replaceState(null, '', '');
|
||||
// Redirect to the saved state or root
|
||||
let currentRoute = '';
|
||||
const savedRoute = sessionStorage.getItem(UMB_STORAGE_REDIRECT_URL);
|
||||
if (savedRoute) {
|
||||
sessionStorage.removeItem(UMB_STORAGE_REDIRECT_URL);
|
||||
currentRoute = savedRoute.endsWith('logout') ? currentRoute : savedRoute;
|
||||
}
|
||||
history.replaceState(null, '', currentRoute);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -99,6 +106,11 @@ export class UmbAppElement extends UmbLitElement {
|
||||
component: () => import('../upgrader/upgrader.element.js'),
|
||||
guards: [this.#isAuthorizedGuard()],
|
||||
},
|
||||
{
|
||||
path: 'preview',
|
||||
component: () => import('../preview/preview.element.js'),
|
||||
guards: [this.#isAuthorizedGuard()],
|
||||
},
|
||||
{
|
||||
path: 'logout',
|
||||
resolve: () => {
|
||||
|
||||
@@ -44,6 +44,7 @@ export class UmbBackofficeHeaderLogoElement extends UmbLitElement {
|
||||
UmbTextStyles,
|
||||
css`
|
||||
#logo {
|
||||
display: var(--umb-header-logo-display, inline);
|
||||
--uui-button-padding-top-factor: 1;
|
||||
--uui-button-padding-bottom-factor: 0.5;
|
||||
margin-right: var(--uui-size-space-2);
|
||||
|
||||
@@ -20,7 +20,7 @@ export class UmbBackofficeHeaderElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
#appHeader {
|
||||
background-color: var(--uui-color-header-surface);
|
||||
background-color: var(--umb-header-background-color, var(--uui-color-header-surface));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
32
src/Umbraco.Web.UI.Client/src/apps/preview/apps/manifests.ts
Normal file
32
src/Umbraco.Web.UI.Client/src/apps/preview/apps/manifests.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { ManifestPreviewAppProvider } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const manifests: Array<ManifestPreviewAppProvider> = [
|
||||
{
|
||||
type: 'previewApp',
|
||||
alias: 'Umb.PreviewApps.Device',
|
||||
name: 'Preview: Device Switcher',
|
||||
element: () => import('./preview-device.element.js'),
|
||||
weight: 400,
|
||||
},
|
||||
{
|
||||
type: 'previewApp',
|
||||
alias: 'Umb.PreviewApps.Culture',
|
||||
name: 'Preview: Culture Switcher',
|
||||
element: () => import('./preview-culture.element.js'),
|
||||
weight: 300,
|
||||
},
|
||||
{
|
||||
type: 'previewApp',
|
||||
alias: 'Umb.PreviewApps.OpenWebsite',
|
||||
name: 'Preview: Open Website Button',
|
||||
element: () => import('./preview-open-website.element.js'),
|
||||
weight: 200,
|
||||
},
|
||||
{
|
||||
type: 'previewApp',
|
||||
alias: 'Umb.PreviewApps.Exit',
|
||||
name: 'Preview: Exit Button',
|
||||
element: () => import('./preview-exit.element.js'),
|
||||
weight: 100,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,103 @@
|
||||
import { UMB_PREVIEW_CONTEXT } from '../preview.context.js';
|
||||
import { css, customElement, html, nothing, repeat, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language';
|
||||
import type { UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language';
|
||||
|
||||
const elementName = 'umb-preview-culture';
|
||||
|
||||
@customElement(elementName)
|
||||
export class UmbPreviewCultureElement extends UmbLitElement {
|
||||
#languageRepository = new UmbLanguageCollectionRepository(this);
|
||||
|
||||
@state()
|
||||
private _culture?: UmbLanguageDetailModel;
|
||||
|
||||
@state()
|
||||
private _cultures: Array<UmbLanguageDetailModel> = [];
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.#getCultures();
|
||||
}
|
||||
|
||||
async #getCultures() {
|
||||
const { data: langauges } = await this.#languageRepository.requestCollection({ skip: 0, take: 100 });
|
||||
this._cultures = langauges?.items ?? [];
|
||||
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const culture = searchParams.get('culture');
|
||||
|
||||
if (culture && culture !== this._culture?.unique) {
|
||||
this._culture = this._cultures.find((c) => c.unique === culture);
|
||||
}
|
||||
}
|
||||
|
||||
async #onClick(culture: UmbLanguageDetailModel) {
|
||||
if (this._culture === culture) return;
|
||||
this._culture = culture;
|
||||
|
||||
const previewContext = await this.getContext(UMB_PREVIEW_CONTEXT);
|
||||
previewContext.updateIFrame({ culture: culture.unique });
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this._cultures.length <= 1) return nothing;
|
||||
return html`
|
||||
<uui-button look="primary" popovertarget="cultures-popover">
|
||||
<div>
|
||||
<uui-icon name="icon-globe"></uui-icon>
|
||||
<span>${this._culture?.name ?? this.localize.term('treeHeaders_languages')}</span>
|
||||
</div>
|
||||
</uui-button>
|
||||
<uui-popover-container id="cultures-popover" placement="top-end">
|
||||
<umb-popover-layout>
|
||||
${repeat(
|
||||
this._cultures,
|
||||
(item) => item.unique,
|
||||
(item) => html`
|
||||
<uui-menu-item
|
||||
label=${item.name}
|
||||
?active=${item.unique === this._culture?.unique}
|
||||
@click=${() => this.#onClick(item)}>
|
||||
<uui-icon slot="icon" name="icon-globe"></uui-icon>
|
||||
</uui-menu-item>
|
||||
`,
|
||||
)}
|
||||
</umb-popover-layout>
|
||||
</uui-popover-container>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
border-left: 1px solid var(--uui-color-header-contrast);
|
||||
--uui-button-font-weight: 400;
|
||||
--uui-button-padding-left-factor: 3;
|
||||
--uui-button-padding-right-factor: 3;
|
||||
}
|
||||
|
||||
uui-button > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
umb-popover-layout {
|
||||
--uui-color-surface: var(--uui-color-header-surface);
|
||||
--uui-color-border: var(--uui-color-header-surface);
|
||||
color: var(--uui-color-header-contrast);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
export { UmbPreviewCultureElement as element };
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
[elementName]: UmbPreviewCultureElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
import { UMB_PREVIEW_CONTEXT } from '../preview.context.js';
|
||||
import { css, customElement, html, property, repeat } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
|
||||
export interface UmbPreviewDevice {
|
||||
alias: string;
|
||||
label: string;
|
||||
css: string;
|
||||
icon: string;
|
||||
dimensions: { height: string; width: string };
|
||||
}
|
||||
|
||||
const elementName = 'umb-preview-device';
|
||||
|
||||
@customElement(elementName)
|
||||
export class UmbPreviewDeviceElement extends UmbLitElement {
|
||||
#devices: Array<UmbPreviewDevice> = [
|
||||
{
|
||||
alias: 'fullsize',
|
||||
label: 'Fit browser',
|
||||
css: 'fullsize',
|
||||
icon: 'icon-application-window-alt',
|
||||
dimensions: { height: '100%', width: '100%' },
|
||||
},
|
||||
{
|
||||
alias: 'desktop',
|
||||
label: 'Desktop',
|
||||
css: 'desktop shadow',
|
||||
icon: 'icon-display',
|
||||
dimensions: { height: '1080px', width: '1920px' },
|
||||
},
|
||||
{
|
||||
alias: 'laptop',
|
||||
label: 'Laptop',
|
||||
css: 'laptop shadow',
|
||||
icon: 'icon-laptop',
|
||||
dimensions: { height: '768px', width: '1366px' },
|
||||
},
|
||||
{
|
||||
alias: 'ipad-portrait',
|
||||
label: 'Tablet portrait',
|
||||
css: 'ipad-portrait shadow',
|
||||
icon: 'icon-ipad',
|
||||
dimensions: { height: '929px', width: '769px' },
|
||||
},
|
||||
{
|
||||
alias: 'ipad-landscape',
|
||||
label: 'Tablet landscape',
|
||||
css: 'ipad-landscape shadow flip',
|
||||
icon: 'icon-ipad',
|
||||
dimensions: { height: '675px', width: '1024px' },
|
||||
},
|
||||
{
|
||||
alias: 'smartphone-portrait',
|
||||
label: 'Smartphone portrait',
|
||||
css: 'smartphone-portrait shadow',
|
||||
icon: 'icon-iphone',
|
||||
dimensions: { height: '640px', width: '360px' },
|
||||
},
|
||||
{
|
||||
alias: 'smartphone-landscape',
|
||||
label: 'Smartphone landscape',
|
||||
css: 'smartphone-landscape shadow flip',
|
||||
icon: 'icon-iphone',
|
||||
dimensions: { height: '360px', width: '640px' },
|
||||
},
|
||||
];
|
||||
|
||||
@property({ attribute: false, type: Object })
|
||||
device = this.#devices[0];
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.#changeDevice(this.device);
|
||||
}
|
||||
|
||||
async #changeDevice(device: UmbPreviewDevice) {
|
||||
if (device === this.device) return;
|
||||
|
||||
this.device = device;
|
||||
|
||||
const previewContext = await this.getContext(UMB_PREVIEW_CONTEXT);
|
||||
|
||||
previewContext?.updateIFrame({
|
||||
className: device.css,
|
||||
height: device.dimensions.height,
|
||||
width: device.dimensions.width,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-button look="primary" popovertarget="devices-popover">
|
||||
<div>
|
||||
<uui-icon name=${this.device.icon} class=${this.device.css.includes('flip') ? 'flip' : ''}></uui-icon>
|
||||
<span>${this.device.label}</span>
|
||||
</div>
|
||||
</uui-button>
|
||||
<uui-popover-container id="devices-popover" placement="top-end">
|
||||
<umb-popover-layout>
|
||||
${repeat(
|
||||
this.#devices,
|
||||
(item) => item.alias,
|
||||
(item) => html`
|
||||
<uui-menu-item
|
||||
label=${item.label}
|
||||
?active=${item === this.device}
|
||||
@click=${() => this.#changeDevice(item)}>
|
||||
<uui-icon slot="icon" name=${item.icon} class=${item.css.includes('flip') ? 'flip' : ''}></uui-icon>
|
||||
</uui-menu-item>
|
||||
`,
|
||||
)}
|
||||
</umb-popover-layout>
|
||||
</uui-popover-container>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
border-left: 1px solid var(--uui-color-header-contrast);
|
||||
--uui-button-font-weight: 400;
|
||||
--uui-button-padding-left-factor: 3;
|
||||
--uui-button-padding-right-factor: 3;
|
||||
}
|
||||
|
||||
uui-button > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
umb-popover-layout {
|
||||
--uui-color-surface: var(--uui-color-header-surface);
|
||||
--uui-color-border: var(--uui-color-header-surface);
|
||||
color: var(--uui-color-header-contrast);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
export { UmbPreviewDeviceElement as element };
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
[elementName]: UmbPreviewDeviceElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { UMB_PREVIEW_CONTEXT } from '../preview.context.js';
|
||||
import { css, customElement, html } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
|
||||
const elementName = 'umb-preview-exit';
|
||||
@customElement(elementName)
|
||||
export class UmbPreviewExitElement extends UmbLitElement {
|
||||
async #onClick() {
|
||||
const previewContext = await this.getContext(UMB_PREVIEW_CONTEXT);
|
||||
previewContext.exitPreview(0);
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-button look="primary" @click=${this.#onClick}>
|
||||
<div>
|
||||
<uui-icon name="icon-power"></uui-icon>
|
||||
<span>${this.localize.term('preview_endLabel')}</span>
|
||||
</div>
|
||||
</uui-button>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
border-left: 1px solid var(--uui-color-header-contrast);
|
||||
--uui-button-font-weight: 400;
|
||||
--uui-button-padding-left-factor: 3;
|
||||
--uui-button-padding-right-factor: 3;
|
||||
}
|
||||
|
||||
uui-button > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
export { UmbPreviewExitElement as element };
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
[elementName]: UmbPreviewExitElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { UMB_PREVIEW_CONTEXT } from '../preview.context.js';
|
||||
import { css, customElement, html } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
|
||||
const elementName = 'umb-preview-open-website';
|
||||
@customElement(elementName)
|
||||
export class UmbPreviewOpenWebsiteElement extends UmbLitElement {
|
||||
async #onClick() {
|
||||
const previewContext = await this.getContext(UMB_PREVIEW_CONTEXT);
|
||||
previewContext.openWebsite();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-button look="primary" @click=${this.#onClick}>
|
||||
<div>
|
||||
<uui-icon name="icon-out"></uui-icon>
|
||||
<span>${this.localize.term('preview_openWebsiteLabel')}</span>
|
||||
</div>
|
||||
</uui-button>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
border-left: 1px solid var(--uui-color-header-contrast);
|
||||
--uui-button-font-weight: 400;
|
||||
--uui-button-padding-left-factor: 3;
|
||||
--uui-button-padding-right-factor: 3;
|
||||
}
|
||||
|
||||
uui-button > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
export { UmbPreviewOpenWebsiteElement as element };
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
[elementName]: UmbPreviewOpenWebsiteElement;
|
||||
}
|
||||
}
|
||||
208
src/Umbraco.Web.UI.Client/src/apps/preview/preview.context.ts
Normal file
208
src/Umbraco.Web.UI.Client/src/apps/preview/preview.context.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
import { UMB_APP_CONTEXT } from '../app/app.context.js';
|
||||
import { UmbBooleanState, UmbStringState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
||||
import { UmbDocumentPreviewRepository } from '@umbraco-cms/backoffice/document';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
const UMB_LOCALSTORAGE_SESSION_KEY = 'umb:previewSessions';
|
||||
|
||||
export class UmbPreviewContext extends UmbContextBase<UmbPreviewContext> {
|
||||
#culture?: string | null;
|
||||
#serverUrl: string = '';
|
||||
#webSocket?: WebSocket;
|
||||
#unique?: string | null;
|
||||
|
||||
#iframeReady = new UmbBooleanState(false);
|
||||
public readonly iframeReady = this.#iframeReady.asObservable();
|
||||
|
||||
#previewUrl = new UmbStringState(undefined);
|
||||
public readonly previewUrl = this.#previewUrl.asObservable();
|
||||
|
||||
#documentPreviewRepository = new UmbDocumentPreviewRepository(this);
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host, UMB_PREVIEW_CONTEXT);
|
||||
this.#init();
|
||||
}
|
||||
|
||||
async #init() {
|
||||
const appContext = await this.getContext(UMB_APP_CONTEXT);
|
||||
this.#serverUrl = appContext.getServerUrl();
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
|
||||
this.#culture = params.get('culture');
|
||||
this.#unique = params.get('id');
|
||||
|
||||
if (!this.#unique) {
|
||||
console.error('No unique ID found in query string.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.#setPreviewUrl();
|
||||
}
|
||||
|
||||
#configureWebSocket() {
|
||||
if (this.#webSocket && this.#webSocket.readyState < 2) return;
|
||||
|
||||
const url = `${this.#serverUrl.replace('https://', 'wss://')}/umbraco/PreviewHub`;
|
||||
|
||||
this.#webSocket = new WebSocket(url);
|
||||
|
||||
this.#webSocket.addEventListener('open', () => {
|
||||
// NOTE: SignalR protocol handshake; it requires a terminating control character.
|
||||
const endChar = String.fromCharCode(30);
|
||||
this.#webSocket?.send(`{"protocol":"json","version":1}${endChar}`);
|
||||
});
|
||||
|
||||
this.#webSocket.addEventListener('message', (event: MessageEvent<string>) => {
|
||||
if (!event?.data) return;
|
||||
|
||||
// NOTE: Strip the terminating control character, (from SignalR).
|
||||
const data = event.data.substring(0, event.data.length - 1);
|
||||
const json = JSON.parse(data) as { type: number; target: string; arguments: Array<string> };
|
||||
|
||||
if (json.type === 1 && json.target === 'refreshed') {
|
||||
const pageId = json.arguments?.[0];
|
||||
if (pageId === this.#unique) {
|
||||
this.#setPreviewUrl({ rnd: Math.random() });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#getSessionCount(): number {
|
||||
return Math.max(Number(localStorage.getItem(UMB_LOCALSTORAGE_SESSION_KEY)), 0) || 0;
|
||||
}
|
||||
|
||||
#setPreviewUrl(args?: { serverUrl?: string; unique?: string | null; culture?: string | null; rnd?: number }) {
|
||||
const host = args?.serverUrl || this.#serverUrl;
|
||||
const path = args?.unique || this.#unique;
|
||||
const params = new URLSearchParams();
|
||||
const culture = args?.culture || this.#culture;
|
||||
|
||||
if (culture) params.set('culture', culture);
|
||||
if (args?.rnd) params.set('rnd', args.rnd.toString());
|
||||
|
||||
this.#previewUrl.setValue(`${host}/${path}?${params}`);
|
||||
}
|
||||
|
||||
#setSessionCount(sessions: number) {
|
||||
localStorage.setItem(UMB_LOCALSTORAGE_SESSION_KEY, sessions.toString());
|
||||
}
|
||||
|
||||
checkSession() {
|
||||
const sessions = this.#getSessionCount();
|
||||
if (sessions > 0) return;
|
||||
|
||||
umbConfirmModal(this._host, {
|
||||
headline: `Preview website?`,
|
||||
content: `You have ended preview mode, do you want to enable it again to view the latest saved version of your website?`,
|
||||
cancelLabel: 'View published version',
|
||||
confirmLabel: 'Preview latest version',
|
||||
})
|
||||
.then(() => {
|
||||
this.restartSession();
|
||||
})
|
||||
.catch(() => {
|
||||
this.exitSession();
|
||||
});
|
||||
}
|
||||
|
||||
async exitPreview(sessions: number = 0) {
|
||||
this.#setSessionCount(sessions);
|
||||
|
||||
// We are good to end preview mode.
|
||||
if (sessions <= 0) {
|
||||
await this.#documentPreviewRepository.exit();
|
||||
}
|
||||
|
||||
if (this.#webSocket) {
|
||||
this.#webSocket.close();
|
||||
this.#webSocket = undefined;
|
||||
}
|
||||
|
||||
const url = this.#previewUrl.getValue() as string;
|
||||
window.location.replace(url);
|
||||
}
|
||||
|
||||
async exitSession() {
|
||||
let sessions = this.#getSessionCount();
|
||||
sessions--;
|
||||
this.exitPreview(sessions);
|
||||
}
|
||||
|
||||
iframeLoaded(iframe: HTMLIFrameElement) {
|
||||
if (!iframe) return;
|
||||
this.#configureWebSocket();
|
||||
this.#iframeReady.setValue(true);
|
||||
}
|
||||
|
||||
getIFrameWrapper(): HTMLElement | undefined {
|
||||
return this.getHostElement().shadowRoot?.querySelector('#wrapper') as HTMLElement;
|
||||
}
|
||||
|
||||
openWebsite() {
|
||||
const url = this.#previewUrl.getValue() as string;
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
|
||||
// TODO: [LK] Figure out how to make `iframe.contentDocument` works, as it's not from SameOrigin.
|
||||
reloadIFrame(iframe: HTMLIFrameElement) {
|
||||
const document = iframe.contentDocument;
|
||||
if (!document) return;
|
||||
|
||||
document.location.reload();
|
||||
}
|
||||
|
||||
async restartSession() {
|
||||
await this.#documentPreviewRepository.enter();
|
||||
this.startSession();
|
||||
}
|
||||
|
||||
startSession() {
|
||||
let sessions = this.#getSessionCount();
|
||||
sessions++;
|
||||
this.#setSessionCount(sessions);
|
||||
}
|
||||
|
||||
async updateIFrame(args?: { culture?: string; className?: string; height?: string; width?: string }) {
|
||||
if (!args) return;
|
||||
|
||||
const wrapper = this.getIFrameWrapper();
|
||||
if (!wrapper) return;
|
||||
|
||||
const scaleIFrame = () => {
|
||||
if (wrapper.className === 'fullsize') {
|
||||
wrapper.style.transform = '';
|
||||
} else {
|
||||
const wScale = document.body.offsetWidth / (wrapper.offsetWidth + 30);
|
||||
const hScale = document.body.offsetHeight / (wrapper.offsetHeight + 30);
|
||||
const scale = Math.min(wScale, hScale, 1); // get the lowest ratio, but not higher than 1
|
||||
wrapper.style.transform = `scale(${scale})`;
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('resize', scaleIFrame);
|
||||
wrapper.addEventListener('transitionend', scaleIFrame);
|
||||
|
||||
if (args.culture) {
|
||||
this.#iframeReady.setValue(false);
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
params.set('culture', args.culture);
|
||||
const newRelativePathQuery = window.location.pathname + '?' + params.toString();
|
||||
history.pushState(null, '', newRelativePathQuery);
|
||||
|
||||
this.#setPreviewUrl({ culture: args.culture });
|
||||
}
|
||||
|
||||
if (args.className) wrapper.className = args.className;
|
||||
if (args.height) wrapper.style.height = args.height;
|
||||
if (args.width) wrapper.style.width = args.width;
|
||||
}
|
||||
}
|
||||
|
||||
export const UMB_PREVIEW_CONTEXT = new UmbContextToken<UmbPreviewContext>('UmbPreviewContext');
|
||||
204
src/Umbraco.Web.UI.Client/src/apps/preview/preview.element.ts
Normal file
204
src/Umbraco.Web.UI.Client/src/apps/preview/preview.element.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
import { manifests as previewApps } from './apps/manifests.js';
|
||||
import { UmbPreviewContext } from './preview.context.js';
|
||||
import { css, customElement, html, nothing, state, when } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
|
||||
const elementName = 'umb-preview';
|
||||
|
||||
/**
|
||||
* @element umb-preview
|
||||
*/
|
||||
@customElement(elementName)
|
||||
export class UmbPreviewElement extends UmbLitElement {
|
||||
#context = new UmbPreviewContext(this);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
if (previewApps?.length) {
|
||||
umbExtensionsRegistry.registerMany(previewApps);
|
||||
}
|
||||
|
||||
this.observe(this.#context.iframeReady, (iframeReady) => (this._iframeReady = iframeReady));
|
||||
this.observe(this.#context.previewUrl, (previewUrl) => (this._previewUrl = previewUrl));
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.addEventListener('visibilitychange', this.#onVisibilityChange);
|
||||
window.addEventListener('beforeunload', () => this.#context.exitSession());
|
||||
this.#context.startSession();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.removeEventListener('visibilitychange', this.#onVisibilityChange);
|
||||
// NOTE: Unsure how we remove an anonymous function from 'beforeunload' event listener.
|
||||
// The reason for the anonymous function is that if we used a named function,
|
||||
// `this` would be the `window` and would not have context to the class instance. [LK]
|
||||
//window.removeEventListener('beforeunload', () => this.#context.exitSession());
|
||||
this.#context.exitSession();
|
||||
}
|
||||
|
||||
@state()
|
||||
private _iframeReady?: boolean;
|
||||
|
||||
@state()
|
||||
private _previewUrl?: string;
|
||||
|
||||
#onIFrameLoad(event: Event & { target: HTMLIFrameElement }) {
|
||||
this.#context.iframeLoaded(event.target);
|
||||
}
|
||||
|
||||
#onVisibilityChange() {
|
||||
this.#context.checkSession();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this._previewUrl) return nothing;
|
||||
return html`
|
||||
${when(!this._iframeReady, () => html`<div id="loading"><uui-loader-circle></uui-loader-circle></div>`)}
|
||||
<div id="wrapper">
|
||||
<div id="container">
|
||||
<iframe
|
||||
src=${this._previewUrl}
|
||||
title="Page preview"
|
||||
@load=${this.#onIFrameLoad}
|
||||
sandbox="allow-scripts"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
<div id="menu">
|
||||
<h4>Preview Mode</h4>
|
||||
<uui-button-group>
|
||||
<umb-extension-slot id="apps" type="previewApp"></umb-extension-slot>
|
||||
</uui-button-group>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
#loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
font-size: 6rem;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
#wrapper {
|
||||
transition: all 240ms cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||
flex-shrink: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#wrapper.fullsize {
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#wrapper.shadow {
|
||||
margin: 10px auto;
|
||||
background-color: white;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
opacity: 1;
|
||||
box-shadow: 0 5px 20px 0 rgba(0, 0, 0, 0.26);
|
||||
}
|
||||
|
||||
#container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#menu {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
background-color: var(--uui-color-header-surface);
|
||||
height: 40px;
|
||||
|
||||
animation: menu-bar-animation 1.2s;
|
||||
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
|
||||
#menu > h4 {
|
||||
color: var(--uui-color-header-contrast-emphasis);
|
||||
margin: 0;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
#menu > uui-button-group {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
uui-icon.flip {
|
||||
rotate: 90deg;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
@keyframes menu-bar-animation {
|
||||
0% {
|
||||
bottom: -50px;
|
||||
}
|
||||
40% {
|
||||
bottom: -50px;
|
||||
}
|
||||
80% {
|
||||
bottom: 0px;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
export default UmbPreviewElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
[elementName]: UmbPreviewElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { Meta } from '@storybook/web-components';
|
||||
import { html } from '@umbraco-cms/backoffice/external/lit';
|
||||
|
||||
export default {
|
||||
title: 'Apps/Preview',
|
||||
component: 'umb-preview',
|
||||
id: 'umb-preview',
|
||||
} satisfies Meta;
|
||||
|
||||
export const Preview = () => html`<umb-preview></umb-preview>`;
|
||||
14
src/Umbraco.Web.UI.Client/src/apps/preview/preview.test.ts
Normal file
14
src/Umbraco.Web.UI.Client/src/apps/preview/preview.test.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { UmbPreviewElement } from './preview.element.js';
|
||||
|
||||
describe('UmbPreview', () => {
|
||||
let element: UmbPreviewElement;
|
||||
|
||||
beforeEach(async () => {
|
||||
element = await fixture(html`<umb-preview></umb-preview>`);
|
||||
});
|
||||
|
||||
it('is defined with its own instance', () => {
|
||||
expect(element).to.be.instanceOf(UmbPreviewElement);
|
||||
});
|
||||
});
|
||||
@@ -548,13 +548,15 @@ export default {
|
||||
noIconsFound: 'Ingen ikoner blev fundet',
|
||||
noMacroParams: 'Der er ingen parametre for denne makro',
|
||||
noMacros: 'Der er ikke tilføjet nogen makroer',
|
||||
externalLoginProviders: 'Eksterne login-udbydere',
|
||||
externalLoginProviders: 'Eksternt login',
|
||||
exceptionDetail: 'Undtagelsesdetaljer',
|
||||
stacktrace: 'Stacktrace',
|
||||
innerException: 'Indre undtagelse',
|
||||
linkYour: 'Link dit',
|
||||
unLinkYour: 'Fjern link fra dit',
|
||||
account: 'konto',
|
||||
linkYour: 'Link din {0} konto',
|
||||
linkYourConfirm: 'For at linke dine Umbraco og {0} konti, vil du blive sendt til {0} for at bekræfte.',
|
||||
unLinkYour: 'Fjern link fra din {0} konto',
|
||||
unLinkYourConfirm: 'Du er ved at fjerne linket mellem dine Umbraco og {0} konti og du vil blive logget ud.',
|
||||
linkedToService: 'Din konto er linket til denne service',
|
||||
selectEditor: 'Vælg editor',
|
||||
selectEditorConfiguration: 'Vælg konfiguration',
|
||||
selectSnippet: 'Vælg snippet',
|
||||
|
||||
@@ -559,13 +559,16 @@ export default {
|
||||
noIconsFound: 'No icons were found',
|
||||
noMacroParams: 'There are no parameters for this macro',
|
||||
noMacros: 'There are no macros available to insert',
|
||||
externalLoginProviders: 'External login providers',
|
||||
externalLoginProviders: 'External logins',
|
||||
exceptionDetail: 'Exception Details',
|
||||
stacktrace: 'Stacktrace',
|
||||
innerException: 'Inner Exception',
|
||||
linkYour: 'Link your',
|
||||
unLinkYour: 'Un-link your',
|
||||
account: 'account',
|
||||
linkYour: 'Link your {0} account',
|
||||
linkYourConfirm:
|
||||
'You are about to link your Umbraco and {0} accounts and you will be redirected to {0} to confirm.',
|
||||
unLinkYour: 'Un-link your {0} account',
|
||||
unLinkYourConfirm: 'You are about to un-link your Umbraco and {0} accounts and you will be logged out.',
|
||||
linkedToService: 'Your account is linked to this service',
|
||||
selectEditor: 'Select editor',
|
||||
selectEditorConfiguration: 'Select configuration',
|
||||
selectSnippet: 'Select snippet',
|
||||
|
||||
@@ -568,13 +568,16 @@ export default {
|
||||
noIconsFound: 'No icons were found',
|
||||
noMacroParams: 'There are no parameters for this macro',
|
||||
noMacros: 'There are no macros available to insert',
|
||||
externalLoginProviders: 'External login providers',
|
||||
externalLoginProviders: 'External logins',
|
||||
exceptionDetail: 'Exception Details',
|
||||
stacktrace: 'Stacktrace',
|
||||
innerException: 'Inner Exception',
|
||||
linkYour: 'Link your',
|
||||
unLinkYour: 'Un-link your',
|
||||
account: 'account',
|
||||
linkYour: 'Link your {0} account',
|
||||
linkYourConfirm:
|
||||
'You are about to link your Umbraco and {0} accounts and you will be redirected to {0} to confirm.',
|
||||
unLinkYour: 'Un-link your {0} account',
|
||||
unLinkYourConfirm: 'You are about to un-link your Umbraco and {0} accounts and you will be logged out.',
|
||||
linkedToService: 'Your account is linked to this service',
|
||||
selectEditor: 'Select editor',
|
||||
selectSnippet: 'Select snippet',
|
||||
variantdeletewarning:
|
||||
|
||||
1
src/Umbraco.Web.UI.Client/src/css/user-defined.css
Normal file
1
src/Umbraco.Web.UI.Client/src/css/user-defined.css
Normal file
@@ -0,0 +1 @@
|
||||
/* This file can be overridden by placing a file with the same name in the /wwwroot/umbraco/backoffice/css folder of the website */
|
||||
@@ -1057,15 +1057,6 @@ fallbackIsoCode?: string | null
|
||||
isoCode: string
|
||||
};
|
||||
|
||||
export type LinkedLoginModel = {
|
||||
providerName: string
|
||||
providerKey: string
|
||||
};
|
||||
|
||||
export type LinkedLoginsRequestModel = {
|
||||
linkedLogins: Array<LinkedLoginModel>
|
||||
};
|
||||
|
||||
export type LogLevelCountsReponseModel = {
|
||||
information: number
|
||||
debug: number
|
||||
@@ -2585,6 +2576,7 @@ key: string
|
||||
|
||||
export type UserExternalLoginProviderModel = {
|
||||
providerSchemeName: string
|
||||
providerKey?: string | null
|
||||
isLinkedOnUser: boolean
|
||||
hasManualLinkingEnabled: boolean
|
||||
};
|
||||
@@ -5241,7 +5233,6 @@ PostUserUnlock: {
|
||||
,PostUserCurrentChangePassword: string
|
||||
,GetUserCurrentConfiguration: CurrenUserConfigurationResponseModel
|
||||
,GetUserCurrentLoginProviders: Array<UserExternalLoginProviderModel>
|
||||
,GetUserCurrentLogins: LinkedLoginsRequestModel
|
||||
,GetUserCurrentPermissions: UserPermissionsResponseModel
|
||||
,GetUserCurrentPermissionsDocument: Array<UserPermissionsResponseModel>
|
||||
,GetUserCurrentPermissionsMedia: UserPermissionsResponseModel
|
||||
|
||||
@@ -8701,21 +8701,6 @@ requestBody
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns unknown Success
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static getUserCurrentLogins(): CancelablePromise<UserData['responses']['GetUserCurrentLogins']> {
|
||||
|
||||
return __request(OpenAPI, {
|
||||
method: 'GET',
|
||||
url: '/umbraco/management/api/v1/user/current/logins',
|
||||
errors: {
|
||||
401: `The resource is protected and requires an authentication token`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns unknown Success
|
||||
* @throws ApiError
|
||||
|
||||
@@ -831,7 +831,6 @@ export const data: Array<UmbMockDataTypeModel> = [
|
||||
{ alias: 'icon', value: 'icon-layers' },
|
||||
{ alias: 'tabName', value: 'Children' },
|
||||
{ alias: 'showContentFirst', value: true },
|
||||
{ alias: 'useInfiniteEditor', value: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -876,7 +875,6 @@ export const data: Array<UmbMockDataTypeModel> = [
|
||||
{ alias: 'icon', value: 'icon-layers' },
|
||||
{ alias: 'tabName', value: 'Items' },
|
||||
{ alias: 'showContentFirst', value: false },
|
||||
{ alias: 'useInfiniteEditor', value: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -128,8 +128,4 @@ export const mfaLoginProviders: Array<UserTwoFactorProviderModel> = [
|
||||
isEnabledOnUser: false,
|
||||
providerName: 'sms',
|
||||
},
|
||||
{
|
||||
isEnabledOnUser: true,
|
||||
providerName: 'Email',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -76,6 +76,15 @@ const privateManifests: PackageManifestResponse = [
|
||||
label: 'Setup SMS Verification',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'mfaLoginProvider',
|
||||
alias: 'My.MfaLoginProvider.Custom.Email',
|
||||
name: 'My Custom Email MFA Provider',
|
||||
forProviderName: 'email',
|
||||
meta: {
|
||||
label: 'Setup Email Verification',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -92,20 +101,6 @@ const privateManifests: PackageManifestResponse = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
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 = [
|
||||
|
||||
@@ -1,7 +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 type { UserData } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import { umbracoPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
export const handlers = [
|
||||
@@ -9,19 +9,22 @@ 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: [
|
||||
rest.get<UserData['responses']['GetUserCurrentLoginProviders']>(
|
||||
umbracoPath(`${UMB_SLUG}/current/login-providers`),
|
||||
(_req, res, ctx) => {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json<UserData['responses']['GetUserCurrentLoginProviders']>([
|
||||
{
|
||||
hasManualLinkingEnabled: true,
|
||||
isLinkedOnUser: true,
|
||||
providerKey: 'google',
|
||||
providerName: 'Umbraco.Google',
|
||||
providerSchemeName: 'Umbraco.Google',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
}),
|
||||
]),
|
||||
);
|
||||
},
|
||||
),
|
||||
rest.get(umbracoPath(`${UMB_SLUG}/current/2fa`), (_req, res, ctx) => {
|
||||
const mfaLoginProviders = umbUserMockDb.getMfaLoginProviders();
|
||||
return res(ctx.status(200), ctx.json(mfaLoginProviders));
|
||||
|
||||
@@ -98,6 +98,11 @@ export class UmbAuthFlow {
|
||||
// tokens
|
||||
#tokenResponse?: TokenResponse;
|
||||
|
||||
// external login
|
||||
#link_endpoint;
|
||||
#link_key_endpoint;
|
||||
#unlink_endpoint;
|
||||
|
||||
/**
|
||||
* This signal will emit when the authorization flow is complete.
|
||||
* @remark It will also emit if there is an error during the authorization flow.
|
||||
@@ -125,6 +130,10 @@ export class UmbAuthFlow {
|
||||
end_session_endpoint: `${openIdConnectUrl}/umbraco/management/api/v1/security/back-office/signout`,
|
||||
});
|
||||
|
||||
this.#link_endpoint = `${openIdConnectUrl}/umbraco/management/api/v1/security/back-office/link-login`;
|
||||
this.#link_key_endpoint = `${openIdConnectUrl}/umbraco/management/api/v1/security/back-office/link-login-key`;
|
||||
this.#unlink_endpoint = `${openIdConnectUrl}/umbraco/management/api/v1/security/back-office/unlink-login`;
|
||||
|
||||
this.#notifier = new AuthorizationNotifier();
|
||||
this.#tokenHandler = new BaseTokenRequestHandler(requestor);
|
||||
this.#storageBackend = new LocalStorageBackend();
|
||||
@@ -320,6 +329,55 @@ export class UmbAuthFlow {
|
||||
: Promise.reject('Missing tokenResponse.');
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will link the current user to the specified provider by redirecting the user to the link endpoint.
|
||||
* @param provider The provider to link to.
|
||||
*/
|
||||
async linkLogin(provider: string): Promise<void> {
|
||||
const linkKey = await this.#makeLinkTokenRequest(provider);
|
||||
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = this.#link_endpoint;
|
||||
form.style.display = 'none';
|
||||
|
||||
const providerInput = document.createElement('input');
|
||||
providerInput.name = 'provider';
|
||||
providerInput.value = provider;
|
||||
form.appendChild(providerInput);
|
||||
|
||||
const linkKeyInput = document.createElement('input');
|
||||
linkKeyInput.name = 'linkKey';
|
||||
linkKeyInput.value = linkKey;
|
||||
form.appendChild(linkKeyInput);
|
||||
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will unlink the current user from the specified provider.
|
||||
*/
|
||||
async unlinkLogin(loginProvider: string, providerKey: string): Promise<boolean> {
|
||||
const token = await this.performWithFreshTokens();
|
||||
const request = new Request(this.#unlink_endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
|
||||
body: JSON.stringify({ loginProvider, providerKey }),
|
||||
});
|
||||
|
||||
const result = await fetch(request);
|
||||
|
||||
if (!result.ok) {
|
||||
const error = await result.json();
|
||||
throw error;
|
||||
}
|
||||
|
||||
await this.signOut();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current token response to local storage.
|
||||
*/
|
||||
@@ -384,4 +442,21 @@ export class UmbAuthFlow {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async #makeLinkTokenRequest(provider: string) {
|
||||
const token = await this.performWithFreshTokens();
|
||||
|
||||
const request = await fetch(`${this.#link_key_endpoint}?provider=${provider}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!request.ok) {
|
||||
throw new Error('Failed to link login');
|
||||
}
|
||||
|
||||
return request.json();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,8 @@ export class UmbAuthContext extends UmbContextBase<UmbAuthContext> {
|
||||
#isAuthorized = new UmbBooleanState<boolean>(false);
|
||||
// Timeout is different from `isAuthorized` because it can occur repeatedly
|
||||
#isTimeout = new Subject<void>();
|
||||
/**
|
||||
* Observable that emits true when the auth context is initialized.
|
||||
* @remark It will only emit once and then complete itself.
|
||||
*/
|
||||
#isInitialized = new ReplaySubject<void>(1);
|
||||
#isBypassed = false;
|
||||
#isBypassed;
|
||||
#serverUrl;
|
||||
#backofficePath;
|
||||
#authFlow;
|
||||
@@ -25,6 +21,12 @@ export class UmbAuthContext extends UmbContextBase<UmbAuthContext> {
|
||||
#authWindowProxy?: WindowProxy | null;
|
||||
#previousAuthUrl?: string;
|
||||
|
||||
/**
|
||||
* Observable that emits true when the auth context is initialized.
|
||||
* @remark It will only emit once and then complete itself.
|
||||
*/
|
||||
readonly isInitialized = this.#isInitialized.asObservable();
|
||||
|
||||
/**
|
||||
* Observable that emits true if the user is authorized, otherwise false.
|
||||
* @remark It will only emit when the authorization state changes.
|
||||
@@ -254,22 +256,51 @@ export class UmbAuthContext extends UmbContextBase<UmbAuthContext> {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the auth context as initialized, which means that the auth context is ready to be used.
|
||||
* @remark This is used to let the app context know that the core module is ready, which means that the core auth providers are available.
|
||||
*/
|
||||
setInitialized() {
|
||||
this.#isInitialized.next();
|
||||
this.#isInitialized.complete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all registered auth providers.
|
||||
*/
|
||||
getAuthProviders(extensionsRegistry: UmbBackofficeExtensionRegistry) {
|
||||
return this.#isInitialized.pipe(
|
||||
switchMap(() => extensionsRegistry.byType<'authProvider', ManifestAuthProvider>('authProvider')),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the authorized redirect url.
|
||||
* @returns The redirect url, which is the backoffice path.
|
||||
*/
|
||||
getRedirectUrl() {
|
||||
return `${window.location.origin}${this.#backofficePath}${this.#backofficePath.endsWith('/') ? '' : '/'}oauth_complete`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the post logout redirect url.
|
||||
* @returns The post logout redirect url, which is the backoffice path with the logout path appended.
|
||||
*/
|
||||
getPostLogoutRedirectUrl() {
|
||||
return `${window.location.origin}${this.#backofficePath}${this.#backofficePath.endsWith('/') ? '' : '/'}logout`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see UmbAuthFlow#linkLogin
|
||||
*/
|
||||
linkLogin(provider: string) {
|
||||
return this.#authFlow.linkLogin(provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see UmbAuthFlow#unlinkLogin
|
||||
*/
|
||||
unlinkLogin(providerName: string, providerKey: string) {
|
||||
return this.#authFlow.unlinkLogin(providerName, providerKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,19 +18,25 @@ export class UmbAuthProviderDefaultElement extends UmbLitElement implements UmbA
|
||||
this.setAttribute('part', 'auth-provider-default');
|
||||
}
|
||||
|
||||
get #label() {
|
||||
const label = this.manifest.meta?.label ?? this.manifest.forProviderName;
|
||||
const labelLocalized = this.localize.string(label);
|
||||
return this.localize.term('login_signInWith', labelLocalized);
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-button
|
||||
type="button"
|
||||
@click=${() => this.onSubmit(this.manifest)}
|
||||
id="auth-provider-button"
|
||||
.label=${this.manifest.meta?.label ?? this.manifest.forProviderName}
|
||||
.label=${this.#label}
|
||||
.look=${this.manifest.meta?.defaultView?.look ?? 'outline'}
|
||||
.color=${this.manifest.meta?.defaultView?.color ?? 'default'}>
|
||||
${this.manifest.meta?.defaultView?.icon
|
||||
? html`<uui-icon .name=${this.manifest.meta?.defaultView?.icon}></uui-icon>`
|
||||
? html`<uui-icon id="icon" .name=${this.manifest.meta?.defaultView?.icon}></uui-icon>`
|
||||
: nothing}
|
||||
${this.manifest.meta?.label ?? this.manifest.forProviderName}
|
||||
${this.#label}
|
||||
</uui-button>
|
||||
`;
|
||||
}
|
||||
@@ -45,6 +51,10 @@ export class UmbAuthProviderDefaultElement extends UmbLitElement implements UmbA
|
||||
#auth-provider-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#icon {
|
||||
margin-right: var(--uui-size-space-1);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ export const manifests: Array<ManifestAuthProvider> = [
|
||||
forProviderName: 'Umbraco',
|
||||
weight: 1000,
|
||||
meta: {
|
||||
label: 'Sign in with Umbraco',
|
||||
label: 'Umbraco',
|
||||
defaultView: {
|
||||
icon: 'icon-umbraco',
|
||||
look: 'primary',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { UMB_COLLECTION_CONTEXT } from './default/collection-default.context.js';
|
||||
import { UMB_COLLECTION_CONTEXT } from './default/index.js';
|
||||
import type { CollectionAliasConditionConfig } from './collection-alias.manifest.js';
|
||||
import type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api';
|
||||
import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { UMB_COLLECTION_CONTEXT } from './default/collection-default.context.js';
|
||||
import { UMB_COLLECTION_CONTEXT } from './default/index.js';
|
||||
import type { CollectionBulkActionPermissionConditionConfig } from './collection-bulk-action-permission.manifest.js';
|
||||
import type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api';
|
||||
import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { UMB_COLLECTION_CONTEXT } from '../default/collection-default.context.js';
|
||||
import type { ManifestEntityBulkAction, MetaEntityBulkAction } from '../../extension-registry/models/index.js';
|
||||
import { UMB_COLLECTION_CONTEXT } from '../default/index.js';
|
||||
import type { UmbActionExecutedEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { css, html, nothing, customElement, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import type { ManifestEntityBulkAction, MetaEntityBulkAction } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
function apiArgsMethod(manifest: ManifestEntityBulkAction<MetaEntityBulkAction>) {
|
||||
return [{ meta: manifest.meta }] as unknown[];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { UmbDefaultCollectionContext } from '../default/collection-default.context.js';
|
||||
import { UMB_COLLECTION_CONTEXT } from '../default/collection-default.context.js';
|
||||
import type { UmbDefaultCollectionContext } from '../default/index.js';
|
||||
import { UMB_COLLECTION_CONTEXT } from '../default/index.js';
|
||||
import type { UmbCollectionLayoutConfiguration } from '../types.js';
|
||||
import { css, html, customElement, state, nothing, repeat, query } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { observeMultiple } from '@umbraco-cms/backoffice/observable-api';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { UMB_COLLECTION_CONTEXT } from '../../default/collection-default.context.js';
|
||||
import { UMB_COLLECTION_CONTEXT } from '../../default/index.js';
|
||||
import type { UUIPaginationEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { css, html, customElement, nothing, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { UmbDefaultCollectionContext } from './collection-default.context.js';
|
||||
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
||||
|
||||
export const UMB_COLLECTION_CONTEXT = new UmbContextToken<UmbDefaultCollectionContext>('UmbCollectionContext');
|
||||
@@ -6,23 +6,31 @@ import type {
|
||||
UmbCollectionContext,
|
||||
UmbCollectionLayoutConfiguration,
|
||||
} from '../types.js';
|
||||
import type { UmbCollectionFilterModel } from '../collection-filter-model.interface.js';
|
||||
import type { UmbCollectionRepository } from '../repository/collection-repository.interface.js';
|
||||
import { UMB_COLLECTION_CONTEXT } from './collection-default.context-token.js';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UmbArrayState, UmbNumberState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
||||
import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api';
|
||||
import { UmbSelectionManager, UmbPaginationManager } from '@umbraco-cms/backoffice/utils';
|
||||
import type { ManifestCollection, ManifestRepository } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
|
||||
import type { UmbCollectionFilterModel, UmbCollectionRepository } from '@umbraco-cms/backoffice/collection';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import {
|
||||
UmbRequestReloadChildrenOfEntityEvent,
|
||||
UmbRequestReloadStructureForEntityEvent,
|
||||
} from '@umbraco-cms/backoffice/entity-action';
|
||||
import type { UmbActionEventContext } from '@umbraco-cms/backoffice/action';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import { UMB_ENTITY_CONTEXT } from '@umbraco-cms/backoffice/entity';
|
||||
|
||||
const LOCAL_STORAGE_KEY = 'umb-collection-view';
|
||||
|
||||
export class UmbDefaultCollectionContext<
|
||||
CollectionItemType = any,
|
||||
FilterModelType extends UmbCollectionFilterModel = any,
|
||||
CollectionItemType extends { entityType: string; unique: string } = any,
|
||||
FilterModelType extends UmbCollectionFilterModel = UmbCollectionFilterModel,
|
||||
>
|
||||
extends UmbContextBase<UmbDefaultCollectionContext>
|
||||
implements UmbCollectionContext, UmbApi
|
||||
@@ -34,7 +42,7 @@ export class UmbDefaultCollectionContext<
|
||||
#loading = new UmbObjectState<boolean>(false);
|
||||
public readonly loading = this.#loading.asObservable();
|
||||
|
||||
#items = new UmbArrayState<CollectionItemType>([], (x) => x);
|
||||
#items = new UmbArrayState<CollectionItemType>([], (x) => x.unique);
|
||||
public readonly items = this.#items.asObservable();
|
||||
|
||||
#totalItems = new UmbNumberState(0);
|
||||
@@ -63,6 +71,8 @@ export class UmbDefaultCollectionContext<
|
||||
this.#initialized ? resolve() : (this.#initResolver = resolve);
|
||||
});
|
||||
|
||||
#actionEventContext: UmbActionEventContext | undefined;
|
||||
|
||||
constructor(host: UmbControllerHost, defaultViewAlias: string, defaultFilter: Partial<FilterModelType> = {}) {
|
||||
super(host, UMB_COLLECTION_CONTEXT);
|
||||
|
||||
@@ -70,6 +80,33 @@ export class UmbDefaultCollectionContext<
|
||||
this.#defaultFilter = defaultFilter;
|
||||
|
||||
this.pagination.addEventListener(UmbChangeEvent.TYPE, this.#onPageChange);
|
||||
this.#listenToEntityEvents();
|
||||
}
|
||||
|
||||
async #listenToEntityEvents() {
|
||||
this.consumeContext(UMB_ACTION_EVENT_CONTEXT, (context) => {
|
||||
this.#actionEventContext = context;
|
||||
|
||||
context?.removeEventListener(
|
||||
UmbRequestReloadStructureForEntityEvent.TYPE,
|
||||
this.#onReloadStructureRequest as unknown as EventListener,
|
||||
);
|
||||
|
||||
context?.removeEventListener(
|
||||
UmbRequestReloadChildrenOfEntityEvent.TYPE,
|
||||
this.#onReloadChildrenRequest as unknown as EventListener,
|
||||
);
|
||||
|
||||
context?.addEventListener(
|
||||
UmbRequestReloadStructureForEntityEvent.TYPE,
|
||||
this.#onReloadStructureRequest as unknown as EventListener,
|
||||
);
|
||||
|
||||
context?.addEventListener(
|
||||
UmbRequestReloadChildrenOfEntityEvent.TYPE,
|
||||
this.#onReloadChildrenRequest as unknown as EventListener,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#configured = false;
|
||||
@@ -222,11 +259,37 @@ export class UmbDefaultCollectionContext<
|
||||
|
||||
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(layouts));
|
||||
}
|
||||
|
||||
#onReloadStructureRequest = (event: UmbRequestReloadStructureForEntityEvent) => {
|
||||
const items = this.#items.getValue();
|
||||
const hasItem = items.some((item) => item.unique === event.getUnique());
|
||||
if (hasItem) {
|
||||
this.requestCollection();
|
||||
}
|
||||
};
|
||||
|
||||
#onReloadChildrenRequest = async (event: UmbRequestReloadChildrenOfEntityEvent) => {
|
||||
// check if the collection is in the same context as the entity from the event
|
||||
const entityContext = await this.getContext(UMB_ENTITY_CONTEXT);
|
||||
const unique = entityContext.getUnique();
|
||||
const entityType = entityContext.getEntityType();
|
||||
|
||||
if (unique === event.getUnique() && entityType === event.getEntityType()) {
|
||||
this.requestCollection();
|
||||
}
|
||||
};
|
||||
|
||||
destroy(): void {
|
||||
this.#actionEventContext?.removeEventListener(
|
||||
UmbRequestReloadStructureForEntityEvent.TYPE,
|
||||
this.#onReloadStructureRequest as unknown as EventListener,
|
||||
);
|
||||
|
||||
this.#actionEventContext?.removeEventListener(
|
||||
UmbRequestReloadChildrenOfEntityEvent.TYPE,
|
||||
this.#onReloadChildrenRequest as unknown as EventListener,
|
||||
);
|
||||
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export const UMB_COLLECTION_CONTEXT = new UmbContextToken<UmbDefaultCollectionContext>('UmbCollectionContext');
|
||||
|
||||
/**
|
||||
* @deprecated Use UMB_COLLECTION_CONTEXT instead.
|
||||
*/
|
||||
export { UMB_COLLECTION_CONTEXT as UMB_DEFAULT_COLLECTION_CONTEXT };
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { UMB_COLLECTION_CONTEXT, UmbDefaultCollectionContext } from './collection-default.context.js';
|
||||
import { UmbDefaultCollectionContext } from './collection-default.context.js';
|
||||
import { UMB_COLLECTION_CONTEXT } from './collection-default.context-token.js';
|
||||
import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type { UmbRoute } from '@umbraco-cms/backoffice/router';
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export { UMB_COLLECTION_CONTEXT } from './collection-default.context-token.js';
|
||||
export { UmbDefaultCollectionContext } from './collection-default.context.js';
|
||||
@@ -7,6 +7,7 @@ export * from './collection.element.js';
|
||||
export * from './components/index.js';
|
||||
|
||||
export * from './default/collection-default.context.js';
|
||||
export * from './default/collection-default.context-token.js';
|
||||
export * from './collection-filter-model.interface.js';
|
||||
|
||||
export { UMB_COLLECTION_ALIAS_CONDITION } from './collection-alias.manifest.js';
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import type { UmbDataSourceResponse } from '../../repository/index.js';
|
||||
import type { UmbPagedModel } from '../../repository/types.js';
|
||||
import type { UmbCollectionFilterModel } from '../collection-filter-model.interface.js';
|
||||
|
||||
export interface UmbCollectionDataSource<CollectionItemType, FilterType = unknown> {
|
||||
export interface UmbCollectionDataSource<
|
||||
CollectionItemType extends { entityType: string; unique: string } = any,
|
||||
FilterType extends UmbCollectionFilterModel = UmbCollectionFilterModel,
|
||||
> {
|
||||
getCollection(filter: FilterType): Promise<UmbDataSourceResponse<UmbPagedModel<CollectionItemType>>>;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import type { UmbCollectionFilterModel } from '../collection-filter-model.interface.js';
|
||||
import type { UmbPagedModel, UmbRepositoryResponse } from '@umbraco-cms/backoffice/repository';
|
||||
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
|
||||
|
||||
export interface UmbCollectionRepository extends UmbApi {
|
||||
requestCollection(filter?: any): Promise<any>;
|
||||
export interface UmbCollectionRepository<
|
||||
CollectionItemType extends { entityType: string; unique: string } = any,
|
||||
FilterType extends UmbCollectionFilterModel = UmbCollectionFilterModel,
|
||||
> extends UmbApi {
|
||||
requestCollection(filter?: FilterType): Promise<UmbRepositoryResponse<UmbPagedModel<CollectionItemType>>>;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ export interface UmbCollectionConfiguration {
|
||||
orderBy?: string;
|
||||
orderDirection?: string;
|
||||
pageSize?: number;
|
||||
useInfiniteEditor?: boolean;
|
||||
userDefinedProperties?: Array<UmbCollectionColumnConfiguration>;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type { CSSResultGroup } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, html, LitElement, customElement, ifDefined } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, html, customElement, ifDefined } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type {
|
||||
ManifestHeaderAppButtonKind,
|
||||
UmbBackofficeManifestKind,
|
||||
} from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
|
||||
const manifest: UmbBackofficeManifestKind = {
|
||||
type: 'kind',
|
||||
@@ -21,7 +22,7 @@ const manifest: UmbBackofficeManifestKind = {
|
||||
umbExtensionsRegistry.register(manifest);
|
||||
|
||||
@customElement('umb-header-app-button')
|
||||
export class UmbHeaderAppButtonElement extends LitElement {
|
||||
export class UmbHeaderAppButtonElement extends UmbLitElement {
|
||||
public manifest?: ManifestHeaderAppButtonKind;
|
||||
|
||||
render() {
|
||||
@@ -41,7 +42,11 @@ export class UmbHeaderAppButtonElement extends LitElement {
|
||||
css`
|
||||
uui-button {
|
||||
font-size: 18px;
|
||||
--uui-button-background-color: transparent;
|
||||
--uui-button-background-color: var(--umb-header-app-button-background-color, transparent);
|
||||
--uui-button-background-color-hover: var(
|
||||
--umb-header-app-button-background-color-hover,
|
||||
var(--uui-color-emphasis)
|
||||
);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -25,7 +25,7 @@ export class UmbInputUploadFieldElement extends UmbLitElement {
|
||||
this._src = value.src;
|
||||
}
|
||||
get value(): MediaValueType {
|
||||
return !this.temporaryFile ? { src: this._src } : { temporaryFileId: this.temporaryFile.unique };
|
||||
return !this.temporaryFile ? { src: this._src } : { temporaryFileId: this.temporaryFile.temporaryUnique };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,7 +67,7 @@ export class UmbInputUploadFieldElement extends UmbLitElement {
|
||||
async #onUpload(e: UUIFileDropzoneEvent) {
|
||||
//Property Editor for Upload field will always only have one file.
|
||||
const item: UmbTemporaryFileModel = {
|
||||
unique: UmbId.new(),
|
||||
temporaryUnique: UmbId.new(),
|
||||
file: e.detail.files[0],
|
||||
};
|
||||
const upload = this.#manager.uploadOne(item);
|
||||
@@ -80,7 +80,7 @@ export class UmbInputUploadFieldElement extends UmbLitElement {
|
||||
|
||||
const uploaded = await upload;
|
||||
if (uploaded.status === TemporaryFileStatus.SUCCESS) {
|
||||
this.temporaryFile = { unique: item.unique, file: item.file };
|
||||
this.temporaryFile = { temporaryUnique: item.temporaryUnique, file: item.file };
|
||||
this.dispatchEvent(new UmbChangeEvent());
|
||||
}
|
||||
}
|
||||
@@ -172,6 +172,9 @@ export class UmbInputUploadFieldElement extends UmbLitElement {
|
||||
|
||||
static styles = [
|
||||
css`
|
||||
:host {
|
||||
position: relative;
|
||||
}
|
||||
uui-icon {
|
||||
vertical-align: sub;
|
||||
margin-right: var(--uui-size-space-4);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { UmbPropertyEditorConfig } from '../../../property-editor/index.js';
|
||||
import type { UmbPropertyTypeModel } from '../../types.js';
|
||||
import { UmbContentPropertyContext } from '@umbraco-cms/backoffice/content';
|
||||
import type { UmbDataTypeDetailModel } from '@umbraco-cms/backoffice/data-type';
|
||||
import { UmbDataTypeDetailRepository } from '@umbraco-cms/backoffice/data-type';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
@@ -34,6 +35,8 @@ export class UmbPropertyTypeBasedPropertyElement extends UmbLitElement {
|
||||
private _dataTypeDetailRepository = new UmbDataTypeDetailRepository(this);
|
||||
private _dataTypeObserver?: UmbObserverController<UmbDataTypeDetailModel | undefined>;
|
||||
|
||||
#contentPropertyContext = new UmbContentPropertyContext(this);
|
||||
|
||||
private async _observeDataType(dataTypeUnique?: string) {
|
||||
this._dataTypeObserver?.destroy();
|
||||
if (dataTypeUnique) {
|
||||
@@ -42,6 +45,9 @@ export class UmbPropertyTypeBasedPropertyElement extends UmbLitElement {
|
||||
this._dataTypeObserver = this.observe(
|
||||
await this._dataTypeDetailRepository.byUnique(dataTypeUnique),
|
||||
(dataType) => {
|
||||
const contextValue = dataType ? { unique: dataType.unique } : undefined;
|
||||
this.#contentPropertyContext.setDataType(contextValue);
|
||||
|
||||
this._dataTypeData = dataType?.values;
|
||||
this._propertyEditorUiAlias = dataType?.editorUiAlias || undefined;
|
||||
// If there is no UI, we will look up the Property editor model to find the default UI alias:
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { UmbContentPropertyContext } from './content-property.context.js';
|
||||
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
||||
|
||||
export const UMB_CONTENT_PROPERTY_CONTEXT = new UmbContextToken<UmbContentPropertyContext>('UmbContentPropertyContext');
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { UmbPropertyTypeModel } from '../content-type/types.js';
|
||||
import { UMB_CONTENT_PROPERTY_CONTEXT } from './content-property.context-token.js';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
|
||||
|
||||
export class UmbContentPropertyContext extends UmbContextBase<UmbContentPropertyContext> {
|
||||
#dataType = new UmbObjectState<UmbPropertyTypeModel['dataType'] | undefined>(undefined);
|
||||
dataType = this.#dataType.asObservable();
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host, UMB_CONTENT_PROPERTY_CONTEXT);
|
||||
}
|
||||
|
||||
setDataType(dataType: UmbPropertyTypeModel['dataType'] | undefined) {
|
||||
this.#dataType.setValue(dataType);
|
||||
}
|
||||
}
|
||||
@@ -1 +1,4 @@
|
||||
export * from './workspace/index.js';
|
||||
|
||||
export { UmbContentPropertyContext } from './content-property.context.js';
|
||||
export { UMB_CONTENT_PROPERTY_CONTEXT } from './content-property.context-token.js';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { UmbEntityActionBase } from '../../entity-action-base.js';
|
||||
import { UmbRequestReloadChildrenOfEntityEvent } from '../../request-reload-children-of-entity.event.js';
|
||||
import { UMB_SORT_CHILDREN_OF_MODAL } from './modal/index.js';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import type { MetaEntityActionSortChildrenOfKind } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import { UmbRequestReloadTreeItemChildrenEvent } from '@umbraco-cms/backoffice/tree';
|
||||
|
||||
export class UmbSortChildrenOfEntityAction extends UmbEntityActionBase<MetaEntityActionSortChildrenOfKind> {
|
||||
async execute() {
|
||||
@@ -22,7 +22,7 @@ export class UmbSortChildrenOfEntityAction extends UmbEntityActionBase<MetaEntit
|
||||
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
|
||||
|
||||
eventContext.dispatchEvent(
|
||||
new UmbRequestReloadTreeItemChildrenEvent({
|
||||
new UmbRequestReloadChildrenOfEntityEvent({
|
||||
unique: this.args.unique,
|
||||
entityType: this.args.entityType,
|
||||
}),
|
||||
|
||||
@@ -7,4 +7,5 @@ export * from './types.js';
|
||||
export type * from './entity-action-element.interface.js';
|
||||
|
||||
export { UmbRequestReloadStructureForEntityEvent } from './request-reload-structure-for-entity.event.js';
|
||||
export { UmbRequestReloadChildrenOfEntityEvent } from './request-reload-children-of-entity.event.js';
|
||||
export { UMB_ENTITY_ACTION_DEFAULT_KIND_MANIFEST } from './default/default.action.kind.js';
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { UmbEntityActionEventArgs } from './entity-action.event.js';
|
||||
import { UmbEntityActionEvent } from './entity-action.event.js';
|
||||
|
||||
export class UmbRequestReloadChildrenOfEntityEvent extends UmbEntityActionEvent {
|
||||
static readonly TYPE = 'request-reload-children-of-entity';
|
||||
|
||||
constructor(args: UmbEntityActionEventArgs) {
|
||||
super(UmbRequestReloadChildrenOfEntityEvent.TYPE, args);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { umbExtensionsRegistry } from '../registry.js';
|
||||
import type { UmbExtensionCollectionFilterModel } from './types.js';
|
||||
import { html, customElement, css } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { fromCamelCase } from '@umbraco-cms/backoffice/utils';
|
||||
import { UMB_COLLECTION_CONTEXT, UmbCollectionDefaultElement } from '@umbraco-cms/backoffice/collection';
|
||||
@@ -7,7 +8,7 @@ import type { UUISelectEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
|
||||
@customElement('umb-extension-collection')
|
||||
export class UmbExtensionCollectionElement extends UmbCollectionDefaultElement {
|
||||
#collectionContext?: UmbDefaultCollectionContext;
|
||||
#collectionContext?: UmbDefaultCollectionContext<any, UmbExtensionCollectionFilterModel>;
|
||||
|
||||
#inputTimer?: NodeJS.Timeout;
|
||||
#inputTimerAmount = 500;
|
||||
@@ -29,15 +30,15 @@ export class UmbExtensionCollectionElement extends UmbCollectionDefaultElement {
|
||||
}
|
||||
|
||||
#onChange(event: UUISelectEvent) {
|
||||
const extensionType = event.target.value;
|
||||
const extensionType = event.target.value as string;
|
||||
this.#collectionContext?.setFilter({ type: extensionType });
|
||||
}
|
||||
|
||||
#onSearch(event: InputEvent) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
const query = target.value || '';
|
||||
const filter = target.value || '';
|
||||
clearTimeout(this.#inputTimer);
|
||||
this.#inputTimer = setTimeout(() => this.#collectionContext?.setFilter({ query }), this.#inputTimerAmount);
|
||||
this.#inputTimer = setTimeout(() => this.#collectionContext?.setFilter({ filter }), this.#inputTimerAmount);
|
||||
}
|
||||
|
||||
protected renderToolbar() {
|
||||
|
||||
@@ -1,39 +1,45 @@
|
||||
import { umbExtensionsRegistry } from '../../registry.js';
|
||||
import type { ManifestTypes } from '../../models/index.js';
|
||||
import type { UmbExtensionCollectionFilterModel, UmbExtensionDetailModel } from '../types.js';
|
||||
import { UMB_EXTENSION_ENTITY_TYPE } from '../../entity.js';
|
||||
import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { UmbCollectionRepository } from '@umbraco-cms/backoffice/collection';
|
||||
|
||||
export interface UmbExtensionCollectionFilter {
|
||||
query?: string;
|
||||
skip: number;
|
||||
take: number;
|
||||
type?: ManifestTypes['type'];
|
||||
}
|
||||
|
||||
export class UmbExtensionCollectionRepository extends UmbRepositoryBase implements UmbCollectionRepository {
|
||||
export class UmbExtensionCollectionRepository
|
||||
extends UmbRepositoryBase
|
||||
implements UmbCollectionRepository<UmbExtensionDetailModel, UmbExtensionCollectionFilterModel>
|
||||
{
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host);
|
||||
}
|
||||
|
||||
async requestCollection(filter: UmbExtensionCollectionFilter) {
|
||||
let extensions = umbExtensionsRegistry.getAllExtensions();
|
||||
async requestCollection(query: UmbExtensionCollectionFilterModel) {
|
||||
let extensions: Array<UmbExtensionDetailModel> = umbExtensionsRegistry.getAllExtensions().map((manifest) => {
|
||||
return {
|
||||
...manifest,
|
||||
unique: manifest.alias,
|
||||
entityType: UMB_EXTENSION_ENTITY_TYPE,
|
||||
};
|
||||
});
|
||||
|
||||
if (filter.query) {
|
||||
const query = filter.query.toLowerCase();
|
||||
const skip = query.skip || 0;
|
||||
const take = query.take || 100;
|
||||
|
||||
if (query.filter) {
|
||||
const text = query.filter.toLowerCase();
|
||||
extensions = extensions.filter(
|
||||
(x) => x.name.toLowerCase().includes(query) || x.alias.toLowerCase().includes(query),
|
||||
(x) => x.name.toLowerCase().includes(text) || x.alias.toLowerCase().includes(text),
|
||||
);
|
||||
}
|
||||
|
||||
if (filter.type) {
|
||||
extensions = extensions.filter((x) => x.type === filter.type);
|
||||
if (query.type) {
|
||||
extensions = extensions.filter((x) => x.type === query.type);
|
||||
}
|
||||
|
||||
extensions.sort((a, b) => a.type.localeCompare(b.type) || a.alias.localeCompare(b.alias));
|
||||
|
||||
const total = extensions.length;
|
||||
const items = extensions.slice(filter.skip, filter.skip + filter.take);
|
||||
const items = extensions.slice(skip, skip + take);
|
||||
const data = { items, total };
|
||||
return { data };
|
||||
}
|
||||
@@ -41,4 +47,4 @@ export class UmbExtensionCollectionRepository extends UmbRepositoryBase implemen
|
||||
destroy(): void {}
|
||||
}
|
||||
|
||||
export default UmbExtensionCollectionRepository;
|
||||
export { UmbExtensionCollectionRepository as api };
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { UmbExtensionEntityType } from '../entity.js';
|
||||
import type { UmbCollectionFilterModel } from '@umbraco-cms/backoffice/collection';
|
||||
import type { ManifestBase } from '@umbraco-cms/backoffice/extension-api';
|
||||
|
||||
export interface UmbExtensionCollectionFilterModel extends UmbCollectionFilterModel {
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export interface UmbExtensionDetailModel extends ManifestBase {
|
||||
unique: string;
|
||||
entityType: UmbExtensionEntityType;
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import { umbExtensionsRegistry } from '../../index.js';
|
||||
import { UMB_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection';
|
||||
import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
import type { ManifestBase } from '@umbraco-cms/backoffice/extension-api';
|
||||
import type { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection';
|
||||
|
||||
@customElement('umb-extension-table-action-column-layout')
|
||||
export class UmbExtensionTableActionColumnLayoutElement extends UmbLitElement {
|
||||
@property({ attribute: false })
|
||||
value!: ManifestBase;
|
||||
|
||||
#collectionContext?: UmbDefaultCollectionContext<ManifestBase>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.consumeContext(UMB_COLLECTION_CONTEXT, (instance) => {
|
||||
this.#collectionContext = instance;
|
||||
});
|
||||
}
|
||||
|
||||
async #removeExtension() {
|
||||
await umbConfirmModal(this, {
|
||||
headline: 'Unload extension',
|
||||
confirmLabel: 'Unload',
|
||||
content: html`<p>Are you sure you want to unload the extension <strong>${this.value.alias}</strong>?</p>`,
|
||||
color: 'danger',
|
||||
});
|
||||
|
||||
umbExtensionsRegistry.unregister(this.value.alias);
|
||||
|
||||
this.#collectionContext?.requestCollection();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-button label="Unload" color="danger" look="primary" @click=${this.#removeExtension}>
|
||||
<uui-icon name="icon-trash"></uui-icon>
|
||||
</uui-button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-extension-table-action-column-layout': UmbExtensionTableActionColumnLayoutElement;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { UmbExtensionCollectionFilterModel, UmbExtensionDetailModel } from '../../types.js';
|
||||
import type { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection';
|
||||
import { UMB_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection';
|
||||
import type { UmbTableColumn, UmbTableConfig, UmbTableItem } from '@umbraco-cms/backoffice/components';
|
||||
import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import type { ManifestBase } from '@umbraco-cms/backoffice/extension-api';
|
||||
|
||||
import '../extension-table-action-column-layout.element.js';
|
||||
import './extension-table-entity-actions-column-layout.element.js';
|
||||
|
||||
@customElement('umb-extension-table-collection-view')
|
||||
export class UmbExtensionTableCollectionViewElement extends UmbLitElement {
|
||||
@@ -36,14 +36,14 @@ export class UmbExtensionTableCollectionViewElement extends UmbLitElement {
|
||||
{
|
||||
name: '',
|
||||
alias: 'extensionAction',
|
||||
elementName: 'umb-extension-table-action-column-layout',
|
||||
elementName: 'umb-extension-table-entity-actions-column-layout',
|
||||
},
|
||||
];
|
||||
|
||||
@state()
|
||||
private _tableItems: Array<UmbTableItem> = [];
|
||||
|
||||
#collectionContext?: UmbDefaultCollectionContext<ManifestBase>;
|
||||
#collectionContext?: UmbDefaultCollectionContext<UmbExtensionDetailModel, UmbExtensionCollectionFilterModel>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -59,10 +59,10 @@ export class UmbExtensionTableCollectionViewElement extends UmbLitElement {
|
||||
this.observe(this.#collectionContext.items, (items) => this.#createTableItems(items), 'umbCollectionItemsObserver');
|
||||
}
|
||||
|
||||
#createTableItems(extensions: Array<ManifestBase>) {
|
||||
#createTableItems(extensions: Array<UmbExtensionDetailModel>) {
|
||||
this._tableItems = extensions.map((extension) => {
|
||||
return {
|
||||
id: extension.alias,
|
||||
id: extension.unique,
|
||||
data: [
|
||||
{
|
||||
columnAlias: 'extensionType',
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { UmbExtensionDetailModel } from '../../types.js';
|
||||
import { html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
|
||||
const elementName = 'umb-extension-table-entity-actions-column-layout';
|
||||
@customElement(elementName)
|
||||
export class UmbExtensionTableEntityActionsColumnLayoutElement extends UmbLitElement {
|
||||
@property({ attribute: false })
|
||||
value!: UmbExtensionDetailModel;
|
||||
|
||||
@state()
|
||||
_isOpen = false;
|
||||
|
||||
#onActionExecuted() {
|
||||
this._isOpen = false;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<umb-dropdown .open=${this._isOpen} compact hide-expand>
|
||||
<uui-symbol-more slot="label"></uui-symbol-more>
|
||||
<umb-entity-action-list
|
||||
@action-executed=${this.#onActionExecuted}
|
||||
entity-type=${this.value.entityType}
|
||||
unique=${ifDefined(this.value.unique)}></umb-entity-action-list>
|
||||
</umb-dropdown>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
[elementName]: UmbExtensionTableEntityActionsColumnLayoutElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { manifests as unregisterManifests } from './unregister/manifests.js';
|
||||
|
||||
export const manifests = [...unregisterManifests];
|
||||
@@ -0,0 +1,16 @@
|
||||
import { UMB_EXTENSION_ENTITY_TYPE } from '../../entity.js';
|
||||
|
||||
export const manifests = [
|
||||
{
|
||||
type: 'entityAction',
|
||||
kind: 'default',
|
||||
alias: 'Umb.EntityAction.Extension.Unregister',
|
||||
name: 'Unregister Extension Entity Action',
|
||||
api: () => import('./unregister-extension.action.js'),
|
||||
forEntityTypes: [UMB_EXTENSION_ENTITY_TYPE],
|
||||
meta: {
|
||||
label: 'Unregister',
|
||||
icon: 'icon-trash',
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,33 @@
|
||||
import { umbExtensionsRegistry } from '../../registry.js';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import { UmbEntityActionBase, UmbRequestReloadStructureForEntityEvent } from '@umbraco-cms/backoffice/entity-action';
|
||||
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
import { html } from '@umbraco-cms/backoffice/external/lit';
|
||||
|
||||
export class UmbUnregisterExtensionEntityAction extends UmbEntityActionBase<unknown> {
|
||||
async execute() {
|
||||
if (!this.args.unique) throw new Error('Cannot delete an item without a unique identifier.');
|
||||
|
||||
const extension = umbExtensionsRegistry.getByAlias(this.args.unique);
|
||||
if (!extension) throw new Error('Extension not found');
|
||||
|
||||
await umbConfirmModal(this, {
|
||||
headline: 'Unregister extension',
|
||||
confirmLabel: 'Unregister',
|
||||
content: html`<p>Are you sure you want to unregister the extension <strong>${extension.alias}</strong>?</p>`,
|
||||
color: 'danger',
|
||||
});
|
||||
|
||||
umbExtensionsRegistry.unregister(extension.alias);
|
||||
|
||||
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
|
||||
const event = new UmbRequestReloadStructureForEntityEvent({
|
||||
unique: this.args.unique,
|
||||
entityType: this.args.entityType,
|
||||
});
|
||||
|
||||
actionEventContext.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
export { UmbUnregisterExtensionEntityAction as api };
|
||||
@@ -0,0 +1,3 @@
|
||||
export const UMB_EXTENSION_ENTITY_TYPE = 'extension';
|
||||
|
||||
export type UmbExtensionEntityType = typeof UMB_EXTENSION_ENTITY_TYPE;
|
||||
@@ -2,6 +2,7 @@ import { manifests as conditionManifests } from './conditions/manifests.js';
|
||||
import { manifests as menuItemManifests } from './menu-item/manifests.js';
|
||||
import { manifests as workspaceManifests } from './workspace/manifests.js';
|
||||
import { manifests as collectionManifests } from './collection/manifests.js';
|
||||
import { manifests as entityActionManifests } from './entity-actions/manifests.js';
|
||||
import type { ManifestTypes } from './models/index.js';
|
||||
|
||||
export const manifests: Array<ManifestTypes> = [
|
||||
@@ -9,4 +10,5 @@ export const manifests: Array<ManifestTypes> = [
|
||||
...menuItemManifests,
|
||||
...workspaceManifests,
|
||||
...collectionManifests,
|
||||
...entityActionManifests,
|
||||
];
|
||||
|
||||
@@ -33,6 +33,7 @@ import type { ManifestMenu } from './menu.model.js';
|
||||
import type { ManifestMenuItem, ManifestMenuItemTreeKind } from './menu-item.model.js';
|
||||
import type { ManifestModal } from './modal.model.js';
|
||||
import type { ManifestPackageView } from './package-view.model.js';
|
||||
import type { ManifestPreviewAppProvider } from './preview-app.model.js';
|
||||
import type { ManifestPropertyAction, ManifestPropertyActionDefaultKind } from './property-action.model.js';
|
||||
import type { ManifestPropertyEditorUi, ManifestPropertyEditorSchema } from './property-editor.model.js';
|
||||
import type { ManifestRepository } from './repository.model.js';
|
||||
@@ -97,6 +98,7 @@ export type * from './mfa-login-provider.model.js';
|
||||
export type * from './modal.model.js';
|
||||
export type * from './monaco-markdown-editor-action.model.js';
|
||||
export type * from './package-view.model.js';
|
||||
export type * from './preview-app.model.js';
|
||||
export type * from './property-action.model.js';
|
||||
export type * from './property-editor.model.js';
|
||||
export type * from './repository.model.js';
|
||||
@@ -183,6 +185,7 @@ export type ManifestTypes =
|
||||
| ManifestModal
|
||||
| ManifestMonacoMarkdownEditorAction
|
||||
| ManifestPackageView
|
||||
| ManifestPreviewAppProvider
|
||||
| ManifestPropertyActions
|
||||
| ManifestPropertyEditorSchema
|
||||
| ManifestPropertyEditorUi
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { ManifestElement } from '@umbraco-cms/backoffice/extension-api';
|
||||
|
||||
/**
|
||||
* Preview apps are displayed in the menu of the preview window.
|
||||
*/
|
||||
export interface ManifestPreviewAppProvider extends ManifestElement {
|
||||
type: 'previewApp';
|
||||
}
|
||||
@@ -2404,6 +2404,10 @@
|
||||
"name": "icon-window-popin",
|
||||
"file": "square-arrow-down-left.svg"
|
||||
},
|
||||
{
|
||||
"name": "icon-window-popout",
|
||||
"file": "square-arrow-up-right.svg"
|
||||
},
|
||||
{
|
||||
"name": "icon-window-sizes",
|
||||
"file": "scaling.svg"
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
export default `<!-- @license lucide-static v0.367.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-square-arrow-up-right"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.75"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<rect width="18" height="18" x="3" y="3" rx="2" />
|
||||
<path d="M8 8h8v8" />
|
||||
<path d="m8 16 8-8" />
|
||||
</svg>
|
||||
`;
|
||||
@@ -2055,6 +2055,10 @@ name: "icon-window-popin",
|
||||
|
||||
path: "./icons/icon-window-popin.js",
|
||||
},{
|
||||
name: "icon-window-popout",
|
||||
|
||||
path: "./icons/icon-window-popout.js",
|
||||
},{
|
||||
name: "icon-window-sizes",
|
||||
|
||||
path: "./icons/icon-window-sizes.js",
|
||||
|
||||
@@ -104,7 +104,7 @@ export class UmbPropertyLayoutElement extends LitElement {
|
||||
height: min-content;
|
||||
}
|
||||
/*@container (width > 600px) {*/
|
||||
#headerColumn {
|
||||
:host(:not([orientation='vertical'])) #headerColumn {
|
||||
position: sticky;
|
||||
top: calc(var(--uui-size-space-2) * -1);
|
||||
}
|
||||
@@ -128,7 +128,7 @@ export class UmbPropertyLayoutElement extends LitElement {
|
||||
margin-top: var(--uui-size-space-3);
|
||||
}
|
||||
/*@container (width > 600px) {*/
|
||||
#editorColumn {
|
||||
:host(:not([orientation='vertical'])) #editorColumn {
|
||||
margin-top: 0;
|
||||
}
|
||||
/*}*/
|
||||
|
||||
@@ -235,7 +235,10 @@ export class UmbPropertyElement extends UmbLitElement {
|
||||
this.#valueObserver = this.observe(
|
||||
this.#propertyContext.value,
|
||||
(value) => {
|
||||
// Set the value on the element:
|
||||
this._element!.value = value;
|
||||
// Set the value on the context as well, to ensure that any default values are stored right away:
|
||||
this.#propertyContext.setValue(value);
|
||||
if (this.#validationMessageBinder) {
|
||||
this.#validationMessageBinder.value = value;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
type MetaEntityActionEmptyRecycleBinKind,
|
||||
} from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import { UmbReloadTreeItemChildrenRequestEntityActionEvent } from '@umbraco-cms/backoffice/tree';
|
||||
import { UmbRequestReloadChildrenOfEntityEvent } from '@umbraco-cms/backoffice/entity-action';
|
||||
|
||||
/**
|
||||
* Entity action for emptying the recycle bin.
|
||||
@@ -34,7 +34,7 @@ export class UmbEmptyRecycleBinEntityAction extends UmbEntityActionBase<MetaEnti
|
||||
await recycleBinRepository.requestEmpty();
|
||||
|
||||
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
|
||||
const event = new UmbReloadTreeItemChildrenRequestEntityActionEvent({
|
||||
const event = new UmbRequestReloadChildrenOfEntityEvent({
|
||||
unique: this.args.unique,
|
||||
entityType: this.args.entityType,
|
||||
});
|
||||
|
||||
@@ -2,7 +2,6 @@ import { UmbTemporaryFileRepository } from './temporary-file.repository.js';
|
||||
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import { UmbId } from '@umbraco-cms/backoffice/id';
|
||||
|
||||
///export type TemporaryFileStatus = 'success' | 'waiting' | 'error';
|
||||
|
||||
@@ -14,7 +13,7 @@ export enum TemporaryFileStatus {
|
||||
|
||||
export interface UmbTemporaryFileModel {
|
||||
file: File;
|
||||
unique: string;
|
||||
temporaryUnique: string;
|
||||
status?: TemporaryFileStatus;
|
||||
}
|
||||
|
||||
@@ -23,7 +22,7 @@ export class UmbTemporaryFileManager<
|
||||
> extends UmbControllerBase {
|
||||
#temporaryFileRepository;
|
||||
|
||||
#queue = new UmbArrayState<UploadableItem>([], (item) => item.unique);
|
||||
#queue = new UmbArrayState<UploadableItem>([], (item) => item.temporaryUnique);
|
||||
public readonly queue = this.#queue.asObservable();
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
@@ -66,18 +65,18 @@ export class UmbTemporaryFileManager<
|
||||
if (!queue.length) return filesCompleted;
|
||||
|
||||
for (const item of queue) {
|
||||
if (!item.unique) throw new Error(`Unique is missing for item ${item}`);
|
||||
if (!item.temporaryUnique) throw new Error(`Unique is missing for item ${item}`);
|
||||
|
||||
const { error } = await this.#temporaryFileRepository.upload(item.unique, item.file);
|
||||
const { error } = await this.#temporaryFileRepository.upload(item.temporaryUnique, item.file);
|
||||
//await new Promise((resolve) => setTimeout(resolve, (Math.random() + 0.5) * 1000)); // simulate small delay so that the upload badge is properly shown
|
||||
|
||||
let status: TemporaryFileStatus;
|
||||
if (error) {
|
||||
status = TemporaryFileStatus.ERROR;
|
||||
this.#queue.updateOne(item.unique, { ...item, status });
|
||||
this.#queue.updateOne(item.temporaryUnique, { ...item, status });
|
||||
} else {
|
||||
status = TemporaryFileStatus.SUCCESS;
|
||||
this.#queue.updateOne(item.unique, { ...item, status });
|
||||
this.#queue.updateOne(item.temporaryUnique, { ...item, status });
|
||||
}
|
||||
|
||||
filesCompleted.push({ ...item, status });
|
||||
|
||||
@@ -23,25 +23,30 @@ export interface UmbTreeDataSourceConstructor<TreeItemType extends UmbTreeItemMo
|
||||
* @interface UmbTreeDataSource
|
||||
* @template TreeItemType
|
||||
*/
|
||||
export interface UmbTreeDataSource<TreeItemType extends UmbTreeItemModel> {
|
||||
export interface UmbTreeDataSource<
|
||||
TreeItemType extends UmbTreeItemModel,
|
||||
TreeRootItemsRequestArgsType extends UmbTreeRootItemsRequestArgs = UmbTreeRootItemsRequestArgs,
|
||||
TreeChildrenOfRequestArgsType extends UmbTreeChildrenOfRequestArgs = UmbTreeChildrenOfRequestArgs,
|
||||
TreeAncestorsOfRequestArgsType extends UmbTreeAncestorsOfRequestArgs = UmbTreeAncestorsOfRequestArgs,
|
||||
> {
|
||||
/**
|
||||
* Gets the root items of the tree.
|
||||
* @return {*} {Promise<UmbDataSourceResponse<UmbPagedModel<TreeItemType>>>}
|
||||
* @memberof UmbTreeDataSource
|
||||
*/
|
||||
getRootItems(args: UmbTreeRootItemsRequestArgs): Promise<UmbDataSourceResponse<UmbPagedModel<TreeItemType>>>;
|
||||
getRootItems(args: TreeRootItemsRequestArgsType): Promise<UmbDataSourceResponse<UmbPagedModel<TreeItemType>>>;
|
||||
|
||||
/**
|
||||
* Gets the children of the given parent item.
|
||||
* @return {*} {Promise<UmbDataSourceResponse<UmbPagedModel<TreeItemType>>}
|
||||
* @memberof UmbTreeDataSource
|
||||
*/
|
||||
getChildrenOf(args: UmbTreeChildrenOfRequestArgs): Promise<UmbDataSourceResponse<UmbPagedModel<TreeItemType>>>;
|
||||
getChildrenOf(args: TreeChildrenOfRequestArgsType): Promise<UmbDataSourceResponse<UmbPagedModel<TreeItemType>>>;
|
||||
|
||||
/**
|
||||
* Gets the ancestors of the given item.
|
||||
* @return {*} {Promise<UmbDataSourceResponse<Array<TreeItemType>>}
|
||||
* @memberof UmbTreeDataSource
|
||||
*/
|
||||
getAncestorsOf(args: UmbTreeAncestorsOfRequestArgs): Promise<UmbDataSourceResponse<Array<TreeItemType>>>;
|
||||
getAncestorsOf(args: TreeAncestorsOfRequestArgsType): Promise<UmbDataSourceResponse<Array<TreeItemType>>>;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import type { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
||||
* @abstract
|
||||
* @class UmbTreeRepositoryBase
|
||||
* @extends {UmbRepositoryBase}
|
||||
* @implements {UmbTreeRepository<TreeItemType, TreeRootType>}
|
||||
* @implements {UmbTreeRepository}
|
||||
* @implements {UmbApi}
|
||||
* @template TreeItemType
|
||||
* @template TreeRootType
|
||||
@@ -27,9 +27,20 @@ import type { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
||||
export abstract class UmbTreeRepositoryBase<
|
||||
TreeItemType extends UmbTreeItemModel,
|
||||
TreeRootType extends UmbTreeRootModel,
|
||||
TreeRootItemsRequestArgsType extends UmbTreeRootItemsRequestArgs = UmbTreeRootItemsRequestArgs,
|
||||
TreeChildrenOfRequestArgsType extends UmbTreeChildrenOfRequestArgs = UmbTreeChildrenOfRequestArgs,
|
||||
TreeAncestorsOfRequestArgsType extends UmbTreeAncestorsOfRequestArgs = UmbTreeAncestorsOfRequestArgs,
|
||||
>
|
||||
extends UmbRepositoryBase
|
||||
implements UmbTreeRepository<TreeItemType, TreeRootType>, UmbApi
|
||||
implements
|
||||
UmbTreeRepository<
|
||||
TreeItemType,
|
||||
TreeRootType,
|
||||
TreeRootItemsRequestArgsType,
|
||||
TreeChildrenOfRequestArgsType,
|
||||
TreeAncestorsOfRequestArgsType
|
||||
>,
|
||||
UmbApi
|
||||
{
|
||||
protected _init: Promise<unknown>;
|
||||
protected _treeStore?: UmbTreeStore<TreeItemType>;
|
||||
@@ -67,7 +78,7 @@ export abstract class UmbTreeRepositoryBase<
|
||||
* @return {*}
|
||||
* @memberof UmbTreeRepositoryBase
|
||||
*/
|
||||
async requestRootTreeItems(args: UmbTreeRootItemsRequestArgs) {
|
||||
async requestTreeRootItems(args: TreeRootItemsRequestArgsType) {
|
||||
await this._init;
|
||||
|
||||
const { data, error: _error } = await this._treeSource.getRootItems(args);
|
||||
@@ -85,7 +96,7 @@ export abstract class UmbTreeRepositoryBase<
|
||||
* @return {*}
|
||||
* @memberof UmbTreeRepositoryBase
|
||||
*/
|
||||
async requestTreeItemsOf(args: UmbTreeChildrenOfRequestArgs) {
|
||||
async requestTreeItemsOf(args: TreeChildrenOfRequestArgsType) {
|
||||
if (!args.parent) throw new Error('Parent is missing');
|
||||
if (args.parent.unique === undefined) throw new Error('Parent unique is missing');
|
||||
if (args.parent.entityType === null) throw new Error('Parent entity type is missing');
|
||||
@@ -106,7 +117,7 @@ export abstract class UmbTreeRepositoryBase<
|
||||
* @return {*}
|
||||
* @memberof UmbTreeRepositoryBase
|
||||
*/
|
||||
async requestTreeItemAncestors(args: UmbTreeAncestorsOfRequestArgs) {
|
||||
async requestTreeItemAncestors(args: TreeAncestorsOfRequestArgsType) {
|
||||
if (args.treeItem.unique === undefined) throw new Error('Descendant unique is missing');
|
||||
await this._init;
|
||||
|
||||
|
||||
@@ -20,6 +20,9 @@ import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
|
||||
export interface UmbTreeRepository<
|
||||
TreeItemType extends UmbTreeItemModel = UmbTreeItemModel,
|
||||
TreeRootType extends UmbTreeRootModel = UmbTreeRootModel,
|
||||
TreeRootItemsRequestArgsType extends UmbTreeRootItemsRequestArgs = UmbTreeRootItemsRequestArgs,
|
||||
TreeChildrenOfRequestArgsType extends UmbTreeChildrenOfRequestArgs = UmbTreeChildrenOfRequestArgs,
|
||||
TreeAncestorsOfRequestArgsType extends UmbTreeAncestorsOfRequestArgs = UmbTreeAncestorsOfRequestArgs,
|
||||
> extends UmbApi {
|
||||
/**
|
||||
* Requests the root of the tree.
|
||||
@@ -35,7 +38,7 @@ export interface UmbTreeRepository<
|
||||
* @param {UmbTreeRootItemsRequestArgs} args
|
||||
* @memberof UmbTreeRepository
|
||||
*/
|
||||
requestRootTreeItems: (args: UmbTreeRootItemsRequestArgs) => Promise<{
|
||||
requestTreeRootItems: (args: TreeRootItemsRequestArgsType) => Promise<{
|
||||
data?: UmbPagedModel<TreeItemType>;
|
||||
error?: ProblemDetails;
|
||||
asObservable?: () => Observable<TreeItemType[]>;
|
||||
@@ -46,7 +49,7 @@ export interface UmbTreeRepository<
|
||||
* @param {UmbTreeChildrenOfRequestArgs} args
|
||||
* @memberof UmbTreeRepository
|
||||
*/
|
||||
requestTreeItemsOf: (args: UmbTreeChildrenOfRequestArgs) => Promise<{
|
||||
requestTreeItemsOf: (args: TreeChildrenOfRequestArgsType) => Promise<{
|
||||
data?: UmbPagedModel<TreeItemType>;
|
||||
error?: ProblemDetails;
|
||||
asObservable?: () => Observable<TreeItemType[]>;
|
||||
@@ -58,7 +61,7 @@ export interface UmbTreeRepository<
|
||||
* @memberof UmbTreeRepository
|
||||
*/
|
||||
requestTreeItemAncestors: (
|
||||
args: UmbTreeAncestorsOfRequestArgs,
|
||||
args: TreeAncestorsOfRequestArgsType,
|
||||
) => Promise<{ data?: TreeItemType[]; error?: ProblemDetails; asObservable?: () => Observable<TreeItemType[]> }>;
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,10 +12,13 @@ import type { UmbPagedModel } from '@umbraco-cms/backoffice/repository';
|
||||
export interface UmbTreeServerDataSourceBaseArgs<
|
||||
ServerTreeItemType extends { hasChildren: boolean },
|
||||
ClientTreeItemType extends UmbTreeItemModelBase,
|
||||
TreeRootItemsRequestArgsType extends UmbTreeRootItemsRequestArgs = UmbTreeRootItemsRequestArgs,
|
||||
TreeChildrenOfRequestArgsType extends UmbTreeChildrenOfRequestArgs = UmbTreeChildrenOfRequestArgs,
|
||||
TreeAncestorsOfRequestArgsType extends UmbTreeAncestorsOfRequestArgs = UmbTreeAncestorsOfRequestArgs,
|
||||
> {
|
||||
getRootItems: (args: UmbTreeRootItemsRequestArgs) => Promise<UmbPagedModel<ServerTreeItemType>>;
|
||||
getChildrenOf: (args: UmbTreeChildrenOfRequestArgs) => Promise<UmbPagedModel<ServerTreeItemType>>;
|
||||
getAncestorsOf: (args: UmbTreeAncestorsOfRequestArgs) => Promise<Array<ServerTreeItemType>>;
|
||||
getRootItems: (args: TreeRootItemsRequestArgsType) => Promise<UmbPagedModel<ServerTreeItemType>>;
|
||||
getChildrenOf: (args: TreeChildrenOfRequestArgsType) => Promise<UmbPagedModel<ServerTreeItemType>>;
|
||||
getAncestorsOf: (args: TreeAncestorsOfRequestArgsType) => Promise<Array<ServerTreeItemType>>;
|
||||
mapper: (item: ServerTreeItemType) => ClientTreeItemType;
|
||||
}
|
||||
|
||||
@@ -28,7 +31,16 @@ export interface UmbTreeServerDataSourceBaseArgs<
|
||||
export abstract class UmbTreeServerDataSourceBase<
|
||||
ServerTreeItemType extends { hasChildren: boolean },
|
||||
ClientTreeItemType extends UmbTreeItemModel,
|
||||
> implements UmbTreeDataSource<ClientTreeItemType>
|
||||
TreeRootItemsRequestArgsType extends UmbTreeRootItemsRequestArgs = UmbTreeRootItemsRequestArgs,
|
||||
TreeChildrenOfRequestArgsType extends UmbTreeChildrenOfRequestArgs = UmbTreeChildrenOfRequestArgs,
|
||||
TreeAncestorsOfRequestArgsType extends UmbTreeAncestorsOfRequestArgs = UmbTreeAncestorsOfRequestArgs,
|
||||
> implements
|
||||
UmbTreeDataSource<
|
||||
ClientTreeItemType,
|
||||
TreeRootItemsRequestArgsType,
|
||||
TreeChildrenOfRequestArgsType,
|
||||
TreeAncestorsOfRequestArgsType
|
||||
>
|
||||
{
|
||||
#host;
|
||||
#getRootItems;
|
||||
@@ -41,7 +53,16 @@ export abstract class UmbTreeServerDataSourceBase<
|
||||
* @param {UmbControllerHost} host
|
||||
* @memberof UmbTreeServerDataSourceBase
|
||||
*/
|
||||
constructor(host: UmbControllerHost, args: UmbTreeServerDataSourceBaseArgs<ServerTreeItemType, ClientTreeItemType>) {
|
||||
constructor(
|
||||
host: UmbControllerHost,
|
||||
args: UmbTreeServerDataSourceBaseArgs<
|
||||
ServerTreeItemType,
|
||||
ClientTreeItemType,
|
||||
TreeRootItemsRequestArgsType,
|
||||
TreeChildrenOfRequestArgsType,
|
||||
TreeAncestorsOfRequestArgsType
|
||||
>,
|
||||
) {
|
||||
this.#host = host;
|
||||
this.#getRootItems = args.getRootItems;
|
||||
this.#getChildrenOf = args.getChildrenOf;
|
||||
@@ -55,7 +76,7 @@ export abstract class UmbTreeServerDataSourceBase<
|
||||
* @return {*}
|
||||
* @memberof UmbTreeServerDataSourceBase
|
||||
*/
|
||||
async getRootItems(args: UmbTreeRootItemsRequestArgs) {
|
||||
async getRootItems(args: TreeRootItemsRequestArgsType) {
|
||||
const { data, error } = await tryExecuteAndNotify(this.#host, this.#getRootItems(args));
|
||||
|
||||
if (data) {
|
||||
@@ -72,7 +93,7 @@ export abstract class UmbTreeServerDataSourceBase<
|
||||
* @return {*}
|
||||
* @memberof UmbTreeServerDataSourceBase
|
||||
*/
|
||||
async getChildrenOf(args: UmbTreeChildrenOfRequestArgs) {
|
||||
async getChildrenOf(args: TreeChildrenOfRequestArgsType) {
|
||||
if (args.parent.unique === undefined) throw new Error('Parent unique is missing');
|
||||
|
||||
const { data, error } = await tryExecuteAndNotify(this.#host, this.#getChildrenOf(args));
|
||||
@@ -91,7 +112,7 @@ export abstract class UmbTreeServerDataSourceBase<
|
||||
* @return {*}
|
||||
* @memberof UmbTreeServerDataSourceBase
|
||||
*/
|
||||
async getAncestorsOf(args: UmbTreeAncestorsOfRequestArgs) {
|
||||
async getAncestorsOf(args: TreeAncestorsOfRequestArgsType) {
|
||||
if (!args.treeItem.entityType) throw new Error('Parent unique is missing');
|
||||
|
||||
const { data, error } = await tryExecuteAndNotify(this.#host, this.#getAncestorsOf(args));
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { UmbTreeItemModel, UmbTreeRootModel } from '../types.js';
|
||||
import type { UmbDefaultTreeContext } from './default-tree.context.js';
|
||||
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
||||
|
||||
export const UMB_TREE_CONTEXT = new UmbContextToken<UmbDefaultTreeContext<UmbTreeItemModel, UmbTreeRootModel>>(
|
||||
'UmbTreeContext',
|
||||
);
|
||||
@@ -2,6 +2,8 @@ import { UmbRequestReloadTreeItemChildrenEvent } from '../reload-tree-item-child
|
||||
import type { UmbTreeItemModel, UmbTreeRootModel, UmbTreeStartNode } from '../types.js';
|
||||
import type { UmbTreeRepository } from '../data/tree-repository.interface.js';
|
||||
import type { UmbTreeContext } from '../tree-context.interface.js';
|
||||
import type { UmbTreeRootItemsRequestArgs } from '../data/types.js';
|
||||
import { UMB_TREE_CONTEXT } from './default-tree.context-token.js';
|
||||
import { type UmbActionEventContext, UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import {
|
||||
type ManifestRepository,
|
||||
@@ -12,15 +14,24 @@ import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api';
|
||||
import { UmbPaginationManager, UmbSelectionManager, debounce } from '@umbraco-cms/backoffice/utils';
|
||||
import type { UmbEntityActionEvent } from '@umbraco-cms/backoffice/entity-action';
|
||||
import {
|
||||
UmbRequestReloadChildrenOfEntityEvent,
|
||||
type UmbEntityActionEvent,
|
||||
} from '@umbraco-cms/backoffice/entity-action';
|
||||
import { UmbArrayState, UmbBooleanState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
|
||||
export class UmbDefaultTreeContext<TreeItemType extends UmbTreeItemModel, TreeRootType extends UmbTreeRootModel>
|
||||
extends UmbContextBase<UmbDefaultTreeContext<TreeItemType, TreeRootType>>
|
||||
export class UmbDefaultTreeContext<
|
||||
TreeItemType extends UmbTreeItemModel,
|
||||
TreeRootType extends UmbTreeRootModel,
|
||||
RequestArgsType extends UmbTreeRootItemsRequestArgs = UmbTreeRootItemsRequestArgs,
|
||||
>
|
||||
extends UmbContextBase<UmbDefaultTreeContext<TreeItemType, TreeRootType, RequestArgsType>>
|
||||
implements UmbTreeContext
|
||||
{
|
||||
#additionalRequestArgs = new UmbObjectState<Partial<RequestArgsType> | object>({});
|
||||
public readonly additionalRequestArgs = this.#additionalRequestArgs.asObservable();
|
||||
|
||||
#treeRoot = new UmbObjectState<TreeRootType | undefined>(undefined);
|
||||
treeRoot = this.#treeRoot.asObservable();
|
||||
|
||||
@@ -57,7 +68,7 @@ export class UmbDefaultTreeContext<TreeItemType extends UmbTreeItemModel, TreeRo
|
||||
constructor(host: UmbControllerHost) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
super(host, UMB_DEFAULT_TREE_CONTEXT);
|
||||
super(host, UMB_TREE_CONTEXT);
|
||||
this.pagination.setPageSize(this.#paging.take);
|
||||
this.#consumeContexts();
|
||||
|
||||
@@ -136,7 +147,7 @@ export class UmbDefaultTreeContext<TreeItemType extends UmbTreeItemModel, TreeRo
|
||||
public loadMore = () => this.#debouncedLoadTree(true);
|
||||
|
||||
#debouncedLoadTree(reload = false) {
|
||||
if (this.getStartFrom()) {
|
||||
if (this.getStartNode()) {
|
||||
this.#loadRootItems(reload);
|
||||
return;
|
||||
}
|
||||
@@ -166,10 +177,12 @@ export class UmbDefaultTreeContext<TreeItemType extends UmbTreeItemModel, TreeRo
|
||||
const take = loadMore ? this.#paging.take : this.pagination.getCurrentPageNumber() * this.#paging.take;
|
||||
|
||||
// If we have a start node get children of that instead of the root
|
||||
const startNode = this.getStartFrom();
|
||||
const startNode = this.getStartNode();
|
||||
const additionalArgs = this.#additionalRequestArgs.getValue();
|
||||
|
||||
const { data } = startNode?.unique
|
||||
? await this.#repository!.requestTreeItemsOf({
|
||||
...additionalArgs,
|
||||
parent: {
|
||||
unique: startNode.unique,
|
||||
entityType: startNode.entityType,
|
||||
@@ -177,7 +190,8 @@ export class UmbDefaultTreeContext<TreeItemType extends UmbTreeItemModel, TreeRo
|
||||
skip,
|
||||
take,
|
||||
})
|
||||
: await this.#repository!.requestRootTreeItems({
|
||||
: await this.#repository!.requestTreeRootItems({
|
||||
...additionalArgs,
|
||||
skip,
|
||||
take,
|
||||
});
|
||||
@@ -220,19 +234,32 @@ export class UmbDefaultTreeContext<TreeItemType extends UmbTreeItemModel, TreeRo
|
||||
* @param {UmbTreeStartNode} startNode
|
||||
* @memberof UmbDefaultTreeContext
|
||||
*/
|
||||
setStartFrom(startNode: UmbTreeStartNode | undefined) {
|
||||
setStartNode(startNode: UmbTreeStartNode | undefined) {
|
||||
this.#startNode.setValue(startNode);
|
||||
// we need to reset the tree if this config changes
|
||||
this.#resetTree();
|
||||
this.loadTree();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the requestArgs config and reloads the tree.
|
||||
*/
|
||||
public updateAdditionalRequestArgs(args: Partial<RequestArgsType>) {
|
||||
this.#additionalRequestArgs.setValue({ ...this.#additionalRequestArgs.getValue(), ...args });
|
||||
this.#resetTree();
|
||||
this.loadTree();
|
||||
}
|
||||
|
||||
public getAdditionalRequestArgs() {
|
||||
return this.#additionalRequestArgs.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the startNode config
|
||||
* @return {UmbTreeStartNode}
|
||||
* @memberof UmbDefaultTreeContext
|
||||
*/
|
||||
getStartFrom() {
|
||||
getStartNode() {
|
||||
return this.#startNode.getValue();
|
||||
}
|
||||
|
||||
@@ -245,14 +272,26 @@ export class UmbDefaultTreeContext<TreeItemType extends UmbTreeItemModel, TreeRo
|
||||
#consumeContexts() {
|
||||
this.consumeContext(UMB_ACTION_EVENT_CONTEXT, (instance) => {
|
||||
this.#actionEventContext = instance;
|
||||
|
||||
this.#actionEventContext.removeEventListener(
|
||||
UmbRequestReloadTreeItemChildrenEvent.TYPE,
|
||||
this.#onReloadRequest as EventListener,
|
||||
);
|
||||
|
||||
this.#actionEventContext.removeEventListener(
|
||||
UmbRequestReloadChildrenOfEntityEvent.TYPE,
|
||||
this.#onReloadRequest as EventListener,
|
||||
);
|
||||
|
||||
this.#actionEventContext.addEventListener(
|
||||
UmbRequestReloadTreeItemChildrenEvent.TYPE,
|
||||
this.#onReloadRequest as EventListener,
|
||||
);
|
||||
|
||||
this.#actionEventContext.addEventListener(
|
||||
UmbRequestReloadChildrenOfEntityEvent.TYPE,
|
||||
this.#onReloadRequest as EventListener,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -291,12 +330,14 @@ export class UmbDefaultTreeContext<TreeItemType extends UmbTreeItemModel, TreeRo
|
||||
UmbRequestReloadTreeItemChildrenEvent.TYPE,
|
||||
this.#onReloadRequest as EventListener,
|
||||
);
|
||||
|
||||
this.#actionEventContext?.removeEventListener(
|
||||
UmbRequestReloadChildrenOfEntityEvent.TYPE,
|
||||
this.#onReloadRequest as EventListener,
|
||||
);
|
||||
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbDefaultTreeContext;
|
||||
|
||||
export const UMB_DEFAULT_TREE_CONTEXT = new UmbContextToken<UmbDefaultTreeContext<UmbTreeItemModel, UmbTreeRootModel>>(
|
||||
'UmbTreeContext',
|
||||
);
|
||||
export { UmbDefaultTreeContext as api };
|
||||
|
||||
@@ -6,7 +6,7 @@ import type {
|
||||
UmbTreeStartNode,
|
||||
} from '../types.js';
|
||||
import type { UmbDefaultTreeContext } from './default-tree.context.js';
|
||||
import { UMB_DEFAULT_TREE_CONTEXT } from './default-tree.context.js';
|
||||
import { UMB_TREE_CONTEXT } from './default-tree.context-token.js';
|
||||
import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { html, nothing, customElement, property, state, repeat } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
@@ -57,7 +57,7 @@ export class UmbDefaultTreeElement extends UmbLitElement {
|
||||
|
||||
this.#init = Promise.all([
|
||||
// TODO: Notice this can be retrieve via a api property. [NL]
|
||||
this.consumeContext(UMB_DEFAULT_TREE_CONTEXT, (instance) => {
|
||||
this.consumeContext(UMB_TREE_CONTEXT, (instance) => {
|
||||
this.#treeContext = instance;
|
||||
this.observe(this.#treeContext.treeRoot, (treeRoot) => (this._treeRoot = treeRoot));
|
||||
this.observe(this.#treeContext.rootItems, (rootItems) => (this._rootItems = rootItems));
|
||||
@@ -80,7 +80,7 @@ export class UmbDefaultTreeElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
if (_changedProperties.has('startNode')) {
|
||||
this.#treeContext!.setStartFrom(this.startNode);
|
||||
this.#treeContext!.setStartNode(this.startNode);
|
||||
}
|
||||
|
||||
if (_changedProperties.has('hideTreeRoot')) {
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export { UmbDefaultTreeElement as UmbTreeDefaultElement } from './default-tree.element.js';
|
||||
export { UmbDefaultTreeContext as UmbTreeDefaultContext } from './default-tree.context.js';
|
||||
export { UmbDefaultTreeElement } from './default-tree.element.js';
|
||||
export { UmbDefaultTreeContext } from './default-tree.context.js';
|
||||
export { UMB_TREE_CONTEXT } from './default-tree.context-token.js';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
|
||||
import { UmbEntityActionBase, UmbRequestReloadChildrenOfEntityEvent } from '@umbraco-cms/backoffice/entity-action';
|
||||
import type { MetaEntityActionFolderKind } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_FOLDER_CREATE_MODAL, UmbRequestReloadTreeItemChildrenEvent } from '@umbraco-cms/backoffice/tree';
|
||||
import { UMB_FOLDER_CREATE_MODAL } from '@umbraco-cms/backoffice/tree';
|
||||
|
||||
export class UmbCreateFolderEntityAction extends UmbEntityActionBase<MetaEntityActionFolderKind> {
|
||||
async execute() {
|
||||
@@ -20,7 +20,7 @@ export class UmbCreateFolderEntityAction extends UmbEntityActionBase<MetaEntityA
|
||||
await modalContext.onSubmit();
|
||||
|
||||
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
|
||||
const event = new UmbRequestReloadTreeItemChildrenEvent({
|
||||
const event = new UmbRequestReloadChildrenOfEntityEvent({
|
||||
entityType: this.args.entityType,
|
||||
unique: this.args.unique,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { UmbRequestReloadTreeItemChildrenEvent } from './reload-tree-item-children/index.js';
|
||||
|
||||
export * from './tree-item/index.js';
|
||||
export * from './default/index.js';
|
||||
export * from './data/index.js';
|
||||
@@ -16,9 +14,3 @@ export type { UmbTreePickerModalData, UmbTreePickerModalValue } from './tree-pic
|
||||
export { UMB_TREE_PICKER_MODAL, UMB_TREE_PICKER_MODAL_ALIAS } from './tree-picker/index.js';
|
||||
|
||||
export * from './types.js';
|
||||
|
||||
/*
|
||||
* @deprecated Use UmbRequestReloadTreeItemChildrenEvent instead — Will be removed before RC.
|
||||
* TODO: Delete before RC.
|
||||
*/
|
||||
export { UmbRequestReloadTreeItemChildrenEvent as UmbReloadTreeItemChildrenRequestEntityActionEvent };
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { UmbEntityActionEvent, type UmbEntityActionEventArgs } from '@umbraco-cms/backoffice/entity-action';
|
||||
|
||||
/**
|
||||
* @deprecated Use `UmbRequestReloadChildrenOfEntityEvent` instead.
|
||||
*/
|
||||
export class UmbRequestReloadTreeItemChildrenEvent extends UmbEntityActionEvent {
|
||||
static readonly TYPE = 'request-reload-tree-item-children';
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { UmbRequestReloadTreeItemChildrenEvent } from './reload-tree-item-children-request.event.js';
|
||||
import type { UmbEntityActionArgs } from '@umbraco-cms/backoffice/entity-action';
|
||||
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
|
||||
import { UmbEntityActionBase, UmbRequestReloadChildrenOfEntityEvent } from '@umbraco-cms/backoffice/entity-action';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import type { MetaEntityActionReloadTreeItemChildrenKind } from '@umbraco-cms/backoffice/extension-registry';
|
||||
@@ -14,7 +13,7 @@ export class UmbReloadTreeItemChildrenEntityAction extends UmbEntityActionBase<M
|
||||
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
|
||||
|
||||
eventContext.dispatchEvent(
|
||||
new UmbRequestReloadTreeItemChildrenEvent({
|
||||
new UmbRequestReloadChildrenOfEntityEvent({
|
||||
unique: this.args.unique,
|
||||
entityType: this.args.entityType,
|
||||
}),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { UmbTreeItemContext } from '../tree-item-context.interface.js';
|
||||
import { UMB_DEFAULT_TREE_CONTEXT, type UmbDefaultTreeContext } from '../../default/default-tree.context.js';
|
||||
import { UMB_TREE_CONTEXT, type UmbDefaultTreeContext } from '../../default/index.js';
|
||||
import type { UmbTreeItemModel, UmbTreeRootModel } from '../../types.js';
|
||||
import { UmbRequestReloadTreeItemChildrenEvent } from '../../reload-tree-item-children/index.js';
|
||||
import { map } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
@@ -12,7 +12,10 @@ import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
||||
import { UMB_ACTION_EVENT_CONTEXT, type UmbActionEventContext } from '@umbraco-cms/backoffice/action';
|
||||
import { UmbRequestReloadStructureForEntityEvent } from '@umbraco-cms/backoffice/entity-action';
|
||||
import {
|
||||
UmbRequestReloadChildrenOfEntityEvent,
|
||||
UmbRequestReloadStructureForEntityEvent,
|
||||
} from '@umbraco-cms/backoffice/entity-action';
|
||||
import type { UmbEntityActionEvent } from '@umbraco-cms/backoffice/entity-action';
|
||||
import { UmbPaginationManager, debounce } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
@@ -172,6 +175,7 @@ export abstract class UmbTreeItemContextBase<
|
||||
|
||||
const skip = loadMore ? this.#paging.skip : 0;
|
||||
const take = loadMore ? this.#paging.take : this.pagination.getCurrentPageNumber() * this.#paging.take;
|
||||
const additionalArgs = this.treeContext?.getAdditionalRequestArgs();
|
||||
|
||||
const { data } = await repository.requestTreeItemsOf({
|
||||
parent: {
|
||||
@@ -180,6 +184,7 @@ export abstract class UmbTreeItemContextBase<
|
||||
},
|
||||
skip,
|
||||
take,
|
||||
...additionalArgs,
|
||||
});
|
||||
|
||||
if (data) {
|
||||
@@ -229,7 +234,7 @@ export abstract class UmbTreeItemContextBase<
|
||||
this.#sectionSidebarContext = instance;
|
||||
});
|
||||
|
||||
this.consumeContext(UMB_DEFAULT_TREE_CONTEXT, (treeContext) => {
|
||||
this.consumeContext(UMB_TREE_CONTEXT, (treeContext) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
this.treeContext = treeContext;
|
||||
@@ -243,6 +248,11 @@ export abstract class UmbTreeItemContextBase<
|
||||
this.#onReloadRequest as EventListener,
|
||||
);
|
||||
|
||||
this.#actionEventContext?.removeEventListener(
|
||||
UmbRequestReloadChildrenOfEntityEvent.TYPE,
|
||||
this.#onReloadRequest as EventListener,
|
||||
);
|
||||
|
||||
this.#actionEventContext?.removeEventListener(
|
||||
UmbRequestReloadStructureForEntityEvent.TYPE,
|
||||
this.#onReloadStructureRequest as unknown as EventListener,
|
||||
@@ -255,6 +265,11 @@ export abstract class UmbTreeItemContextBase<
|
||||
this.#onReloadRequest as EventListener,
|
||||
);
|
||||
|
||||
this.#actionEventContext.addEventListener(
|
||||
UmbRequestReloadChildrenOfEntityEvent.TYPE,
|
||||
this.#onReloadRequest as EventListener,
|
||||
);
|
||||
|
||||
this.#actionEventContext.addEventListener(
|
||||
UmbRequestReloadStructureForEntityEvent.TYPE,
|
||||
this.#onReloadStructureRequest as unknown as EventListener,
|
||||
@@ -382,6 +397,12 @@ export abstract class UmbTreeItemContextBase<
|
||||
UmbRequestReloadTreeItemChildrenEvent.TYPE,
|
||||
this.#onReloadRequest as EventListener,
|
||||
);
|
||||
|
||||
this.#actionEventContext?.removeEventListener(
|
||||
UmbRequestReloadChildrenOfEntityEvent.TYPE,
|
||||
this.#onReloadRequest as EventListener,
|
||||
);
|
||||
|
||||
this.#actionEventContext?.removeEventListener(
|
||||
UmbRequestReloadStructureForEntityEvent.TYPE,
|
||||
this.#onReloadStructureRequest as unknown as EventListener,
|
||||
|
||||
@@ -67,7 +67,6 @@ export class UmbWorkspaceViewCollectionElement extends UmbLitElement implements
|
||||
orderBy: config?.getValueByAlias('orderBy') ?? 'updateDate',
|
||||
orderDirection: config?.getValueByAlias('orderDirection') ?? 'asc',
|
||||
pageSize: Number(config?.getValueByAlias('pageSize')) ?? 50,
|
||||
useInfiniteEditor: config?.getValueByAlias('useInfiniteEditor') ?? false,
|
||||
userDefinedProperties: config?.getValueByAlias('includeProperties'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -27,8 +27,10 @@ import type {
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UMB_PROPERTY_EDITOR_SCHEMA_ALIAS_DEFAULT } from '@umbraco-cms/backoffice/property-editor';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import { UmbRequestReloadTreeItemChildrenEvent } from '@umbraco-cms/backoffice/tree';
|
||||
import { UmbRequestReloadStructureForEntityEvent } from '@umbraco-cms/backoffice/entity-action';
|
||||
import {
|
||||
UmbRequestReloadChildrenOfEntityEvent,
|
||||
UmbRequestReloadStructureForEntityEvent,
|
||||
} from '@umbraco-cms/backoffice/entity-action';
|
||||
|
||||
type EntityType = UmbDataTypeDetailModel;
|
||||
export class UmbDataTypeWorkspaceContext
|
||||
@@ -347,7 +349,7 @@ export class UmbDataTypeWorkspaceContext
|
||||
|
||||
// TODO: this might not be the right place to alert the tree, but it works for now
|
||||
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
|
||||
const event = new UmbRequestReloadTreeItemChildrenEvent({
|
||||
const event = new UmbRequestReloadChildrenOfEntityEvent({
|
||||
entityType: parent.entityType,
|
||||
unique: parent.unique,
|
||||
});
|
||||
|
||||
@@ -10,9 +10,11 @@ import {
|
||||
} from '@umbraco-cms/backoffice/workspace';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { UmbRequestReloadTreeItemChildrenEvent } from '@umbraco-cms/backoffice/tree';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import { UmbRequestReloadStructureForEntityEvent } from '@umbraco-cms/backoffice/entity-action';
|
||||
import {
|
||||
UmbRequestReloadChildrenOfEntityEvent,
|
||||
UmbRequestReloadStructureForEntityEvent,
|
||||
} from '@umbraco-cms/backoffice/entity-action';
|
||||
|
||||
export class UmbDictionaryWorkspaceContext
|
||||
extends UmbSubmittableWorkspaceContextBase<UmbDictionaryDetailModel>
|
||||
@@ -146,7 +148,7 @@ export class UmbDictionaryWorkspaceContext
|
||||
|
||||
// TODO: this might not be the right place to alert the tree, but it works for now
|
||||
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
|
||||
const event = new UmbRequestReloadTreeItemChildrenEvent({
|
||||
const event = new UmbRequestReloadChildrenOfEntityEvent({
|
||||
entityType: parent.entityType,
|
||||
unique: parent.unique,
|
||||
});
|
||||
|
||||
@@ -25,8 +25,10 @@ import {
|
||||
UmbDocumentTypeDetailRepository,
|
||||
} from '@umbraco-cms/backoffice/document-type';
|
||||
import { UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language';
|
||||
import { UmbRequestReloadTreeItemChildrenEvent } from '@umbraco-cms/backoffice/tree';
|
||||
import { UmbRequestReloadStructureForEntityEvent } from '@umbraco-cms/backoffice/entity-action';
|
||||
import {
|
||||
UmbRequestReloadChildrenOfEntityEvent,
|
||||
UmbRequestReloadStructureForEntityEvent,
|
||||
} from '@umbraco-cms/backoffice/entity-action';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import { UMB_INVARIANT_CULTURE, UmbVariantId } from '@umbraco-cms/backoffice/variant';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
@@ -407,7 +409,7 @@ export class UmbDocumentBlueprintWorkspaceContext
|
||||
|
||||
// TODO: this might not be the right place to alert the tree, but it works for now
|
||||
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
|
||||
const event = new UmbRequestReloadTreeItemChildrenEvent({
|
||||
const event = new UmbRequestReloadChildrenOfEntityEvent({
|
||||
entityType: parent.entityType,
|
||||
unique: parent.unique,
|
||||
});
|
||||
|
||||
@@ -11,8 +11,10 @@ import {
|
||||
import { UmbDocumentTypeWorkspaceEditorElement } from './document-type-workspace-editor.element.js';
|
||||
import { UmbContentTypeStructureManager } from '@umbraco-cms/backoffice/content-type';
|
||||
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { UmbRequestReloadTreeItemChildrenEvent } from '@umbraco-cms/backoffice/tree';
|
||||
import { UmbRequestReloadStructureForEntityEvent } from '@umbraco-cms/backoffice/entity-action';
|
||||
import {
|
||||
UmbRequestReloadChildrenOfEntityEvent,
|
||||
UmbRequestReloadStructureForEntityEvent,
|
||||
} from '@umbraco-cms/backoffice/entity-action';
|
||||
import {
|
||||
UmbSubmittableWorkspaceContextBase,
|
||||
UmbWorkspaceIsNewRedirectController,
|
||||
@@ -295,7 +297,7 @@ export class UmbDocumentTypeWorkspaceContext
|
||||
|
||||
// TODO: this might not be the right place to alert the tree, but it works for now
|
||||
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
|
||||
const event = new UmbRequestReloadTreeItemChildrenEvent({
|
||||
const event = new UmbRequestReloadChildrenOfEntityEvent({
|
||||
entityType: parent.entityType,
|
||||
unique: parent.unique,
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { UMB_DOCUMENT_COLLECTION_CONTEXT } from '../document-collection.context-token.js';
|
||||
import { css, customElement, html, map, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbDocumentTypeStructureRepository } from '@umbraco-cms/backoffice/document-type';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UMB_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection';
|
||||
import {
|
||||
UMB_CREATE_DOCUMENT_WORKSPACE_PATH_PATTERN,
|
||||
UMB_DOCUMENT_ENTITY_TYPE,
|
||||
@@ -35,9 +35,6 @@ export class UmbCreateDocumentCollectionActionElement extends UmbLitElement {
|
||||
@state()
|
||||
private _rootPathName?: string;
|
||||
|
||||
@state()
|
||||
private _useInfiniteEditor = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
manifest?: ManifestCollectionAction;
|
||||
|
||||
@@ -64,16 +61,13 @@ export class UmbCreateDocumentCollectionActionElement extends UmbLitElement {
|
||||
});
|
||||
});
|
||||
|
||||
this.consumeContext(UMB_COLLECTION_CONTEXT, (collectionContext) => {
|
||||
this.consumeContext(UMB_DOCUMENT_COLLECTION_CONTEXT, (collectionContext) => {
|
||||
this.observe(collectionContext.view.currentView, (currentView) => {
|
||||
this._currentView = currentView?.meta.pathName;
|
||||
});
|
||||
this.observe(collectionContext.view.rootPathName, (rootPathName) => {
|
||||
this._rootPathName = rootPathName;
|
||||
});
|
||||
this.observe(collectionContext.filter, (filter) => {
|
||||
this._useInfiniteEditor = filter.useInfiniteEditor == true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -99,22 +93,14 @@ export class UmbCreateDocumentCollectionActionElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
#getCreateUrl(item: UmbAllowedDocumentTypeModel) {
|
||||
if (this._useInfiniteEditor) {
|
||||
return (
|
||||
this._createDocumentPath.replace(`${this._rootPathName}`, `${this._rootPathName}/${this._currentView}`) +
|
||||
UMB_CREATE_DOCUMENT_WORKSPACE_PATH_PATTERN.generateLocal({
|
||||
parentEntityType: this._documentUnique ? UMB_DOCUMENT_ENTITY_TYPE : UMB_DOCUMENT_ROOT_ENTITY_TYPE,
|
||||
parentUnique: this._documentUnique ?? 'null',
|
||||
documentTypeUnique: item.unique,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return UMB_CREATE_DOCUMENT_WORKSPACE_PATH_PATTERN.generateAbsolute({
|
||||
parentEntityType: this._documentUnique ? UMB_DOCUMENT_ENTITY_TYPE : UMB_DOCUMENT_ROOT_ENTITY_TYPE,
|
||||
parentUnique: this._documentUnique ?? 'null',
|
||||
documentTypeUnique: item.unique,
|
||||
});
|
||||
return (
|
||||
this._createDocumentPath.replace(`${this._rootPathName}`, `${this._rootPathName}/${this._currentView}`) +
|
||||
UMB_CREATE_DOCUMENT_WORKSPACE_PATH_PATTERN.generateLocal({
|
||||
parentEntityType: this._documentUnique ? UMB_DOCUMENT_ENTITY_TYPE : UMB_DOCUMENT_ROOT_ENTITY_TYPE,
|
||||
parentUnique: this._documentUnique ?? 'null',
|
||||
documentTypeUnique: item.unique,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { UmbDocumentCollectionContext } from './document-collection.context.js';
|
||||
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
||||
|
||||
export const UMB_DOCUMENT_COLLECTION_CONTEXT = new UmbContextToken<UmbDocumentCollectionContext>(
|
||||
'UmbCollectionContext',
|
||||
);
|
||||
@@ -2,11 +2,11 @@ import { getPropertyValueByAlias } from '../index.js';
|
||||
import { UMB_EDIT_DOCUMENT_WORKSPACE_PATH_PATTERN } from '../../../paths.js';
|
||||
import type { UmbCollectionColumnConfiguration } from '../../../../../core/collection/types.js';
|
||||
import type { UmbDocumentCollectionFilterModel, UmbDocumentCollectionItemModel } from '../../types.js';
|
||||
import { UMB_DOCUMENT_COLLECTION_CONTEXT } from '../../document-collection.context-token.js';
|
||||
import { css, customElement, html, nothing, repeat, state, when } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { fromCamelCase } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UMB_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection';
|
||||
import { UMB_WORKSPACE_MODAL, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal';
|
||||
import type { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection';
|
||||
import type { UUIInterfaceColor } from '@umbraco-cms/backoffice/external/uui';
|
||||
@@ -33,7 +33,7 @@ export class UmbDocumentGridCollectionViewElement extends UmbLitElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.consumeContext(UMB_COLLECTION_CONTEXT, (collectionContext) => {
|
||||
this.consumeContext(UMB_DOCUMENT_COLLECTION_CONTEXT, (collectionContext) => {
|
||||
this.#collectionContext = collectionContext;
|
||||
this.#observeCollectionContext();
|
||||
});
|
||||
|
||||
@@ -3,10 +3,10 @@ import { UMB_EDIT_DOCUMENT_WORKSPACE_PATH_PATTERN } from '../../../paths.js';
|
||||
import type { UmbCollectionColumnConfiguration } from '../../../../../core/collection/types.js';
|
||||
import type { UmbDocumentCollectionItemModel } from '../../types.js';
|
||||
import type { UmbDocumentCollectionContext } from '../../document-collection.context.js';
|
||||
import { UMB_DOCUMENT_COLLECTION_CONTEXT } from '../../document-collection.context-token.js';
|
||||
import { css, customElement, html, nothing, state, when } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UMB_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection';
|
||||
import { UMB_WORKSPACE_MODAL, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal';
|
||||
import type { UmbModalRouteBuilder } from '@umbraco-cms/backoffice/modal';
|
||||
import type {
|
||||
@@ -68,7 +68,7 @@ export class UmbDocumentTableCollectionViewElement extends UmbLitElement {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.consumeContext(UMB_COLLECTION_CONTEXT, (collectionContext) => {
|
||||
this.consumeContext(UMB_DOCUMENT_COLLECTION_CONTEXT, (collectionContext) => {
|
||||
this.#collectionContext = collectionContext;
|
||||
});
|
||||
|
||||
|
||||
@@ -90,9 +90,6 @@ export class UmbInputDocumentElement extends UUIFormControlMixin(UmbLitElement,
|
||||
@property({ type: Boolean })
|
||||
showOpenButton?: boolean;
|
||||
|
||||
@property({ type: Boolean })
|
||||
ignoreUserStartNodes?: boolean;
|
||||
|
||||
@property()
|
||||
public set value(idsString: string) {
|
||||
this.selection = splitStringToArray(idsString);
|
||||
@@ -153,7 +150,6 @@ export class UmbInputDocumentElement extends UUIFormControlMixin(UmbLitElement,
|
||||
};
|
||||
|
||||
#openPicker() {
|
||||
// TODO: Configure the content picker, with `startNodeId` and `ignoreUserStartNodes` [LK]
|
||||
this.#pickerContext.openPicker({
|
||||
hideTreeRoot: true,
|
||||
pickableFilter: this.#pickableFilter,
|
||||
|
||||
@@ -3,10 +3,11 @@ import { UmbDocumentDetailRepository, UmbDocumentPublishingRepository } from '..
|
||||
import type { UmbDocumentVariantOptionModel } from '../types.js';
|
||||
import { UMB_APP_LANGUAGE_CONTEXT, UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language';
|
||||
import type { UmbEntityActionArgs } from '@umbraco-cms/backoffice/entity-action';
|
||||
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
|
||||
import { UmbEntityActionBase, UmbRequestReloadStructureForEntityEvent } from '@umbraco-cms/backoffice/entity-action';
|
||||
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
|
||||
export class UmbPublishDocumentEntityAction extends UmbEntityActionBase<never> {
|
||||
constructor(host: UmbControllerHost, args: UmbEntityActionArgs<never>) {
|
||||
@@ -44,11 +45,18 @@ export class UmbPublishDocumentEntityAction extends UmbEntityActionBase<never> {
|
||||
}),
|
||||
);
|
||||
|
||||
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
|
||||
const event = new UmbRequestReloadStructureForEntityEvent({
|
||||
unique: this.args.unique,
|
||||
entityType: this.args.entityType,
|
||||
});
|
||||
|
||||
// If the document has only one variant, we can skip the modal and publish directly:
|
||||
if (options.length === 1) {
|
||||
const variantId = UmbVariantId.Create(documentData.variants[0]);
|
||||
const publishingRepository = new UmbDocumentPublishingRepository(this._host);
|
||||
await publishingRepository.publish(this.args.unique, [{ variantId }]);
|
||||
actionEventContext.dispatchEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -84,6 +92,7 @@ export class UmbPublishDocumentEntityAction extends UmbEntityActionBase<never> {
|
||||
this.args.unique,
|
||||
variantIds.map((variantId) => ({ variantId })),
|
||||
);
|
||||
actionEventContext.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,15 @@ import { UmbDocumentDetailRepository, UmbDocumentPublishingRepository } from '..
|
||||
import type { UmbDocumentVariantOptionModel } from '../types.js';
|
||||
import { UMB_DOCUMENT_UNPUBLISH_MODAL } from '../modals/index.js';
|
||||
import { UMB_APP_LANGUAGE_CONTEXT, UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language';
|
||||
import { type UmbEntityActionArgs, UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
|
||||
import {
|
||||
type UmbEntityActionArgs,
|
||||
UmbEntityActionBase,
|
||||
UmbRequestReloadStructureForEntityEvent,
|
||||
} from '@umbraco-cms/backoffice/entity-action';
|
||||
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
|
||||
export class UmbUnpublishDocumentEntityAction extends UmbEntityActionBase<never> {
|
||||
constructor(host: UmbControllerHost, args: UmbEntityActionArgs<never>) {
|
||||
@@ -73,6 +78,14 @@ export class UmbUnpublishDocumentEntityAction extends UmbEntityActionBase<never>
|
||||
if (variantIds.length) {
|
||||
const publishingRepository = new UmbDocumentPublishingRepository(this._host);
|
||||
await publishingRepository.unpublish(this.args.unique, variantIds);
|
||||
|
||||
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
|
||||
const event = new UmbRequestReloadStructureForEntityEvent({
|
||||
unique: this.args.unique,
|
||||
entityType: this.args.entityType,
|
||||
});
|
||||
|
||||
actionEventContext.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,19 @@ import { UMB_APP_LANGUAGE_CONTEXT, UmbLanguageCollectionRepository } from '@umbr
|
||||
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
|
||||
import { UMB_CONFIRM_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api';
|
||||
import { UMB_ENTITY_CONTEXT } from '@umbraco-cms/backoffice/entity';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import { UmbRequestReloadChildrenOfEntityEvent } from '@umbraco-cms/backoffice/entity-action';
|
||||
|
||||
export class UmbDocumentPublishEntityBulkAction extends UmbEntityBulkActionBase<object> {
|
||||
async execute() {
|
||||
const entityContext = await this.getContext(UMB_ENTITY_CONTEXT);
|
||||
const entityType = entityContext.getEntityType();
|
||||
const unique = entityContext.getUnique();
|
||||
|
||||
if (!entityType) throw new Error('Entity type not found');
|
||||
if (unique === undefined) throw new Error('Entity unique not found');
|
||||
|
||||
// If there is only one selection, we can refer to the regular publish entity action:
|
||||
if (this.selection.length === 1) {
|
||||
const action = new UmbPublishDocumentEntityAction(this._host, {
|
||||
@@ -43,6 +53,12 @@ export class UmbDocumentPublishEntityBulkAction extends UmbEntityBulkActionBase<
|
||||
|
||||
const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
|
||||
|
||||
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
|
||||
const event = new UmbRequestReloadChildrenOfEntityEvent({
|
||||
entityType,
|
||||
unique,
|
||||
});
|
||||
|
||||
// If there is only one language available, we can skip the modal and publish directly:
|
||||
if (options.length === 1) {
|
||||
const localizationController = new UmbLocalizationController(this._host);
|
||||
@@ -62,6 +78,7 @@ export class UmbDocumentPublishEntityBulkAction extends UmbEntityBulkActionBase<
|
||||
const variantId = new UmbVariantId(options[0].language.unique, null);
|
||||
const publishingRepository = new UmbDocumentPublishingRepository(this._host);
|
||||
await publishingRepository.unpublish(this.selection[0], [variantId]);
|
||||
eventContext.dispatchEvent(event);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -98,6 +115,7 @@ export class UmbDocumentPublishEntityBulkAction extends UmbEntityBulkActionBase<
|
||||
unique,
|
||||
variantIds.map((variantId) => ({ variantId })),
|
||||
);
|
||||
eventContext.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,19 @@ import { UmbEntityBulkActionBase } from '@umbraco-cms/backoffice/entity-bulk-act
|
||||
import { UMB_APP_LANGUAGE_CONTEXT, UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language';
|
||||
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
|
||||
import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api';
|
||||
import { UMB_ENTITY_CONTEXT } from '@umbraco-cms/backoffice/entity';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import { UmbRequestReloadChildrenOfEntityEvent } from '@umbraco-cms/backoffice/entity-action';
|
||||
|
||||
export class UmbDocumentUnpublishEntityBulkAction extends UmbEntityBulkActionBase<object> {
|
||||
async execute() {
|
||||
const entityContext = await this.getContext(UMB_ENTITY_CONTEXT);
|
||||
const entityType = entityContext.getEntityType();
|
||||
const unique = entityContext.getUnique();
|
||||
|
||||
if (!entityType) throw new Error('Entity type not found');
|
||||
if (unique === undefined) throw new Error('Entity unique not found');
|
||||
|
||||
// If there is only one selection, we can refer to the regular unpublish entity action:
|
||||
if (this.selection.length === 1) {
|
||||
const action = new UmbUnpublishDocumentEntityAction(this._host, {
|
||||
@@ -43,6 +53,12 @@ export class UmbDocumentUnpublishEntityBulkAction extends UmbEntityBulkActionBas
|
||||
|
||||
const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
|
||||
|
||||
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
|
||||
const event = new UmbRequestReloadChildrenOfEntityEvent({
|
||||
entityType,
|
||||
unique,
|
||||
});
|
||||
|
||||
// If there is only one language available, we can skip the modal and unpublish directly:
|
||||
if (options.length === 1) {
|
||||
const localizationController = new UmbLocalizationController(this._host);
|
||||
@@ -62,6 +78,7 @@ export class UmbDocumentUnpublishEntityBulkAction extends UmbEntityBulkActionBas
|
||||
const variantId = new UmbVariantId(options[0].language.unique, null);
|
||||
const publishingRepository = new UmbDocumentPublishingRepository(this._host);
|
||||
await publishingRepository.unpublish(this.selection[0], [variantId]);
|
||||
eventContext.dispatchEvent(event);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -95,6 +112,7 @@ export class UmbDocumentUnpublishEntityBulkAction extends UmbEntityBulkActionBas
|
||||
if (variantIds.length) {
|
||||
for (const unique of this.selection) {
|
||||
await repository.unpublish(unique, variantIds);
|
||||
eventContext.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,7 +274,9 @@ export class UmbRollbackModalElement extends UmbModalBaseElement<UmbRollbackModa
|
||||
|
||||
get currentVersionHeader() {
|
||||
return (
|
||||
this.localize.date(this.currentVersion?.date || '', this.#localizeDateOptions) + ' - ' + this.currentVersion?.user
|
||||
this.localize.date(this.currentVersion?.date ?? new Date(), this.#localizeDateOptions) +
|
||||
' - ' +
|
||||
this.currentVersion?.user
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ export class UmbPropertyEditorUIDocumentPickerElement extends UmbLitElement impl
|
||||
this._max = minMax.max && minMax.max > 0 ? minMax.max : Infinity;
|
||||
}
|
||||
|
||||
this._ignoreUserStartNodes = config.getValueByAlias('ignoreUserStartNodes') ?? false;
|
||||
this._startNodeId = config.getValueByAlias('startNodeId');
|
||||
this._showOpenButton = config.getValueByAlias('showOpenButton') ?? false;
|
||||
}
|
||||
@@ -39,9 +38,6 @@ export class UmbPropertyEditorUIDocumentPickerElement extends UmbLitElement impl
|
||||
@state()
|
||||
private _showOpenButton?: boolean;
|
||||
|
||||
@state()
|
||||
private _ignoreUserStartNodes?: boolean;
|
||||
|
||||
#onChange(event: CustomEvent & { target: UmbInputDocumentElement }) {
|
||||
this.value = event.target.selection.join(',');
|
||||
this.dispatchEvent(new UmbPropertyValueChangeEvent());
|
||||
@@ -58,7 +54,6 @@ export class UmbPropertyEditorUIDocumentPickerElement extends UmbLitElement impl
|
||||
.max=${this._max}
|
||||
.startNode=${startNode}
|
||||
.value=${this.value ?? ''}
|
||||
?ignoreUserStartNodes=${this._ignoreUserStartNodes}
|
||||
?showOpenButton=${this._showOpenButton}
|
||||
@change=${this.#onChange}>
|
||||
</umb-input-document>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user