From d9cdf03442fb41b5f71683ae140ba88ce37d69bc Mon Sep 17 00:00:00 2001 From: Lee Kelleher Date: Fri, 24 Oct 2025 09:29:22 +0100 Subject: [PATCH] Preview: Allows changing the preview environment inside the preview app, and other UX changes that enhance the experience (#20598) * Preview Device: refactored config Fixed "flip" icon style. Removed "shadow" as unnecessary. Renamed "className" to "wrapperClass" to be descriptive. * Preview element CSS refinement * Preview element: load in private extensions * Added "Preview Environments" preview-app Made `unique`, `culture` and `segment` observable in the context. * Aligned preview-app design with `hidden` attribute and design consistency. * Created "Preview" package * Relocated "Preview Apps" and Context to the new package * Deprecated `UmbDocumentPreviewRepository` (for v19) as the methods have moved to `UmbPreviewRepository`. * Removed Preview Sessions event listeners * Changed localization from "End" to "Exit" * chore: consumes context only when needed * feat: uses the UmbPreviewRepository instead * feat: adds localization to errors and ensures the function does not randomly throw * feat: prevents creating a new repository for every click * feat: prevents potential memory leak by adding a signal to the events added to each iframe update * feat: adds a custom interface to prevent typescript errors * feat: ensures new string states are checked properly * docs: adds comment to avoid confusion * feat: sets up scaling once per iframe load rather than on each update * fix: ensures that you can go back to the default segment again * feat: closes popovers when clicking on the iframe (losing blur) and if selecting an item (expect for devices) --------- Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> --- src/Umbraco.Web.UI.Client/package-lock.json | 7 + src/Umbraco.Web.UI.Client/package.json | 1 + .../src/apps/preview/index.ts | 1 + .../src/apps/preview/preview.element.ts | 76 +++++--- .../src/assets/lang/en.ts | 4 +- .../preview/document-preview.repository.ts | 13 ++ .../workspace/document-workspace.context.ts | 4 +- .../src/packages/preview/context/index.ts | 2 + .../preview/context/preview.context-token.ts | 4 + .../preview/context}/preview.context.ts | 150 ++++++++++------ .../src/packages/preview/index.ts | 2 + .../src/packages/preview/manifests.ts | 4 + .../src/packages/preview/package.json | 8 + .../preview/preview-apps}/manifests.ts | 15 +- .../preview-apps}/preview-culture.element.ts | 59 +++++- .../preview-apps}/preview-device.element.ts | 91 +++++++--- .../preview-environments.element.ts | 170 ++++++++++++++++++ .../preview-apps}/preview-exit.element.ts | 4 +- .../preview-open-website.element.ts | 4 +- .../preview-apps}/preview-segment.element.ts | 66 +++++-- .../packages/preview/preview-apps/types.ts | 6 + .../src/packages/preview/repository/index.ts | 1 + .../preview/repository/preview.repository.ts | 74 ++++++++ .../src/packages/preview/umbraco-package.ts | 9 + .../src/packages/preview/vite.config.ts | 12 ++ src/Umbraco.Web.UI.Client/tsconfig.json | 1 + 26 files changed, 649 insertions(+), 139 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/apps/preview/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/preview/context/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/preview/context/preview.context-token.ts rename src/Umbraco.Web.UI.Client/src/{apps/preview => packages/preview/context}/preview.context.ts (65%) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/preview/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/preview/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/preview/package.json rename src/Umbraco.Web.UI.Client/src/{apps/preview/apps => packages/preview/preview-apps}/manifests.ts (76%) rename src/Umbraco.Web.UI.Client/src/{apps/preview/apps => packages/preview/preview-apps}/preview-culture.element.ts (66%) rename src/Umbraco.Web.UI.Client/src/{apps/preview/apps => packages/preview/preview-apps}/preview-device.element.ts (57%) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/preview/preview-apps/preview-environments.element.ts rename src/Umbraco.Web.UI.Client/src/{apps/preview/apps => packages/preview/preview-apps}/preview-exit.element.ts (91%) rename src/Umbraco.Web.UI.Client/src/{apps/preview/apps => packages/preview/preview-apps}/preview-open-website.element.ts (91%) rename src/Umbraco.Web.UI.Client/src/{apps/preview/apps => packages/preview/preview-apps}/preview-segment.element.ts (62%) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/preview/preview-apps/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/preview/repository/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/preview/repository/preview.repository.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/preview/umbraco-package.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/preview/vite.config.ts diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 9c08dfb59f..74589de99c 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -3638,6 +3638,10 @@ "resolved": "src/packages/performance-profiling", "link": true }, + "node_modules/@umbraco-backoffice/preview": { + "resolved": "src/packages/preview", + "link": true + }, "node_modules/@umbraco-backoffice/property-editors": { "resolved": "src/packages/property-editors", "link": true @@ -17084,6 +17088,9 @@ "src/packages/performance-profiling": { "name": "@umbraco-backoffice/performance-profiling" }, + "src/packages/preview": { + "name": "@umbraco-backoffice/preview" + }, "src/packages/property-editors": { "name": "@umbraco-backoffice/property-editors" }, diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index b22f6f4e74..f91e42d9ba 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -85,6 +85,7 @@ "./picker-input": "./dist-cms/packages/core/picker-input/index.js", "./picker-data-source": "./dist-cms/packages/core/picker-data-source/index.js", "./picker": "./dist-cms/packages/core/picker/index.js", + "./preview": "./dist-cms/packages/preview/index.js", "./property-action": "./dist-cms/packages/core/property-action/index.js", "./property-editor-data-source": "./dist-cms/packages/core/property-editor-data-source/index.js", "./property-editor": "./dist-cms/packages/core/property-editor/index.js", diff --git a/src/Umbraco.Web.UI.Client/src/apps/preview/index.ts b/src/Umbraco.Web.UI.Client/src/apps/preview/index.ts new file mode 100644 index 0000000000..d90c7995f9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/apps/preview/index.ts @@ -0,0 +1 @@ +export * from './preview.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/apps/preview/preview.element.ts b/src/Umbraco.Web.UI.Client/src/apps/preview/preview.element.ts index b2d0db0da7..e9bce3c441 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/preview/preview.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/preview/preview.element.ts @@ -1,8 +1,14 @@ -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 { + umbExtensionsRegistry, + UmbBackofficeEntryPointExtensionInitializer, +} from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbPreviewContext } from '@umbraco-cms/backoffice/preview'; +import { UmbServerExtensionRegistrator } from '@umbraco-cms/backoffice/extension-api'; +import { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth'; + +const CORE_PACKAGES = [import('../../packages/preview/umbraco-package.js')]; /** * @element umb-preview @@ -11,23 +17,40 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; 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)); - } - @state() private _iframeReady?: boolean; @state() private _previewUrl?: string; + constructor() { + super(); + + new UmbBackofficeEntryPointExtensionInitializer(this, umbExtensionsRegistry); + + this.observe(this.#context.iframeReady, (iframeReady) => (this._iframeReady = iframeReady)); + this.observe(this.#context.previewUrl, (previewUrl) => (this._previewUrl = previewUrl)); + } + + override async firstUpdated() { + await this.#extensionsAfterAuth(); + + // Extensions are loaded in parallel and don't need to block the preview frame + CORE_PACKAGES.forEach(async (packageImport) => { + const { extensions } = await packageImport; + umbExtensionsRegistry.registerMany(extensions); + }); + } + + async #extensionsAfterAuth() { + const authContext = await this.getContext(UMB_AUTH_CONTEXT, { preventTimeout: true }); + if (!authContext) { + throw new Error('UmbPreviewElement requires the UMB_AUTH_CONTEXT to be set.'); + } + await this.observe(authContext.isAuthorized).asPromise(); + await new UmbServerExtensionRegistrator(this, umbExtensionsRegistry).registerPrivateExtensions(); + } + #onIFrameLoad(event: Event & { target: HTMLIFrameElement }) { this.#context.iframeLoaded(event.target); } @@ -36,7 +59,7 @@ export class UmbPreviewElement extends UmbLitElement { if (!this._previewUrl) return nothing; return html` ${when(!this._iframeReady, () => html`
`)} -
+