Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix HMR issues with the canvas bridge #1640

Merged
merged 8 commits into from
Feb 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/toolpad-app/src/canvas/BridgeContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import * as React from 'react';
import type { ToolpadBridge } from './ToolpadBridge';

export const BridgeContext = React.createContext<ToolpadBridge | null>(null);
13 changes: 11 additions & 2 deletions packages/toolpad-app/src/canvas/ToolpadBridge.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import type { RuntimeEvents } from '@mui/toolpad-core';
import mitt, { Emitter } from 'mitt';
import type { AppCanvasState } from '.';
import { TOOLPAD_BRIDGE_GLOBAL } from '../constants';
import type { PageViewState } from '../types';

export const TOOLPAD_BRIDGE_GLOBAL = '__TOOLPAD_BRIDGE__';

declare global {
interface Window {
[TOOLPAD_BRIDGE_GLOBAL]?: ToolpadBridge;
Expand Down Expand Up @@ -71,6 +70,16 @@ export interface ToolpadBridge {
}>;
}

if (
typeof window !== 'undefined' &&
process.env.NODE_ENV !== 'test' &&
!(window.frameElement as HTMLIFrameElement | null)?.dataset.toolpadCanvas
) {
throw new Error(
'An attempt was made at setting up the canvas bridge outside of the canvas. Was this file imported unintentionally?',
);
}

let canvasIsReady = false;
export const bridge: ToolpadBridge = {
editorEvents: mitt(),
Expand Down
27 changes: 15 additions & 12 deletions packages/toolpad-app/src/canvas/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import getPageViewState from './getPageViewState';
import { rectContainsPoint } from '../utils/geometry';
import { CanvasHooks, CanvasHooksContext } from '../runtime/CanvasHooksContext';
import { bridge, setCommandHandler } from './ToolpadBridge';
import { BridgeContext } from './BridgeContext';

export interface AppCanvasState extends RuntimeState {
savedNodes: NodeHashes;
Expand Down Expand Up @@ -129,17 +130,19 @@ export default function AppCanvas({ catalog, basename, initialState = null }: Ap
}, [savedNodes]);

return state ? (
<CanvasHooksContext.Provider value={editorHooks}>
<CanvasEventsContext.Provider value={bridge.canvasEvents}>
<ToolpadApp
rootRef={onAppRoot}
catalog={catalog}
hidePreviewBanner
version="preview"
basename={basename}
state={state}
/>
</CanvasEventsContext.Provider>
</CanvasHooksContext.Provider>
<BridgeContext.Provider value={bridge}>
<CanvasHooksContext.Provider value={editorHooks}>
<CanvasEventsContext.Provider value={bridge.canvasEvents}>
<ToolpadApp
rootRef={onAppRoot}
catalog={catalog}
hidePreviewBanner
version="preview"
basename={basename}
state={state}
/>
</CanvasEventsContext.Provider>
</CanvasHooksContext.Provider>
</BridgeContext.Provider>
) : null;
}
2 changes: 2 additions & 0 deletions packages/toolpad-app/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ export const PRODUCTION_DATASOURCES = new Set([
]);

export const APP_ID_LOCAL_MARKER = '__LOCAL_MODE_APP_';

export const TOOLPAD_BRIDGE_GLOBAL = '__TOOLPAD_BRIDGE__';
8 changes: 7 additions & 1 deletion packages/toolpad-app/src/runtime/ToolpadApp.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import ToolpadApp from './ToolpadApp';
import * as appDom from '../appDom';
import createRuntimeState from '../createRuntimeState';
import { bridge } from '../canvas/ToolpadBridge';
import { BridgeContext } from '../canvas/BridgeContext';

// More sensible default for these tests
const waitFor: typeof waitForOrig = (waiter, options) =>
Expand All @@ -31,7 +32,12 @@ function renderPage(initPage: (dom: appDom.AppDom, page: appDom.PageNode) => app

const state = createRuntimeState({ appId, dom });

return render(<ToolpadApp state={state} version={version} basename="toolpad" />);
return render(
<BridgeContext.Provider value={bridge}>
{' '}
<ToolpadApp state={state} version={version} basename="toolpad" />
</BridgeContext.Provider>,
);
}

test(`Static Text`, async () => {
Expand Down
18 changes: 8 additions & 10 deletions packages/toolpad-app/src/runtime/ToolpadApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
NestedBindableAttrs,
GlobalScopeMeta,
BindingEvaluationResult,
ArgTypeDefinition,
getArgTypeDefaultValue,
} from '@mui/toolpad-core';
import { createProvidedContext } from '@mui/toolpad-core/utils/react';
import { QueryClient, QueryClientProvider, useMutation } from '@tanstack/react-query';
Expand Down Expand Up @@ -74,13 +74,9 @@ import { useAppContext, AppContextProvider } from './AppContext';
import { CanvasHooksContext, NavigateToPage } from './CanvasHooksContext';
import useBoolean from '../utils/useBoolean';
import { errorFrom } from '../utils/errors';
import { bridge } from '../canvas/ToolpadBridge';
import Header from '../toolpad/ToolpadShell/Header';
import { ThemeProvider } from '../ThemeContext';

export function getArgTypeDefaultValue<V>(argType: ArgTypeDefinition<{}, V>): V | undefined {
return argType.typeDef.default ?? argType.defaultValue ?? undefined;
}
import { BridgeContext } from '../canvas/BridgeContext';

const ReactQueryDevtoolsProduction = React.lazy(() =>
import('@tanstack/react-query-devtools/build/lib/index.prod.js').then((d) => ({
Expand Down Expand Up @@ -850,13 +846,15 @@ function RenderedPage({ nodeId }: RenderedNodeProps) {
[browserJsRuntime, pageState],
);

const bridge = React.useContext(BridgeContext);

React.useEffect(() => {
bridge.canvasEvents.emit('pageStateUpdated', { pageState, globalScopeMeta });
}, [pageState, globalScopeMeta]);
bridge?.canvasEvents.emit('pageStateUpdated', { pageState, globalScopeMeta });
}, [pageState, globalScopeMeta, bridge]);

React.useEffect(() => {
bridge.canvasEvents.emit('pageBindingsUpdated', { bindings: liveBindings });
}, [liveBindings]);
bridge?.canvasEvents.emit('pageBindingsUpdated', { bindings: liveBindings });
}, [bridge, liveBindings]);

return (
<BindingsContextProvider value={liveBindings}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
TOOLPAD_COMPONENT,
ArgTypeDefinitions,
ArgTypeDefinition,
getArgTypeDefaultValue,
} from '@mui/toolpad-core';
import { useQuery } from '@tanstack/react-query';
import invariant from 'invariant';
Expand All @@ -36,7 +37,6 @@ import { useNodeNameValidation } from '../HierarchyExplorer/validation';
import useUndoRedo from '../../hooks/useUndoRedo';
import config from '../../../config';
import client from '../../../api';
import { getArgTypeDefaultValue } from '../../../runtime';

const TypescriptEditor = lazyComponent(() => import('../../../components/TypescriptEditor'), {
noSsr: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import { CacheProvider } from '@emotion/react';
import ReactDOM from 'react-dom';
import invariant from 'invariant';
import * as appDom from '../../../appDom';
import { HTML_ID_EDITOR_OVERLAY } from '../../../constants';
import { HTML_ID_EDITOR_OVERLAY, TOOLPAD_BRIDGE_GLOBAL } from '../../../constants';
import { NodeHashes } from '../../../types';
import useEvent from '../../../utils/useEvent';
import { LogEntry } from '../../../components/Console';
import { useDomApi } from '../../DomLoader';
import createRuntimeState from '../../../createRuntimeState';
import { ToolpadBridge, TOOLPAD_BRIDGE_GLOBAL } from '../../../canvas/ToolpadBridge';
import type { ToolpadBridge } from '../../../canvas/ToolpadBridge';
import CenteredSpinner from '../../../components/CenteredSpinner';

interface OverlayProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import NodeHud from './NodeHud';
import { OverlayGrid, OverlayGridHandle } from './OverlayGrid';
import { NodeInfo } from '../../../../types';
import NodeDropArea from './NodeDropArea';
import { ToolpadBridge } from '../../../../canvas/ToolpadBridge';
import type { ToolpadBridge } from '../../../../canvas/ToolpadBridge';

const VERTICAL_RESIZE_SNAP_UNITS = 2; // px

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { usePageEditorApi, usePageEditorState } from '../PageEditorProvider';
import RenderOverlay from './RenderOverlay';
import { NodeHashes } from '../../../../types';
import useEvent from '../../../../utils/useEvent';
import { ToolpadBridge } from '../../../../canvas/ToolpadBridge';
import type { ToolpadBridge } from '../../../../canvas/ToolpadBridge';

const classes = {
view: 'Toolpad_View',
Expand Down
6 changes: 5 additions & 1 deletion packages/toolpad-core/src/createComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TOOLPAD_COMPONENT } from './constants.js';
import { ComponentConfig, ToolpadComponent } from './types.js';
import { ArgTypeDefinition, ComponentConfig, ToolpadComponent } from './types.js';

export default function createComponent<P extends object>(
Component: React.ComponentType<P>,
Expand All @@ -9,3 +9,7 @@ export default function createComponent<P extends object>(
[TOOLPAD_COMPONENT]: config || { argTypes: {} },
});
}

export function getArgTypeDefaultValue<V>(argType: ArgTypeDefinition<{}, V>): V | undefined {
return argType.typeDef.default ?? argType.defaultValue ?? undefined;
}
1 change: 1 addition & 0 deletions packages/toolpad-core/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export { default as useUrlQueryState } from './useUrlQueryState.js';
export * from './constants.js';

export { default as createComponent } from './createComponent.js';
export * from './createComponent.js';

export * from './types.js';

Expand Down