From 79e83fff9e78ff5537db463501b4b00355f25820 Mon Sep 17 00:00:00 2001 From: Jan Potoms <2109932+Janpot@users.noreply.github.com> Date: Tue, 9 Apr 2024 15:25:37 +0200 Subject: [PATCH] [core] Make inline canvas the default (#3370) --- packages/toolpad-studio/cli/index.ts | 2 +- packages/toolpad-studio/package.json | 1 - .../src/canvas/ToolpadBridge.tsx | 15 - packages/toolpad-studio/src/constants.ts | 3 - .../src/runtime/PreviewHeader.tsx | 187 +---------- .../toolpad-studio/src/runtime/ToolpadApp.tsx | 6 +- .../toolpad-studio/src/runtime/constants.ts | 1 - .../src/server/appServerWorker.ts | 6 +- packages/toolpad-studio/src/server/index.ts | 118 +------ .../src/server/toolpadAppBuilder.ts | 22 +- .../AppEditor/PageEditor/EditorCanvasHost.tsx | 292 +++++++++++------- .../PageEditor/EditorCanvasHostInline.tsx | 285 ----------------- .../PageEditor/RenderPanel/RenderPanel.tsx | 7 +- .../toolpad-studio/src/toolpad/Toolpad.tsx | 13 - .../toolpad-studio/src/toolpad/index.html | 20 -- packages/toolpad-studio/src/toolpad/main.tsx | 23 -- .../src/toolpad/vite.config.mts | 34 -- packages/toolpad-studio/src/utils/domView.ts | 2 +- test/integration/codeComponents/index.spec.ts | 10 +- test/integration/custom-server/dev.spec.ts | 56 +--- test/integration/duplication/index.spec.ts | 6 +- test/integration/editor/deleteLast.spec.ts | 6 +- 22 files changed, 226 insertions(+), 889 deletions(-) delete mode 100644 packages/toolpad-studio/src/toolpad/AppEditor/PageEditor/EditorCanvasHostInline.tsx delete mode 100644 packages/toolpad-studio/src/toolpad/index.html delete mode 100644 packages/toolpad-studio/src/toolpad/main.tsx delete mode 100644 packages/toolpad-studio/src/toolpad/vite.config.mts diff --git a/packages/toolpad-studio/cli/index.ts b/packages/toolpad-studio/cli/index.ts index 7d44ec97917..41a2e4417ab 100644 --- a/packages/toolpad-studio/cli/index.ts +++ b/packages/toolpad-studio/cli/index.ts @@ -48,7 +48,7 @@ interface EditorOptions { } async function editorCommand({ dev: toolpadDevMode, ...args }: EditorOptions) { - await runEditor(args.url, { toolpadDevMode, ...args }); + await runEditor(args.url); } interface BuildOptions { diff --git a/packages/toolpad-studio/package.json b/packages/toolpad-studio/package.json index 046e07eee0b..a9d3ca9d011 100644 --- a/packages/toolpad-studio/package.json +++ b/packages/toolpad-studio/package.json @@ -12,7 +12,6 @@ "lint": "prettier --check .", "fix": "prettier --write .", "build:cli": "tsup", - "build:editor": "vite build ./src/toolpad", "build:typings": "tsx ./scripts/typings.mts", "dev:cli": "tsup --watch", "dev:typings": "pnpm build:typings", diff --git a/packages/toolpad-studio/src/canvas/ToolpadBridge.tsx b/packages/toolpad-studio/src/canvas/ToolpadBridge.tsx index 67dbfe28350..3bde249181a 100644 --- a/packages/toolpad-studio/src/canvas/ToolpadBridge.tsx +++ b/packages/toolpad-studio/src/canvas/ToolpadBridge.tsx @@ -1,14 +1,7 @@ import { Emitter } from '@toolpad/utils/events'; import type { RuntimeEvents } from '@toolpad/studio-runtime'; -import { TOOLPAD_BRIDGE_GLOBAL } from '../constants'; import type { AppCanvasState, PageViewState } from '../types'; -declare global { - interface Window { - [TOOLPAD_BRIDGE_GLOBAL]?: ToolpadBridge | ((bridge: ToolpadBridge) => void); - } -} - const COMMAND_HANDLERS = Symbol('hidden property to hold the command handlers'); type Commands> = T & { @@ -98,12 +91,4 @@ bridge?.canvasEvents.on('ready', () => { canvasIsReady = true; }); -if (bridge) { - if (typeof window[TOOLPAD_BRIDGE_GLOBAL] === 'function') { - window[TOOLPAD_BRIDGE_GLOBAL](bridge); - } - - window[TOOLPAD_BRIDGE_GLOBAL] = bridge; -} - export { bridge }; diff --git a/packages/toolpad-studio/src/constants.ts b/packages/toolpad-studio/src/constants.ts index 24d4461d467..319baddce42 100644 --- a/packages/toolpad-studio/src/constants.ts +++ b/packages/toolpad-studio/src/constants.ts @@ -1,6 +1,5 @@ export const WINDOW_PROP_TOOLPAD_APP_RENDER_PARAMS = '__TOOLPAD_APP_RENDER_PARAMS__'; export const RUNTIME_CONFIG_WINDOW_PROPERTY = '__TOOLPAD_RUNTIME_CONFIG__'; -export const APP_URL_WINDOW_PROPERTY = '__TOOLPAD_APP_URL__'; export const INITIAL_STATE_WINDOW_PROPERTY = '__initialToolpadState__'; export const TOOLPAD_TARGET_CE = 'CE'; @@ -15,8 +14,6 @@ export const ROADMAP_URL = 'https://github.com/orgs/mui/projects/9'; export const SCHEDULE_DEMO_URL = 'https://calendly.com/prakhar-mui/toolpad'; export const UPGRADE_URL = 'https://mui.com/toolpad/studio/getting-started/roadmap/#paid-plan'; -export const TOOLPAD_BRIDGE_GLOBAL = '__TOOLPAD_BRIDGE__'; - export const VERSION_CHECK_INTERVAL = 1000 * 60 * 10; // TODO: Remove once global functions UI is ready diff --git a/packages/toolpad-studio/src/runtime/PreviewHeader.tsx b/packages/toolpad-studio/src/runtime/PreviewHeader.tsx index 5e938cd2bf2..acbd0244491 100644 --- a/packages/toolpad-studio/src/runtime/PreviewHeader.tsx +++ b/packages/toolpad-studio/src/runtime/PreviewHeader.tsx @@ -1,111 +1,11 @@ import * as React from 'react'; -import { - Button, - Typography, - Box, - useTheme, - Alert, - ButtonProps, - Popover, - styled, - IconButton, - Tooltip, - Snackbar, - IconButtonProps, - popoverClasses, -} from '@mui/material'; +import { Button, Typography, Box, useTheme, Alert, ButtonProps } from '@mui/material'; import EditIcon from '@mui/icons-material/Edit'; import { Link, useMatch } from 'react-router-dom'; -import ContentCopyIcon from '@mui/icons-material/ContentCopy'; import { useNonNullableContext } from '@toolpad/utils/react'; import { PREVIEW_HEADER_HEIGHT } from './constants'; import { AppHostContext } from './AppHostContext'; -interface CopyToClipboardButtonProps extends IconButtonProps { - content: string; -} - -function CopyToClipboardButton({ content, onClick, ...props }: CopyToClipboardButtonProps) { - const [confirmSnackbarOpen, setConfirmSnackbarOpen] = React.useState(false); - - const handleClick = React.useCallback( - (event: React.MouseEvent) => { - window.navigator.clipboard.writeText(content); - setConfirmSnackbarOpen(true); - onClick?.(event); - }, - [content, onClick], - ); - - const handleCopySnackbarClose = React.useCallback(() => setConfirmSnackbarOpen(false), []); - - return ( - - - - - - - - - - ); -} - -interface CodeViewProps { - children?: string; -} - -const classes = { - copyToClipboardButton: 'Toolpad_CodeView_CopyToClipboardButton', - hasCopyToClipboardButton: 'Toolpad_CodeView_hasCopyToClipboardButton', -}; - -const CodeViewRoot = styled('pre')(({ theme }) => ({ - position: 'relative', - border: `1px solid ${theme.palette.divider}`, - padding: theme.spacing(2), - paddingRight: theme.spacing(5), - borderRadius: theme.shape.borderRadius, - - fontFamily: theme.fontFamilyMonospaced, - - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - - [`& .${classes.copyToClipboardButton}`]: { - position: 'absolute', - top: 0, - right: 0, - marginTop: theme.spacing(1), - marginRight: theme.spacing(1), - }, -})); - -function CodeView({ children }: CodeViewProps) { - return ( - - - {children} - - - {children ? ( - - ) : null} - - ); -} - function OpenInEditorButton({ children = 'Open in editor', ...props @@ -117,67 +17,7 @@ function OpenInEditorButton({ ); } -interface CustomServerInstructionsProps { - basename: string; -} - -function CustomServerInstructions({ basename }: CustomServerInstructionsProps) { - const id = React.useId(); - const [anchorEl, setAnchorEl] = React.useState(null); - - const handleClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - const open = Boolean(anchorEl); - - const appUrl = React.useMemo(() => { - return new URL(basename, window.location.origin).href; - }, [basename]); - - return ( - - - - - - This application is running under a custom server. Run the standalone Toolpad Studio - editor to make changes to this application. - - {`npx @toolpad/studio editor ${appUrl}`} - - - - ); -} - -export interface PreviewHeaderProps { - basename: string; -} - -export default function PreviewHeader({ basename }: PreviewHeaderProps) { +export default function PreviewHeader() { const pageMatch = useMatch('/pages/:slug'); const activePage = pageMatch?.params.slug; @@ -187,23 +27,12 @@ export default function PreviewHeader({ basename }: PreviewHeaderProps) { let action: React.ReactNode = null; - if (process.env.EXPERIMENTAL_INLINE_CANVAS) { - action = ( - - ); - } else if (appContext) { - action = appContext.isCustomServer ? ( - - ) : ( - - ); - } + action = ( + + ); return appContext ? ( - {showPreviewHeader ? : null} + {showPreviewHeader ? : null} }>{children} - + diff --git a/packages/toolpad-studio/src/runtime/constants.ts b/packages/toolpad-studio/src/runtime/constants.ts index d11b874574c..699bf4e1cea 100644 --- a/packages/toolpad-studio/src/runtime/constants.ts +++ b/packages/toolpad-studio/src/runtime/constants.ts @@ -1,2 +1 @@ -export const HTML_ID_EDITOR_OVERLAY = 'editor-overlay'; export const PREVIEW_HEADER_HEIGHT = 52; diff --git a/packages/toolpad-studio/src/server/appServerWorker.ts b/packages/toolpad-studio/src/server/appServerWorker.ts index 71b03022577..9e65f9e753b 100644 --- a/packages/toolpad-studio/src/server/appServerWorker.ts +++ b/packages/toolpad-studio/src/server/appServerWorker.ts @@ -3,7 +3,7 @@ import invariant from 'invariant'; import type { Plugin } from 'vite'; import { createRpcClient } from '@toolpad/utils/workerRpc'; import type * as appDom from '@toolpad/studio-runtime/appDom'; -import { createViteConfig, getAppHtmlContent, getEditorHtmlContent } from './toolpadAppBuilder'; +import { createViteConfig, getEditorHtmlContent } from './toolpadAppBuilder'; import type { RuntimeConfig } from '../types'; import type { ComponentEntry, PagesManifest } from './localMode'; import createRuntimeState from '../runtime/createRuntimeState'; @@ -46,9 +46,7 @@ function devServerPlugin({ config }: ToolpadAppDevServerParams): Plugin { try { const dom = await loadDom(); - const template = process.env.EXPERIMENTAL_INLINE_CANVAS - ? getEditorHtmlContent() - : getAppHtmlContent(); + const template = getEditorHtmlContent(); let html = await viteServer.transformIndexHtml(req.url, template); diff --git a/packages/toolpad-studio/src/server/index.ts b/packages/toolpad-studio/src/server/index.ts index 6e765e76fdd..00d3272f9f7 100644 --- a/packages/toolpad-studio/src/server/index.ts +++ b/packages/toolpad-studio/src/server/index.ts @@ -1,13 +1,11 @@ import * as path from 'path'; import { IncomingMessage, createServer } from 'http'; -import * as fs from 'fs/promises'; import { Worker, MessageChannel } from 'worker_threads'; import express from 'express'; import getPort from 'get-port'; import { createProxyMiddleware } from 'http-proxy-middleware'; import { mapValues } from '@toolpad/utils/collections'; import prettyBytes from 'pretty-bytes'; -import type { ViteDevServer } from 'vite'; import { WebSocket, WebSocketServer } from 'ws'; import { listen } from '@toolpad/utils/http'; import openBrowser from 'react-dev-utils/openBrowser.js'; @@ -24,7 +22,6 @@ import type { WorkerRpc, } from './appServerWorker'; import { createRpcHandler } from './rpc'; -import { APP_URL_WINDOW_PROPERTY } from '../constants'; import { createRpcServer as createProjectRpcServer } from './projectRpcServer'; import { createRpcServer as createRuntimeRpcServer } from './runtimeRpcServer'; import { createAuthHandler, createRequireAuthMiddleware, getRequireAuthentication } from './auth'; @@ -224,67 +221,6 @@ export async function createHandler({ }; } -interface EditorHandlerParams { - toolpadDevMode?: boolean; -} - -async function createEditorHandler( - appUrl: string, - { toolpadDevMode = false }: EditorHandlerParams, -): Promise { - const router = express.Router(); - let viteApp: ViteDevServer | undefined; - - const transformIndexHtml = (html: string) => { - return html.replace( - '', - - ` - `, - ); - }; - - if (toolpadDevMode) { - // eslint-disable-next-line no-console - console.log(`${chalk.blue('info')} - Running Toolpad Studio editor in dev mode`); - - const vite = await import('vite'); - viteApp = await vite.createServer({ - configFile: path.resolve(currentDirectory, '../../src/toolpad/vite.config.mts'), - root: path.resolve(currentDirectory, '../../src/toolpad'), - server: { middlewareMode: true }, - plugins: [ - { - name: 'toolpad:transform-index-html', - transformIndexHtml, - }, - ], - }); - - router.use('/', viteApp.middlewares); - } else { - router.use( - '/', - express.static(path.resolve(currentDirectory, '../../dist/editor'), { index: false }), - asyncHandler(async (req, res) => { - const htmlFilePath = path.resolve(currentDirectory, '../../dist/editor/index.html'); - let html = await fs.readFile(htmlFilePath, { encoding: 'utf-8' }); - html = transformIndexHtml(html); - res.setHeader('Content-Type', 'text/html').status(200).end(html); - }), - ); - } - - return { - handler: router, - async dispose() { - await viteApp?.close(); - }, - }; -} - async function createToolpadHandler({ dev, externalUrl, @@ -324,14 +260,9 @@ async function createToolpadHandler({ let editorHandler: AppHandler | undefined; if (dev) { - if (process.env.EXPERIMENTAL_INLINE_CANVAS) { - router.use('/_toolpad', (req, res) => { - res.redirect(`${project.options.base}/editor${req.url}`); - }); - } else { - editorHandler = await createEditorHandler(project.options.base, { toolpadDevMode }); - router.use(editorBasename, editorHandler.handler); - } + router.use('/_toolpad', (req, res) => { + res.redirect(`${project.options.base}/editor${req.url}`); + }); } return { @@ -393,12 +324,7 @@ async function fetchAppUrl(appUrl: string): Promise { return new URL(appBase, appUrl).toString(); } -export interface RunEditorOptions { - port?: number; - toolpadDevMode?: boolean; -} - -export async function runEditor(appUrl: string, options: RunEditorOptions = {}) { +export async function runEditor(appUrl: string) { let appRootUrl; try { appRootUrl = await fetchAppUrl(appUrl); @@ -413,43 +339,9 @@ export async function runEditor(appUrl: string, options: RunEditorOptions = {}) process.exit(1); } - if (process.env.EXPERIMENTAL_INLINE_CANVAS) { - // eslint-disable-next-line no-console - console.log( - `${chalk.yellow('warn')} - The editor command is deprecated and will be removed in the future, please visit ${chalk.cyan(`${appRootUrl}/editor`)}`, - ); - return; - } - - // eslint-disable-next-line no-console - console.log(`${chalk.blue('info')} - starting Toolpad Studio editor...`); - - const app = express(); - - const editorBasename = '/_toolpad'; - const { pathname, origin } = new URL(appRootUrl); - - const editorHandler = await createEditorHandler(pathname, options); - - app.use( - pathname, - createProxyMiddleware({ - logLevel: 'silent', - ws: true, - target: origin, - }), - ); - - app.use(editorBasename, editorHandler.handler); - - const port = options.port || (await getPort({ port: getPreferredPorts(DEFAULT_PORT) })); - const server = await listen(app, port); - // eslint-disable-next-line no-console console.log( - `${chalk.green('ready')} - Toolpad Studio editor ready on ${chalk.cyan( - `http://localhost:${server.port}${editorBasename}`, - )}`, + `${chalk.yellow('warn')} - The editor command is deprecated and will be removed in the future, please visit ${chalk.cyan(`${appRootUrl}/editor`)}`, ); } diff --git a/packages/toolpad-studio/src/server/toolpadAppBuilder.ts b/packages/toolpad-studio/src/server/toolpadAppBuilder.ts index a492b66bc41..b58a158fd4f 100644 --- a/packages/toolpad-studio/src/server/toolpadAppBuilder.ts +++ b/packages/toolpad-studio/src/server/toolpadAppBuilder.ts @@ -44,7 +44,7 @@ function getHtmlContent(entry: string) { `; } -export function getAppHtmlContent() { +function getAppHtmlContent() { return getHtmlContent(MAIN_ENTRY); } @@ -259,7 +259,6 @@ if (import.meta.hot) { const virtualFiles = new Map([ ['main.tsx', getEntryPoint('prod')], - ['dev.tsx', getEntryPoint('dev')], ['editor.tsx', getEntryPoint('editor')], ['components.tsx', await createComponentsFile()], ['page-components.tsx', await createPageComponentsFile()], @@ -284,9 +283,7 @@ if (import.meta.hot) { rollupOptions: { input: { index: path.resolve(currentDirectory, './index.html'), - ...(process.env.EXPERIMENTAL_INLINE_CANVAS && dev - ? { editor: path.resolve(currentDirectory, './editor.html') } - : {}), + ...(dev ? { editor: path.resolve(currentDirectory, './editor.html') } : {}), }, onwarn(warning, warn) { if (warning.code === 'MODULE_LEVEL_DIRECTIVE') { @@ -311,12 +308,7 @@ if (import.meta.hot) { }, { find: MAIN_ENTRY, - // eslint-disable-next-line no-nested-ternary - replacement: process.env.EXPERIMENTAL_INLINE_CANVAS - ? 'virtual:toolpad-files:main.tsx' - : dev - ? 'virtual:toolpad-files:dev.tsx' - : 'virtual:toolpad-files:main.tsx', + replacement: 'virtual:toolpad-files:main.tsx', }, { find: '@toolpad/studio', @@ -326,7 +318,7 @@ if (import.meta.hot) { : // load compiled path.resolve(currentDirectory, '../exports'), }, - ...(process.env.EXPERIMENTAL_INLINE_CANVAS && dev + ...(dev ? [ { find: EDITOR_ENTRY, @@ -347,9 +339,8 @@ if (import.meta.hot) { }, }, optimizeDeps: { - force: !process.env.EXPERIMENTAL_INLINE_CANVAS && toolpadDevMode ? true : undefined, include: [ - ...(process.env.EXPERIMENTAL_INLINE_CANVAS && dev + ...(dev ? [ 'perf-cascade', 'monaco-editor', @@ -371,9 +362,6 @@ if (import.meta.hot) { 'process.env.TOOLPAD_CUSTOM_SERVER': `'${JSON.stringify(customServer)}'`, 'process.env.TOOLPAD_VERSION': JSON.stringify(pkgJson.version), 'process.env.TOOLPAD_BUILD': JSON.stringify(TOOLPAD_BUILD), - 'process.env.EXPERIMENTAL_INLINE_CANVAS': JSON.stringify( - process.env.EXPERIMENTAL_INLINE_CANVAS, - ), }, } satisfies InlineConfig, }; diff --git a/packages/toolpad-studio/src/toolpad/AppEditor/PageEditor/EditorCanvasHost.tsx b/packages/toolpad-studio/src/toolpad/AppEditor/PageEditor/EditorCanvasHost.tsx index e4447e18f66..e0f35a134ed 100644 --- a/packages/toolpad-studio/src/toolpad/AppEditor/PageEditor/EditorCanvasHost.tsx +++ b/packages/toolpad-studio/src/toolpad/AppEditor/PageEditor/EditorCanvasHost.tsx @@ -1,22 +1,30 @@ import * as React from 'react'; -import { Fade, styled } from '@mui/material'; -import { NodeHashes } from '@toolpad/studio-runtime'; +import { styled, useEventCallback } from '@mui/material'; +import { NodeHashes, RuntimeEvents } from '@toolpad/studio-runtime'; import createCache from '@emotion/cache'; import { CacheProvider } from '@emotion/react'; import * as ReactDOM from 'react-dom'; +import { Emitter } from '@toolpad/utils/events'; +import { update } from '@toolpad/utils/immutability'; +import { throttle } from 'lodash-es'; import invariant from 'invariant'; -import useEventCallback from '@mui/utils/useEventCallback'; -import { TOOLPAD_BRIDGE_GLOBAL } from '../../../constants'; -import { HTML_ID_EDITOR_OVERLAY } from '../../../runtime/constants'; -import { useAppStateApi } from '../../AppState'; -import type { ToolpadBridge } from '../../../canvas/ToolpadBridge'; -import CenteredSpinner from '../../../components/CenteredSpinner'; +import * as appDom from '@toolpad/studio-runtime/appDom'; +import { CanvasEventsContext } from '@toolpad/studio-runtime/runtime'; +import { createCommands, type ToolpadBridge } from '../../../canvas/ToolpadBridge'; import { useProject } from '../../../project'; import { RuntimeState } from '../../../runtime'; +import { AppHost, AppHostContext } from '../../../runtime/AppHostContext'; +import { RenderedPage, ToolpadAppProvider } from '../../../runtime/ToolpadApp'; +import { CanvasHooks, CanvasHooksContext } from '../../../runtime/CanvasHooksContext'; +import { rectContainsPoint } from '../../../utils/geometry'; +import { queryClient } from '../../../runtime/api'; +import { PageViewState } from '../../../types'; +import { updateNodeInfo } from '../../../canvas'; +import { useAppStateApi } from '../../AppState'; interface OverlayProps { children?: React.ReactNode; - container: HTMLElement; + container?: HTMLElement; } function Overlay(props: OverlayProps) { @@ -32,6 +40,9 @@ function Overlay(props: OverlayProps) { [container], ); + // See https://github.com/emotion-js/emotion/issues/1105#issuecomment-1058225197 + cache.compat = true; + return {children}; } @@ -50,13 +61,6 @@ const CanvasRoot = styled('div')({ position: 'relative', }); -const CanvasOverlay = styled('div')(({ theme }) => ({ - backgroundColor: theme.palette.background.default, - position: 'absolute', - width: '100%', - height: '100%', -})); - const CanvasFrame = styled('iframe')({ border: 'none', position: 'absolute', @@ -64,20 +68,15 @@ const CanvasFrame = styled('iframe')({ height: '100%', }); -function useOnChange(value: T, handler: (newValue: T, oldValue: T) => void) { - const stableHandler = useEventCallback(handler); - const prevValue = React.useRef(value); - React.useEffect(() => { - if (prevValue.current !== value) { - stableHandler(value, prevValue.current); - prevValue.current = value; - } - }, [value, stableHandler]); -} +const appHost: AppHost = { + isPreview: true, + isCustomServer: false, + isCanvas: true, +}; export default function EditorCanvasHost({ - className, pageName, + className, runtimeState, base, savedNodes, @@ -85,129 +84,202 @@ export default function EditorCanvasHost({ onInit, }: EditorCanvasHostProps) { const project = useProject(); - const appStateApi = useAppStateApi(); - - const [bridge, setBridge] = React.useState(null); - - const updateOnBridge = React.useCallback(() => { - if (bridge) { - bridge.canvasCommands.update({ ...runtimeState, savedNodes }); - } - }, [bridge, runtimeState, savedNodes]); - React.useEffect(() => { - updateOnBridge(); - }, [updateOnBridge]); + const [canvasEvents, setCanvasEvents] = React.useState | null>(null); const [editorOverlayRoot, setEditorOverlayRoot] = React.useState(null); - const handleKeyDown = useEventCallback((event: KeyboardEvent) => { - const isZ = !!event.key && event.key.toLowerCase() === 'z'; + const [portal, setPortal] = React.useState(null); - const undoShortcut = isZ && (event.metaKey || event.ctrlKey); - const redoShortcut = undoShortcut && event.shiftKey; + const appStateApi = useAppStateApi(); - if (redoShortcut) { - event.preventDefault(); - appStateApi.redo(); - } else if (undoShortcut) { - event.preventDefault(); - appStateApi.undo(); - } - }); + const handleIframeLoad = useEventCallback>((event) => { + invariant(event.currentTarget.contentDocument, 'iframe contentDocument is not available'); + const root = event.currentTarget.contentDocument.getElementById('root'); + invariant(root, 'root element not found'); - const src = `${base}/pages/${pageName}`; + const iframeWindow = event.currentTarget.contentWindow; + invariant(iframeWindow, 'Iframe not attached'); - const [loading, setLoading] = React.useState(true); - useOnChange(src, () => setLoading(true)); + const handleKeyDown = (keyDownEvent: KeyboardEvent) => { + const isZ = !!keyDownEvent.key && keyDownEvent.key.toLowerCase() === 'z'; - const initBridge = useEventCallback((bridgeInstance: ToolpadBridge) => { - const handleReady = (readyBridge: ToolpadBridge) => { - setLoading(false); - setBridge(readyBridge); - onInit?.(readyBridge); + const undoShortcut = isZ && (keyDownEvent.metaKey || keyDownEvent.ctrlKey); + const redoShortcut = undoShortcut && keyDownEvent.shiftKey; + + if (redoShortcut) { + keyDownEvent.preventDefault(); + appStateApi.redo(); + } else if (undoShortcut) { + keyDownEvent.preventDefault(); + appStateApi.undo(); + } }; - if (bridgeInstance.canvasCommands.isReady()) { - handleReady(bridgeInstance); - } else { - const readyHandler = () => { - handleReady(bridgeInstance); - bridgeInstance.canvasEvents.off('ready', readyHandler); - }; - bridgeInstance.canvasEvents.on('ready', readyHandler); - } + iframeWindow.addEventListener('keydown', handleKeyDown); + iframeWindow.addEventListener('unload', () => { + iframeWindow.removeEventListener('keydown', handleKeyDown); + }); + + setPortal(root); }); - const handleFrameLoad = React.useCallback>( - (event) => { - const iframeWindow = event.currentTarget.contentWindow; - invariant(iframeWindow, 'Iframe not attached'); + const viewState = React.useRef({ nodes: {} }); + + const canvasHooks: CanvasHooks = React.useMemo( + () => ({ + overlayRef: setEditorOverlayRoot, + savedNodes, + registerNode: (node, props, componentConfig) => { + viewState.current.nodes[node.id] = { + nodeId: node.id, + props, + componentConfig, + }; + + return () => { + delete viewState.current.nodes[node.id]; + }; + }, + }), + [savedNodes], + ); - const bridgeInstance = iframeWindow[TOOLPAD_BRIDGE_GLOBAL]; + const appRootCleanupRef = React.useRef<() => void>(); + const projectEventSubscriptionRef = React.useRef<() => void>(); + const onAppRoot = React.useCallback( + (appRoot: HTMLDivElement) => { + appRootCleanupRef.current?.(); + appRootCleanupRef.current = undefined; - invariant( - typeof bridgeInstance !== 'function', - 'Only the host should set the bridge to a handler', - ); - if (bridgeInstance) { - initBridge(bridgeInstance); - } else { - iframeWindow[TOOLPAD_BRIDGE_GLOBAL] = initBridge; + if (!appRoot) { + return; } - iframeWindow.addEventListener('keydown', handleKeyDown); - iframeWindow.addEventListener('unload', () => { - iframeWindow.removeEventListener('keydown', handleKeyDown); - }); - - setEditorOverlayRoot(iframeWindow.document.getElementById(HTML_ID_EDITOR_OVERLAY)); + const bridge: ToolpadBridge = { + editorEvents: new Emitter(), + editorCommands: createCommands(), + canvasEvents: new Emitter(), + canvasCommands: createCommands({ + isReady: () => true, + getPageViewState: () => { + let nodes = viewState.current.nodes; + + for (const [nodeId, nodeInfo] of Object.entries(nodes)) { + nodes = update(nodes, { + [nodeId]: updateNodeInfo(nodeInfo, appRoot), + }); + } + + return { nodes }; + }, + getViewCoordinates: (clientX: number, clientY: number) => { + const rect = appRoot.getBoundingClientRect(); + if (rectContainsPoint(rect, clientX, clientY)) { + return { x: clientX - rect.x, y: clientY - rect.y }; + } + return null; + }, + invalidateQueries: () => { + queryClient.invalidateQueries(); + }, + update: () => {}, + }), + } satisfies ToolpadBridge; + + const handleScreenUpdate = throttle( + () => { + bridge?.canvasEvents.emit('screenUpdate', {}); + }, + 50, + { trailing: true }, + ); - const observer = new MutationObserver(() => { - setEditorOverlayRoot(iframeWindow.document.getElementById(HTML_ID_EDITOR_OVERLAY)); + projectEventSubscriptionRef.current = project.events.subscribe('queriesInvalidated', () => { + queryClient.invalidateQueries(); }); - observer.observe(iframeWindow.document.body, { - subtree: true, + const mutationObserver = new MutationObserver(handleScreenUpdate); + + mutationObserver.observe(appRoot, { + attributes: true, childList: true, + subtree: true, + characterData: true, }); - return () => { - observer.disconnect(); + const resizeObserver = new ResizeObserver(handleScreenUpdate); + + resizeObserver.observe(appRoot); + appRoot.querySelectorAll('*').forEach((elm) => resizeObserver.observe(elm)); + + appRootCleanupRef.current = () => { + handleScreenUpdate.cancel(); + mutationObserver.disconnect(); + resizeObserver.disconnect(); }; + + onInit?.(bridge); + setCanvasEvents(bridge.canvasEvents); }, - [handleKeyDown, initBridge], + [onInit, project.events], ); - const invalidateCanvasQueries = useEventCallback(() => { - bridge?.canvasCommands.invalidateQueries(); - }); + React.useEffect( + () => () => { + appRootCleanupRef.current?.(); + appRootCleanupRef.current = undefined; + projectEventSubscriptionRef.current?.(); + projectEventSubscriptionRef.current = undefined; + }, + [], + ); - React.useEffect(() => { - return project.events.subscribe('queriesInvalidated', invalidateCanvasQueries); - }, [project.events, invalidateCanvasQueries]); + const page = appDom.getPageByName(runtimeState.dom, pageName); return ( + + + + + + +
+ + + `} + onLoad={handleIframeLoad} /> + {page && portal + ? ReactDOM.createPortal( + + + + + + + + + + + , + portal, + ) + : null} {editorOverlayRoot ? ReactDOM.createPortal( {overlay}, editorOverlayRoot, ) : null} - - - - - -
); } diff --git a/packages/toolpad-studio/src/toolpad/AppEditor/PageEditor/EditorCanvasHostInline.tsx b/packages/toolpad-studio/src/toolpad/AppEditor/PageEditor/EditorCanvasHostInline.tsx deleted file mode 100644 index e0f35a134ed..00000000000 --- a/packages/toolpad-studio/src/toolpad/AppEditor/PageEditor/EditorCanvasHostInline.tsx +++ /dev/null @@ -1,285 +0,0 @@ -import * as React from 'react'; -import { styled, useEventCallback } from '@mui/material'; -import { NodeHashes, RuntimeEvents } from '@toolpad/studio-runtime'; -import createCache from '@emotion/cache'; -import { CacheProvider } from '@emotion/react'; -import * as ReactDOM from 'react-dom'; -import { Emitter } from '@toolpad/utils/events'; -import { update } from '@toolpad/utils/immutability'; -import { throttle } from 'lodash-es'; -import invariant from 'invariant'; -import * as appDom from '@toolpad/studio-runtime/appDom'; -import { CanvasEventsContext } from '@toolpad/studio-runtime/runtime'; -import { createCommands, type ToolpadBridge } from '../../../canvas/ToolpadBridge'; -import { useProject } from '../../../project'; -import { RuntimeState } from '../../../runtime'; -import { AppHost, AppHostContext } from '../../../runtime/AppHostContext'; -import { RenderedPage, ToolpadAppProvider } from '../../../runtime/ToolpadApp'; -import { CanvasHooks, CanvasHooksContext } from '../../../runtime/CanvasHooksContext'; -import { rectContainsPoint } from '../../../utils/geometry'; -import { queryClient } from '../../../runtime/api'; -import { PageViewState } from '../../../types'; -import { updateNodeInfo } from '../../../canvas'; -import { useAppStateApi } from '../../AppState'; - -interface OverlayProps { - children?: React.ReactNode; - container?: HTMLElement; -} - -function Overlay(props: OverlayProps) { - const { children, container } = props; - - const cache = React.useMemo( - () => - createCache({ - key: `toolpad-editor-overlay`, - prepend: true, - container, - }), - [container], - ); - - // See https://github.com/emotion-js/emotion/issues/1105#issuecomment-1058225197 - cache.compat = true; - - return {children}; -} - -export interface EditorCanvasHostProps { - className?: string; - pageName: string; - runtimeState: RuntimeState; - savedNodes: NodeHashes; - overlay?: React.ReactNode; - onInit?: (bridge: ToolpadBridge) => void; - base: string; -} - -const CanvasRoot = styled('div')({ - width: '100%', - position: 'relative', -}); - -const CanvasFrame = styled('iframe')({ - border: 'none', - position: 'absolute', - width: '100%', - height: '100%', -}); - -const appHost: AppHost = { - isPreview: true, - isCustomServer: false, - isCanvas: true, -}; - -export default function EditorCanvasHost({ - pageName, - className, - runtimeState, - base, - savedNodes, - overlay, - onInit, -}: EditorCanvasHostProps) { - const project = useProject(); - - const [canvasEvents, setCanvasEvents] = React.useState | null>(null); - - const [editorOverlayRoot, setEditorOverlayRoot] = React.useState(null); - - const [portal, setPortal] = React.useState(null); - - const appStateApi = useAppStateApi(); - - const handleIframeLoad = useEventCallback>((event) => { - invariant(event.currentTarget.contentDocument, 'iframe contentDocument is not available'); - const root = event.currentTarget.contentDocument.getElementById('root'); - invariant(root, 'root element not found'); - - const iframeWindow = event.currentTarget.contentWindow; - invariant(iframeWindow, 'Iframe not attached'); - - const handleKeyDown = (keyDownEvent: KeyboardEvent) => { - const isZ = !!keyDownEvent.key && keyDownEvent.key.toLowerCase() === 'z'; - - const undoShortcut = isZ && (keyDownEvent.metaKey || keyDownEvent.ctrlKey); - const redoShortcut = undoShortcut && keyDownEvent.shiftKey; - - if (redoShortcut) { - keyDownEvent.preventDefault(); - appStateApi.redo(); - } else if (undoShortcut) { - keyDownEvent.preventDefault(); - appStateApi.undo(); - } - }; - - iframeWindow.addEventListener('keydown', handleKeyDown); - iframeWindow.addEventListener('unload', () => { - iframeWindow.removeEventListener('keydown', handleKeyDown); - }); - - setPortal(root); - }); - - const viewState = React.useRef({ nodes: {} }); - - const canvasHooks: CanvasHooks = React.useMemo( - () => ({ - overlayRef: setEditorOverlayRoot, - savedNodes, - registerNode: (node, props, componentConfig) => { - viewState.current.nodes[node.id] = { - nodeId: node.id, - props, - componentConfig, - }; - - return () => { - delete viewState.current.nodes[node.id]; - }; - }, - }), - [savedNodes], - ); - - const appRootCleanupRef = React.useRef<() => void>(); - const projectEventSubscriptionRef = React.useRef<() => void>(); - const onAppRoot = React.useCallback( - (appRoot: HTMLDivElement) => { - appRootCleanupRef.current?.(); - appRootCleanupRef.current = undefined; - - if (!appRoot) { - return; - } - - const bridge: ToolpadBridge = { - editorEvents: new Emitter(), - editorCommands: createCommands(), - canvasEvents: new Emitter(), - canvasCommands: createCommands({ - isReady: () => true, - getPageViewState: () => { - let nodes = viewState.current.nodes; - - for (const [nodeId, nodeInfo] of Object.entries(nodes)) { - nodes = update(nodes, { - [nodeId]: updateNodeInfo(nodeInfo, appRoot), - }); - } - - return { nodes }; - }, - getViewCoordinates: (clientX: number, clientY: number) => { - const rect = appRoot.getBoundingClientRect(); - if (rectContainsPoint(rect, clientX, clientY)) { - return { x: clientX - rect.x, y: clientY - rect.y }; - } - return null; - }, - invalidateQueries: () => { - queryClient.invalidateQueries(); - }, - update: () => {}, - }), - } satisfies ToolpadBridge; - - const handleScreenUpdate = throttle( - () => { - bridge?.canvasEvents.emit('screenUpdate', {}); - }, - 50, - { trailing: true }, - ); - - projectEventSubscriptionRef.current = project.events.subscribe('queriesInvalidated', () => { - queryClient.invalidateQueries(); - }); - - const mutationObserver = new MutationObserver(handleScreenUpdate); - - mutationObserver.observe(appRoot, { - attributes: true, - childList: true, - subtree: true, - characterData: true, - }); - - const resizeObserver = new ResizeObserver(handleScreenUpdate); - - resizeObserver.observe(appRoot); - appRoot.querySelectorAll('*').forEach((elm) => resizeObserver.observe(elm)); - - appRootCleanupRef.current = () => { - handleScreenUpdate.cancel(); - mutationObserver.disconnect(); - resizeObserver.disconnect(); - }; - - onInit?.(bridge); - setCanvasEvents(bridge.canvasEvents); - }, - [onInit, project.events], - ); - - React.useEffect( - () => () => { - appRootCleanupRef.current?.(); - appRootCleanupRef.current = undefined; - projectEventSubscriptionRef.current?.(); - projectEventSubscriptionRef.current = undefined; - }, - [], - ); - - const page = appDom.getPageByName(runtimeState.dom, pageName); - - return ( - - - - - - - - -
- - - `} - onLoad={handleIframeLoad} - /> - {page && portal - ? ReactDOM.createPortal( - - - - - - - - - - - , - portal, - ) - : null} - {editorOverlayRoot - ? ReactDOM.createPortal( - {overlay}, - editorOverlayRoot, - ) - : null} -
- ); -} diff --git a/packages/toolpad-studio/src/toolpad/AppEditor/PageEditor/RenderPanel/RenderPanel.tsx b/packages/toolpad-studio/src/toolpad/AppEditor/PageEditor/RenderPanel/RenderPanel.tsx index 4f2c1268447..fddf05a302b 100644 --- a/packages/toolpad-studio/src/toolpad/AppEditor/PageEditor/RenderPanel/RenderPanel.tsx +++ b/packages/toolpad-studio/src/toolpad/AppEditor/PageEditor/RenderPanel/RenderPanel.tsx @@ -3,8 +3,7 @@ import { styled } from '@mui/material'; import { NodeHashes, NodeId } from '@toolpad/studio-runtime'; import useEventCallback from '@mui/utils/useEventCallback'; import * as appDom from '@toolpad/studio-runtime/appDom'; -import EditorCanvasHostLegacy from '../EditorCanvasHost'; -import EditorCanvasHostInline from '../EditorCanvasHostInline'; +import EditorCanvasHost from '../EditorCanvasHost'; import { getNodeHashes, useAppState, useAppStateApi, useDomApi } from '../../../AppState'; import { usePageEditorApi, usePageEditorState } from '../PageEditorProvider'; import RenderOverlay from './RenderOverlay'; @@ -13,10 +12,6 @@ import { getBindingType } from '../../../../runtime/bindings'; import createRuntimeState from '../../../../runtime/createRuntimeState'; import { RuntimeState } from '../../../../runtime'; -const EditorCanvasHost = process.env.EXPERIMENTAL_INLINE_CANVAS - ? EditorCanvasHostInline - : EditorCanvasHostLegacy; - const classes = { view: 'Toolpad_View', }; diff --git a/packages/toolpad-studio/src/toolpad/Toolpad.tsx b/packages/toolpad-studio/src/toolpad/Toolpad.tsx index c6a1f18299c..92a14a7c510 100644 --- a/packages/toolpad-studio/src/toolpad/Toolpad.tsx +++ b/packages/toolpad-studio/src/toolpad/Toolpad.tsx @@ -197,16 +197,3 @@ export function ToolpadEditor({ basename, state }: ToolpadEditorProps) { ); } - -export interface ToolpadEditorLegacyProps { - basename: string; - appUrl: string; -} - -export default function ToolpadEditorLegacy({ basename, appUrl }: ToolpadEditorLegacyProps) { - return ( - - - - ); -} diff --git a/packages/toolpad-studio/src/toolpad/index.html b/packages/toolpad-studio/src/toolpad/index.html deleted file mode 100644 index e228a56ba8c..00000000000 --- a/packages/toolpad-studio/src/toolpad/index.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - -
- - - - diff --git a/packages/toolpad-studio/src/toolpad/main.tsx b/packages/toolpad-studio/src/toolpad/main.tsx deleted file mode 100644 index 10516cbaa70..00000000000 --- a/packages/toolpad-studio/src/toolpad/main.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import * as React from 'react'; -import * as ReactDOM from 'react-dom/client'; -import invariant from 'invariant'; -import Toolpad from './Toolpad'; -import { APP_URL_WINDOW_PROPERTY } from '../constants'; - -declare global { - interface Window { - [APP_URL_WINDOW_PROPERTY]?: string; - } -} - -const appUrl = window[APP_URL_WINDOW_PROPERTY]; - -function Main() { - invariant(appUrl, 'Missing app url'); - return ; -} - -const container = document.getElementById('root'); -invariant(container, 'Missing root element'); -const root = ReactDOM.createRoot(container); -root.render(
); diff --git a/packages/toolpad-studio/src/toolpad/vite.config.mts b/packages/toolpad-studio/src/toolpad/vite.config.mts deleted file mode 100644 index bf5de6962a4..00000000000 --- a/packages/toolpad-studio/src/toolpad/vite.config.mts +++ /dev/null @@ -1,34 +0,0 @@ -import * as path from 'path'; -import * as fs from 'fs'; -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; - -const pkgJsonContent = fs.readFileSync(path.resolve(__dirname, '../../package.json'), { - encoding: 'utf-8', -}); -const pkgJson = JSON.parse(pkgJsonContent); -const TOOLPAD_BUILD = process.env.GIT_SHA1?.slice(0, 7) || 'dev'; - -// https://vitejs.dev/config/ -export default defineConfig({ - base: '/_toolpad/', - plugins: [react()], - build: { - outDir: path.resolve(__dirname, '../../dist/editor'), - emptyOutDir: true, - }, - resolve: { - dedupe: ['@mui/material'], - alias: { - vm: 'vm-browserify', - }, - }, - define: { - 'process.env.TOOLPAD_VERSION': JSON.stringify(pkgJson.version), - 'process.env.TOOLPAD_BUILD': JSON.stringify(TOOLPAD_BUILD), - 'process.env.EXPERIMENTAL_INLINE_CANVAS': JSON.stringify( - process.env.EXPERIMENTAL_INLINE_CANVAS, - ), - }, - publicDir: path.resolve(__dirname, '../../public'), -}); diff --git a/packages/toolpad-studio/src/utils/domView.ts b/packages/toolpad-studio/src/utils/domView.ts index c1957fd81f6..4dc75658206 100644 --- a/packages/toolpad-studio/src/utils/domView.ts +++ b/packages/toolpad-studio/src/utils/domView.ts @@ -43,7 +43,7 @@ export type DomView = { pageParametersDialogOpen?: boolean; }; -const PREFIX = process.env.EXPERIMENTAL_INLINE_CANVAS ? '/editor' : ''; +const PREFIX = '/editor'; export function getPathnameFromView(view: DomView): string { switch (view.kind) { diff --git a/test/integration/codeComponents/index.spec.ts b/test/integration/codeComponents/index.spec.ts index 74cf82f9d9a..978fec32483 100644 --- a/test/integration/codeComponents/index.spec.ts +++ b/test/integration/codeComponents/index.spec.ts @@ -61,12 +61,10 @@ export default createComponent(MyInspector, { { encoding: 'utf-8' }, ); - if (process.env.EXPERIMENTAL_INLINE_CANVAS) { - // vite causes a reload when we're creating new custom components - // See https://github.com/vitejs/vite/issues/12912 - await page.locator('[data-testid="page-ready-marker"]').isHidden(); - await editorModel.waitForOverlay(); - } + // vite causes a reload when we're creating new custom components + // See https://github.com/vitejs/vite/issues/12912 + await page.locator('[data-testid="page-ready-marker"]').isHidden(); + await editorModel.waitForOverlay(); await editorModel.componentCatalog.hover(); await expect(editorModel.getComponentCatalogItem('MyInspector')).toBeVisible(); diff --git a/test/integration/custom-server/dev.spec.ts b/test/integration/custom-server/dev.spec.ts index 64c919cae74..90763f00df7 100644 --- a/test/integration/custom-server/dev.spec.ts +++ b/test/integration/custom-server/dev.spec.ts @@ -1,11 +1,9 @@ import * as path from 'path'; import * as url from 'url'; -import * as fs from 'fs/promises'; import invariant from 'invariant'; import { folderExists } from '@toolpad/utils/fs'; -import { getTemporaryDir, runEditor, test, expect } from '../../playwright/localTest'; +import { test, expect } from '../../playwright/localTest'; import { expectBasicRuntimeTests, expectBasicRuntimeContentTests } from '../backend-basic/shared'; -import { using } from '../../utils/resources'; import { ToolpadEditor } from '../../models/ToolpadEditor'; const currentDirectory = url.fileURLToPath(new URL('.', import.meta.url)); @@ -43,49 +41,19 @@ test('standalone editor', async ({ context, customServer, page }) => { 'test must be configured with `customServerConfig`. Add `test.use({ customServerConfig: ... })`', ); - if (process.env.EXPERIMENTAL_INLINE_CANVAS) { - await context.addCookies([ - { name: 'MY_TOOLPAD_COOKIE', value: 'foo-bar-baz', domain: 'localhost', path: '/' }, - ]); - - await page.goto(`${customServer.url}/editor`); - const editorModel = new ToolpadEditor(page); - await editorModel.waitForOverlay(); - - await expectBasicRuntimeContentTests(editorModel.appCanvas); - - const pageFolder = path.resolve(customServer.dir, './toolpad/pages/helloWorld'); - await expect(await folderExists(pageFolder)).toBe(false); - await editorModel.createPage('helloWorld'); - - await expect.poll(async () => folderExists(pageFolder)).toBe(true); - } else { - await using(await getTemporaryDir(), async (editorDir) => { - await context.addCookies([ - { name: 'MY_TOOLPAD_COOKIE', value: 'foo-bar-baz', domain: 'localhost', path: '/' }, - ]); - - await using( - await runEditor({ - cwd: editorDir.path, - appUrl: customServer.url, - }), - async (editor) => { - await page.goto(`${editor.url}/_toolpad`); - const editorModel = new ToolpadEditor(page); - await editorModel.waitForOverlay(); + await context.addCookies([ + { name: 'MY_TOOLPAD_COOKIE', value: 'foo-bar-baz', domain: 'localhost', path: '/' }, + ]); - await expectBasicRuntimeContentTests(editorModel.appCanvas); + await page.goto(`${customServer.url}/editor`); + const editorModel = new ToolpadEditor(page); + await editorModel.waitForOverlay(); - const pageFolder = path.resolve(customServer.dir, './toolpad/pages/helloWorld'); - await expect(await folderExists(pageFolder)).toBe(false); - await editorModel.createPage('helloWorld'); + await expectBasicRuntimeContentTests(editorModel.appCanvas); - await expect.poll(async () => folderExists(pageFolder)).toBe(true); + const pageFolder = path.resolve(customServer.dir, './toolpad/pages/helloWorld'); + await expect(await folderExists(pageFolder)).toBe(false); + await editorModel.createPage('helloWorld'); - await expect(await fs.readdir(editorDir.path)).toEqual([]); - }, - ); - }); - } + await expect.poll(async () => folderExists(pageFolder)).toBe(true); }); diff --git a/test/integration/duplication/index.spec.ts b/test/integration/duplication/index.spec.ts index 227678743e5..dd12e35cf39 100644 --- a/test/integration/duplication/index.spec.ts +++ b/test/integration/duplication/index.spec.ts @@ -23,11 +23,7 @@ test('duplication', async ({ page }) => { const duplicateMenuItem = page.getByRole('menuitem', { name: 'Duplicate' }); await duplicateMenuItem.click(); - if (process.env.EXPERIMENTAL_INLINE_CANVAS) { - await page.waitForURL(/\/prod\/editor\/app\/pages\/[^/]+$/); - } else { - await page.waitForURL(/\/_toolpad\/app\/pages\/[^/]+$/); - } + await page.waitForURL(/\/prod\/editor\/app\/pages\/[^/]+$/); const button = editorModel.appCanvas.getByRole('button', { name: 'hello world' }); await expect(button).toBeVisible(); diff --git a/test/integration/editor/deleteLast.spec.ts b/test/integration/editor/deleteLast.spec.ts index d69380475a8..d7f658dab5f 100644 --- a/test/integration/editor/deleteLast.spec.ts +++ b/test/integration/editor/deleteLast.spec.ts @@ -24,9 +24,5 @@ test('do not find content if you delete page of middle ', async ({ page }) => { await expect(pageMenuItem).toBeHidden(); await expect(page.getByText('No pages in this app.')).toBeVisible(); - if (process.env.EXPERIMENTAL_INLINE_CANVAS) { - await expect(page).toHaveURL('/prod/editor/app/pages'); - } else { - await expect(page).toHaveURL('/_toolpad/app/pages'); - } + await expect(page).toHaveURL('/prod/editor/app/pages'); });