From c081230212dbcac50f41379cdf734a67da0bbb95 Mon Sep 17 00:00:00 2001
From: RheeseyB <1044774+Rheeseyb@users.noreply.github.com>
Date: Tue, 19 Sep 2023 11:18:40 +0100
Subject: [PATCH] Move dependencies from createRemixDerivedData into
useGetRoutes hook (#4205)
* fix(editor) Move dependencies from createRemixDerivedData into useGetRoutes hook
* chore(tests) Updated a remix test to navigate into posts
---
.../remix/remix-navigator.spec.browser2.tsx | 14 +-
.../remix/remix-rendering.spec.browser2.tsx | 143 +++++++++++-------
.../components/canvas/remix/remix-utils.tsx | 79 ++++------
.../remix/utopia-remix-root-component.tsx | 109 ++++++++++++-
.../components/editor/store/editor-state.ts | 5 -
.../editor/store/remix-derived-data.tsx | 51 ++++---
6 files changed, 259 insertions(+), 142 deletions(-)
diff --git a/editor/src/components/canvas/remix/remix-navigator.spec.browser2.tsx b/editor/src/components/canvas/remix/remix-navigator.spec.browser2.tsx
index 946cace86fad..68799353fa76 100644
--- a/editor/src/components/canvas/remix/remix-navigator.spec.browser2.tsx
+++ b/editor/src/components/canvas/remix/remix-navigator.spec.browser2.tsx
@@ -1,18 +1,26 @@
import * as EP from '../../../core/shared/element-path'
import { createModifiedProject } from '../../../sample-projects/sample-project-utils.test-utils'
import { setFeatureForBrowserTestsUseInDescribeBlockOnly } from '../../../utils/utils.test-utils'
+import { runDOMWalker } from '../../editor/actions/action-creators'
import {
StoryboardFilePath,
navigatorEntryToKey,
regularNavigatorEntry,
varSafeNavigatorEntryToKey,
} from '../../editor/store/editor-state'
+import type { PersistentModel } from '../../editor/store/editor-state'
import { NavigatorItemTestId } from '../../navigator/navigator-item/navigator-item'
import { renderTestEditorWithModel } from '../ui-jsx.test-utils'
const DefaultRouteTextContent = 'Hello Remix!'
const RootTextContent = 'This is root!'
+async function renderRemixProject(project: PersistentModel) {
+ const renderResult = await renderTestEditorWithModel(project, 'await-first-dom-report')
+ await renderResult.dispatch([runDOMWalker()], true)
+ return renderResult
+}
+
describe('Remix navigator', () => {
setFeatureForBrowserTestsUseInDescribeBlockOnly('Remix support', true)
it('Shows navigator for remix content', async () => {
@@ -67,7 +75,7 @@ describe('Remix navigator', () => {
`,
})
- const renderResult = await renderTestEditorWithModel(project, 'await-first-dom-report')
+ const renderResult = await renderRemixProject(project)
expect(renderResult.getEditorState().derived.navigatorTargets.map(navigatorEntryToKey)).toEqual(
[
'regular-storyboard/remix-scene',
@@ -130,7 +138,7 @@ describe('Remix navigator', () => {
`,
})
- const renderResult = await renderTestEditorWithModel(project, 'await-first-dom-report')
+ const renderResult = await renderRemixProject(project)
expect(renderResult.getEditorState().derived.navigatorTargets.map(navigatorEntryToKey)).toEqual(
[
'regular-storyboard/remix-scene',
@@ -194,7 +202,7 @@ describe('Remix navigator', () => {
`,
})
- const renderResult = await renderTestEditorWithModel(project, 'await-first-dom-report')
+ const renderResult = await renderRemixProject(project)
const navigatorItemElement = renderResult.renderedDOM.getByTestId(
NavigatorItemTestId(
varSafeNavigatorEntryToKey(
diff --git a/editor/src/components/canvas/remix/remix-rendering.spec.browser2.tsx b/editor/src/components/canvas/remix/remix-rendering.spec.browser2.tsx
index 3adba5bfab0c..2ef96ca2921b 100644
--- a/editor/src/components/canvas/remix/remix-rendering.spec.browser2.tsx
+++ b/editor/src/components/canvas/remix/remix-rendering.spec.browser2.tsx
@@ -9,9 +9,10 @@ import {
selectComponentsForTest,
setFeatureForBrowserTestsUseInDescribeBlockOnly,
} from '../../../utils/utils.test-utils'
-import { switchEditorMode } from '../../editor/actions/action-creators'
+import { runDOMWalker, switchEditorMode } from '../../editor/actions/action-creators'
import { EditorModes } from '../../editor/editor-modes'
import { StoryboardFilePath, navigatorEntryToKey } from '../../editor/store/editor-state'
+import type { PersistentModel } from '../../editor/store/editor-state'
import { AddRemoveLayouSystemControlTestId } from '../../inspector/add-remove-layout-system-control'
import { CanvasControlsContainerID } from '../controls/new-canvas-controls'
import {
@@ -56,6 +57,12 @@ export var storyboard = (
/* eslint jest/expect-expect: ["error", { "assertFunctionNames": ["expect", "expectRemixSceneToBeRendered"] }] */
+async function renderRemixProject(project: PersistentModel) {
+ const renderResult = await renderTestEditorWithModel(project, 'await-first-dom-report')
+ await renderResult.dispatch([runDOMWalker()], true)
+ return renderResult
+}
+
describe('Remix content', () => {
setFeatureForBrowserTestsUseInDescribeBlockOnly('Remix support', true)
it('Renders the remix container with actual content', async () => {
@@ -98,7 +105,7 @@ describe('Remix content', () => {
`,
})
- const renderResult = await renderTestEditorWithModel(project, 'await-first-dom-report')
+ const renderResult = await renderRemixProject(project)
await expectRemixSceneToBeRendered(renderResult)
})
@@ -154,7 +161,7 @@ describe('Remix content', () => {
`,
})
- const renderResult = await renderTestEditorWithModel(project, 'await-first-dom-report')
+ const renderResult = await renderRemixProject(project)
const remixDivMetadata =
renderResult.getEditorState().editor.jsxMetadata[
@@ -234,7 +241,7 @@ describe('Remix content', () => {
`,
})
- const renderResult = await renderTestEditorWithModel(project, 'await-first-dom-report')
+ const renderResult = await renderRemixProject(project)
const remixDivMetadata =
renderResult.getEditorState().editor.jsxMetadata[
@@ -318,7 +325,7 @@ describe('Remix content', () => {
`,
})
- const renderResult = await renderTestEditorWithModel(project, 'await-first-dom-report')
+ const renderResult = await renderRemixProject(project)
const remixDivMetadata =
renderResult.getEditorState().editor.jsxMetadata[
@@ -392,7 +399,7 @@ describe('Remix content', () => {
`,
})
- const renderResult = await renderTestEditorWithModel(project, 'await-first-dom-report')
+ const renderResult = await renderRemixProject(project)
const targetElement = renderResult.renderedDOM.getByTestId('remix-div')
const targetElementBounds = targetElement.getBoundingClientRect()
@@ -452,7 +459,7 @@ describe('Remix content', () => {
`,
})
- const renderResult = await renderTestEditorWithModel(project, 'await-first-dom-report')
+ const renderResult = await renderRemixProject(project)
const targetElement = renderResult.renderedDOM.getAllByTestId('remix-div')[1]
const targetElementBounds = targetElement.getBoundingClientRect()
@@ -521,7 +528,7 @@ describe('Remix content', () => {
`,
})
- const renderResult = await renderTestEditorWithModel(project, 'await-first-dom-report')
+ const renderResult = await renderRemixProject(project)
const targetElement = renderResult.renderedDOM.getByTestId(DraggedElementId)
const targetElementBounds = targetElement.getBoundingClientRect()
@@ -543,7 +550,7 @@ describe('Remix content with feature switch off', () => {
const project = createModifiedProject({
[StoryboardFilePath]: storyboardFileContent,
})
- const renderResult = await renderTestEditorWithModel(project, 'await-first-dom-report')
+ const renderResult = await renderRemixProject(project)
await expect(async () =>
renderResult.renderedDOM.findAllByTestId(REMIX_SCENE_TESTID),
).rejects.toThrow()
@@ -588,28 +595,80 @@ describe('Remix navigation', () => {
import { Link } from '@remix-run/react'
export default function Index() {
- return ${DefaultRouteTextContent}
+ return ${DefaultRouteTextContent}
}
`,
- ['/src/routes/about.js']: `import React from 'react'
-
- export default function About() {
- return
About
+ ['/src/routes/posts._index.js']: `import React from 'react'
+ import { Link } from '@remix-run/react'
+ import { json, useLoaderData } from 'react-router'
+
+ export function loader() {
+ return json({
+ activities: [
+ {
+ id: 0,
+ name: 'Do the thing',
+ },
+ ]
+ })
+ }
+
+ export default function Posts() {
+ const { activities } = useLoaderData()
+ return (
+
+ {activities.map(
+ ({
+ id,
+ name,
+ }) => (
+ {name}
+ )
+ )}
+
+ )
+ }
+ `,
+ ['/src/routes/posts.$postId.js']: `import React from 'react'
+ import { json, useLoaderData } from 'react-router'
+
+ export function loader({ params }) {
+ return json({
+ id: 0,
+ name: 'Do the thing',
+ desc: 'Do it now!'
+ })
+ }
+
+ export default function Post() {
+ const {
+ desc
+ } = useLoaderData()
+
+ return (
+ {desc}
+ )
}
`,
})
- const renderResult = await renderTestEditorWithModel(project, 'await-first-dom-report')
+ const renderResult = await renderRemixProject(project)
await renderResult.dispatch([switchEditorMode(EditorModes.liveMode())], true)
const targetElement = renderResult.renderedDOM.getByTestId('remix-link')
const targetElementBounds = targetElement.getBoundingClientRect()
- const clickPoint = windowPoint({ x: targetElementBounds.x + 5, y: targetElementBounds.y + 5 })
+ await mouseClickAtPoint(targetElement, {
+ x: targetElementBounds.x + 5,
+ y: targetElementBounds.y + 5,
+ })
- await mouseClickAtPoint(targetElement, clickPoint)
+ const postLink = renderResult.renderedDOM.getByTestId('post-link')
+ const postLinkBounds = postLink.getBoundingClientRect()
- await expectRemixSceneToBeRendered(renderResult, 'About')
+ await mouseClickAtPoint(postLink, { x: postLinkBounds.x + 5, y: postLinkBounds.y + 5 })
+
+ await expectRemixSceneToBeRendered(renderResult, 'Do it now!')
})
it('Remix navigation updates metadata', async () => {
@@ -671,7 +730,8 @@ describe('Remix navigation', () => {
`,
})
- const renderResult = await renderTestEditorWithModel(project, 'await-first-dom-report')
+ const renderResult = await renderRemixProject(project)
+ await renderResult.dispatch([switchEditorMode(EditorModes.liveMode())], true)
const remixLinkMetadata =
renderResult.getEditorState().editor.jsxMetadata[
@@ -679,8 +739,6 @@ describe('Remix navigation', () => {
]
expect(remixLinkMetadata).not.toBeUndefined()
- await renderResult.dispatch([switchEditorMode(EditorModes.liveMode())], true)
-
const targetElement = renderResult.renderedDOM.getByTestId('remix-link')
const targetElementBounds = targetElement.getBoundingClientRect()
@@ -753,7 +811,7 @@ describe('Editing Remix content', () => {
`,
})
- const renderResult = await renderTestEditorWithModel(project, 'await-first-dom-report')
+ const renderResult = await renderRemixProject(project)
const pathString = 'sb/rs:root/outlet:title'
@@ -936,7 +994,7 @@ describe('Editing Remix content', () => {
`,
})
- const renderResult = await renderTestEditorWithModel(project, 'await-first-dom-report')
+ const renderResult = await renderRemixProject(project)
const pathString = 'sb/rs:root/outlet2:title'
@@ -949,10 +1007,7 @@ describe('Editing Remix content', () => {
})
it('delete element from remix scene', async () => {
- const renderResult = await renderTestEditorWithModel(
- remixProjectForEditingTests,
- 'await-first-dom-report',
- )
+ const renderResult = await renderRemixProject(remixProjectForEditingTests)
expect(renderResult.renderedDOM.queryAllByTestId(FlexDivTestId)).toHaveLength(1)
@@ -999,10 +1054,7 @@ export default function Index() {
})
it('use the inspector to add a layout system', async () => {
- const renderResult = await renderTestEditorWithModel(
- remixProjectForEditingTests,
- 'await-first-dom-report',
- )
+ const renderResult = await renderRemixProject(remixProjectForEditingTests)
const absoluteDiv = await clickElementOnCanvas(renderResult, AbsoluteDivTestId)
@@ -1013,10 +1065,9 @@ export default function Index() {
})
it('flex reorder elements inside Remix', async () => {
- const renderResult = await renderTestEditorWithModel(
- remixProjectForEditingTests,
- 'await-first-dom-report',
- )
+ const renderResult = await renderRemixProject(remixProjectForEditingTests)
+
+ await renderResult.dispatch([runDOMWalker()], true)
expect(renderResult.getEditorState().derived.navigatorTargets.map(navigatorEntryToKey)).toEqual(
[
@@ -1056,10 +1107,7 @@ export default function Index() {
})
it('absolute move elements inside Remix', async () => {
- const renderResult = await renderTestEditorWithModel(
- remixProjectForEditingTests,
- 'await-first-dom-report',
- )
+ const renderResult = await renderRemixProject(remixProjectForEditingTests)
const absoluteDiv = await clickElementOnCanvas(renderResult, AbsoluteDivTestId)
expect({ left: absoluteDiv.style.left, top: absoluteDiv.style.top }).toEqual({
@@ -1081,10 +1129,9 @@ export default function Index() {
})
it('draw to insert into Remix', async () => {
- const renderResult = await renderTestEditorWithModel(
- remixProjectForEditingTests,
- 'await-first-dom-report',
- )
+ const renderResult = await renderRemixProject(remixProjectForEditingTests)
+
+ await renderResult.dispatch([runDOMWalker()], true)
expect(renderResult.getEditorState().derived.navigatorTargets.map(navigatorEntryToKey)).toEqual(
[
@@ -1203,10 +1250,7 @@ export default function Index() {
const clipboardMock = new MockClipboardHandlers().mock()
it('copy-paste element inside Remix', async () => {
- const renderResult = await renderTestEditorWithModel(
- remixProjectForEditingTests,
- 'await-first-dom-report',
- )
+ const renderResult = await renderRemixProject(remixProjectForEditingTests)
await selectComponentsForTest(renderResult, [
EP.fromString('sb/remix-scene:app/outlet:index/absolute-div'),
@@ -1310,10 +1354,7 @@ export default function Index() {
})
it('dragging elements between Remix and the storyboard', async () => {
- const renderResult = await renderTestEditorWithModel(
- remixProjectForEditingTests,
- 'await-first-dom-report',
- )
+ const renderResult = await renderRemixProject(remixProjectForEditingTests)
expect(renderResult.getEditorState().derived.navigatorTargets.map(navigatorEntryToKey)).toEqual(
[
'regular-sb/remix-scene',
diff --git a/editor/src/components/canvas/remix/remix-utils.tsx b/editor/src/components/canvas/remix/remix-utils.tsx
index 940229fd9a62..ea6be7fdebf3 100644
--- a/editor/src/components/canvas/remix/remix-utils.tsx
+++ b/editor/src/components/canvas/remix/remix-utils.tsx
@@ -139,7 +139,7 @@ export function createAssetsManifest(routes: RouteManifest): AssetsM
export interface RouteModuleCreator {
filePath: string
- executionScopeCreator: (projectContents: ProjectContentTreeRoot) => ExecutionScope
+ executionScopeCreator: ExecutionScopeCreator
}
export interface RouteIdsToModuleCreators {
@@ -160,22 +160,6 @@ export interface GetRoutesAndModulesFromManifestResult {
routingTable: RemixRoutingTable
}
-function addLoaderAndActionToRoutes(
- routes: DataRouteObject[],
- routeId: string,
- loader: LoaderFunction | undefined,
- action: ActionFunction | undefined,
-) {
- routes.forEach((route) => {
- if (route.id === routeId) {
- route.action = action
- route.loader = loader
- } else {
- addLoaderAndActionToRoutes(route.children ?? [], routeId, loader, action)
- }
- })
-}
-
function getRouteModulesWithPaths(
projectContents: ProjectContentTreeRoot,
manifest: RouteManifest,
@@ -235,32 +219,41 @@ function getRouteModulesWithPaths(
return routeModulesWithBasePaths
}
+export type ExecutionScopeCreator = (
+ innerProjectContents: ProjectContentTreeRoot,
+ fileBlobs: CanvasBase64Blobs,
+ hiddenInstances: Array,
+ displayNoneInstances: Array,
+ metadataContext: UiJsxCanvasContextData,
+) => ExecutionScope
+
function getRemixExportsOfModule(
filename: string,
curriedRequireFn: CurriedUtopiaRequireFn,
curriedResolveFn: CurriedResolveFn,
- metadataContext: UiJsxCanvasContextData,
projectContents: ProjectContentTreeRoot,
- mutableContextRef: React.MutableRefObject,
- topLevelComponentRendererComponents: React.MutableRefObject<
- MapLike>
- >,
- fileBlobs: CanvasBase64Blobs,
- hiddenInstances: Array,
- displayNoneInstances: Array,
): {
- executionScopeCreator: (innerProjectContents: ProjectContentTreeRoot) => ExecutionScope
- loader: LoaderFunction | undefined
- action: ActionFunction | undefined
+ executionScopeCreator: ExecutionScopeCreator
rootComponentUid: string
} {
- const executionScopeCreator = (innerProjectContents: ProjectContentTreeRoot) => {
+ const executionScopeCreator = (
+ innerProjectContents: ProjectContentTreeRoot,
+ fileBlobs: CanvasBase64Blobs,
+ hiddenInstances: Array,
+ displayNoneInstances: Array,
+ metadataContext: UiJsxCanvasContextData,
+ ) => {
let resolvedFiles: MapLike> = {}
let resolvedFileNames: Array = ['/src/root.js']
const requireFn = curriedRequireFn(innerProjectContents)
const resolve = curriedResolveFn(innerProjectContents)
+ let mutableContextRef: { current: MutableUtopiaCtxRefData } = { current: {} }
+ let topLevelComponentRendererComponents: {
+ current: MapLike>
+ } = { current: {} }
+
const customRequire = (importOrigin: string, toImport: string) => {
if (resolvedFiles[importOrigin] == null) {
resolvedFiles[importOrigin] = []
@@ -281,9 +274,9 @@ function getRemixExportsOfModule(
mutableContextRef,
topLevelComponentRendererComponents,
'/src/root.js',
- {},
- [],
- [],
+ fileBlobs,
+ hiddenInstances,
+ displayNoneInstances,
metadataContext,
NO_OP,
false,
@@ -319,16 +312,11 @@ function getRemixExportsOfModule(
)
}
- const executionScope = executionScopeCreator(projectContents)
-
const nameAndUid = getDefaultExportNameAndUidFromFile(projectContents, filename)
return {
executionScopeCreator: executionScopeCreator,
rootComponentUid: nameAndUid?.uid ?? 'NO-ROOT',
- // FIXME the executionScope should be created at the point where we use the loader and action like we do for the module's default export component
- loader: executionScope.scope['loader'] as LoaderFunction | undefined,
- action: executionScope.scope['action'] as ActionFunction | undefined,
}
}
export function getRoutesAndModulesFromManifest(
@@ -336,16 +324,8 @@ export function getRoutesAndModulesFromManifest(
futureConfig: FutureConfig,
curriedRequireFn: CurriedUtopiaRequireFn,
curriedResolveFn: CurriedResolveFn,
- metadataContext: UiJsxCanvasContextData,
projectContents: ProjectContentTreeRoot,
- mutableContextRef: React.MutableRefObject,
- topLevelComponentRendererComponents: React.MutableRefObject<
- MapLike>
- >,
routeModulesCache: RouteModules,
- fileBlobs: CanvasBase64Blobs,
- hiddenInstances: Array,
- displayNoneInstances: Array,
): GetRoutesAndModulesFromManifestResult | null {
const routeModuleCreators: RouteIdsToModuleCreators = {}
const routingTable: RemixRoutingTable = {}
@@ -381,20 +361,13 @@ export function getRoutesAndModulesFromManifest(
)
Object.values(routeManifest).forEach((route) => {
- const { executionScopeCreator, loader, action, rootComponentUid } = getRemixExportsOfModule(
+ const { executionScopeCreator, rootComponentUid } = getRemixExportsOfModule(
route.module,
curriedRequireFn,
curriedResolveFn,
- metadataContext,
projectContents,
- mutableContextRef,
- topLevelComponentRendererComponents,
- fileBlobs,
- hiddenInstances,
- displayNoneInstances,
)
- addLoaderAndActionToRoutes(routes, route.id, loader, action)
routeModuleCreators[route.id] = {
filePath: route.module,
executionScopeCreator: executionScopeCreator,
diff --git a/editor/src/components/canvas/remix/utopia-remix-root-component.tsx b/editor/src/components/canvas/remix/utopia-remix-root-component.tsx
index ad95041bc70d..610c54e71a05 100644
--- a/editor/src/components/canvas/remix/utopia-remix-root-component.tsx
+++ b/editor/src/components/canvas/remix/utopia-remix-root-component.tsx
@@ -1,7 +1,7 @@
import { RemixContext } from '@remix-run/react/dist/components'
import type { RouteModules } from '@remix-run/react/dist/routeModules'
import React from 'react'
-import type { Location } from 'react-router'
+import type { DataRouteObject, Location } from 'react-router'
import { createMemoryRouter, RouterProvider } from 'react-router'
import { UTOPIA_PATH_KEY } from '../../../core/model/utopia-constants'
import type { ElementPath } from '../../../core/shared/project-file-types'
@@ -11,6 +11,10 @@ import { PathPropHOC } from './path-props-hoc'
import { atom, useAtom, useSetAtom } from 'jotai'
import { getDefaultExportNameAndUidFromFile } from '../../../core/model/project-file-utils'
import { OutletPathContext } from './remix-utils'
+import { UiJsxCanvasCtxAtom } from '../ui-jsx-canvas'
+import type { UiJsxCanvasContextData } from '../ui-jsx-canvas'
+import { forceNotNull } from '../../../core/shared/optional-utils'
+import { AlwaysFalse, usePubSubAtomReadOnly } from '../../../core/shared/atom-with-pub-sub'
type RouterType = ReturnType
@@ -32,6 +36,14 @@ export const RemixNavigationAtom = atom({})
function useGetRouteModules(basePath: ElementPath) {
const remixDerivedDataRef = useRefEditorState((store) => store.derived.remixData)
const projectContentsRef = useRefEditorState((store) => store.editor.projectContents)
+ const fileBlobsRef = useRefEditorState((store) => store.editor.canvas.base64Blobs)
+ const hiddenInstancesRef = useRefEditorState((store) => store.editor.hiddenInstances)
+ const displayNoneInstancesRef = useRefEditorState((store) => store.editor.displayNoneInstances)
+
+ let metadataContext: UiJsxCanvasContextData = forceNotNull(
+ `Missing UiJsxCanvasCtxAtom provider`,
+ usePubSubAtomReadOnly(UiJsxCanvasCtxAtom, AlwaysFalse),
+ )
const defaultExports = useEditorState(
Substores.derived,
@@ -65,7 +77,13 @@ function useGetRouteModules(basePath: ElementPath) {
const defaultComponent = (componentProps: any) =>
value
- .executionScopeCreator(projectContentsRef.current)
+ .executionScopeCreator(
+ projectContentsRef.current,
+ fileBlobsRef.current,
+ hiddenInstancesRef.current,
+ displayNoneInstancesRef.current,
+ metadataContext,
+ )
.scope[nameAndUid.name]?.(componentProps) ??
routeModulesResult[routeId] = {
@@ -75,7 +93,86 @@ function useGetRouteModules(basePath: ElementPath) {
}
return routeModulesResult
- }, [defaultExports, remixDerivedDataRef, projectContentsRef])
+ }, [
+ defaultExports,
+ metadataContext,
+ remixDerivedDataRef,
+ projectContentsRef,
+ fileBlobsRef,
+ hiddenInstancesRef,
+ displayNoneInstancesRef,
+ ])
+}
+
+function useGetRoutes() {
+ const routes = useEditorState(
+ Substores.derived,
+ (store) => store.derived.remixData?.routes ?? [],
+ 'UtopiaRemixRootComponent routes',
+ )
+
+ const remixDerivedDataRef = useRefEditorState((store) => store.derived.remixData)
+ const projectContentsRef = useRefEditorState((store) => store.editor.projectContents)
+ const fileBlobsRef = useRefEditorState((store) => store.editor.canvas.base64Blobs)
+ const hiddenInstancesRef = useRefEditorState((store) => store.editor.hiddenInstances)
+ const displayNoneInstancesRef = useRefEditorState((store) => store.editor.displayNoneInstances)
+
+ let metadataContext: UiJsxCanvasContextData = forceNotNull(
+ `Missing UiJsxCanvasCtxAtom provider`,
+ usePubSubAtomReadOnly(UiJsxCanvasCtxAtom, AlwaysFalse),
+ )
+
+ return React.useMemo(() => {
+ if (remixDerivedDataRef.current == null) {
+ return routes
+ }
+
+ const creators = remixDerivedDataRef.current.routeModuleCreators
+
+ function addLoaderAndActionToRoutes(innerRoutes: DataRouteObject[]) {
+ innerRoutes.forEach((route) => {
+ // FIXME Adding a loader function to the 'root' route causes the `createShouldRevalidate` to fail, because
+ // we only ever pass in an empty object for the `routeModules` and never mutate it
+ const creatorForRoute = route.id === 'root' ? null : creators[route.id]
+ if (creatorForRoute != null) {
+ route.action = (args: any) =>
+ creatorForRoute
+ .executionScopeCreator(
+ projectContentsRef.current,
+ fileBlobsRef.current,
+ hiddenInstancesRef.current,
+ displayNoneInstancesRef.current,
+ metadataContext,
+ )
+ .scope['action']?.(args) ?? null
+ route.loader = (args: any) =>
+ creatorForRoute
+ .executionScopeCreator(
+ projectContentsRef.current,
+ fileBlobsRef.current,
+ hiddenInstancesRef.current,
+ displayNoneInstancesRef.current,
+ metadataContext,
+ )
+ .scope['loader']?.(args) ?? null
+ }
+
+ addLoaderAndActionToRoutes(route.children ?? [])
+ })
+ }
+
+ addLoaderAndActionToRoutes(routes)
+
+ return routes
+ }, [
+ displayNoneInstancesRef,
+ metadataContext,
+ fileBlobsRef,
+ hiddenInstancesRef,
+ projectContentsRef,
+ remixDerivedDataRef,
+ routes,
+ ])
}
export interface UtopiaRemixRootComponentProps {
@@ -85,11 +182,7 @@ export interface UtopiaRemixRootComponentProps {
export const UtopiaRemixRootComponent = React.memo((props: UtopiaRemixRootComponentProps) => {
const remixDerivedDataRef = useRefEditorState((store) => store.derived.remixData)
- const routes = useEditorState(
- Substores.derived,
- (store) => store.derived.remixData?.routes ?? [],
- 'UtopiaRemixRootComponent routes',
- )
+ const routes = useGetRoutes()
const basePath = props[UTOPIA_PATH_KEY]
diff --git a/editor/src/components/editor/store/editor-state.ts b/editor/src/components/editor/store/editor-state.ts
index ace31577a4db..3f5166c6a51c 100644
--- a/editor/src/components/editor/store/editor-state.ts
+++ b/editor/src/components/editor/store/editor-state.ts
@@ -2598,11 +2598,6 @@ export function deriveState(
const remixDerivedData = createRemixDerivedDataMemo(
editor.projectContents,
- editor.spyMetadata,
- editor.allElementProps,
- editor.canvas.base64Blobs,
- editor.hiddenInstances,
- editor.displayNoneInstances,
editor.codeResultCache.curriedRequireFn,
editor.codeResultCache.curriedResolveFn,
)
diff --git a/editor/src/components/editor/store/remix-derived-data.tsx b/editor/src/components/editor/store/remix-derived-data.tsx
index 76bec4b86752..13de2362899e 100644
--- a/editor/src/components/editor/store/remix-derived-data.tsx
+++ b/editor/src/components/editor/store/remix-derived-data.tsx
@@ -7,6 +7,7 @@ import type { MutableUtopiaCtxRefData } from '../../canvas/ui-jsx-canvas-rendere
import type { MapLike } from 'typescript'
import type { ComponentRendererComponent } from '../../canvas/ui-jsx-canvas-renderer/ui-jsx-canvas-component-renderer'
import type { DataRouteObject } from 'react-router'
+import { isProjectContentDirectory, isProjectContentFile } from '../../assets'
import type { ProjectContentTreeRoot } from '../../assets'
import type {
RouteIdsToModuleCreators,
@@ -19,10 +20,8 @@ import {
getRoutesAndModulesFromManifest,
} from '../../canvas/remix/remix-utils'
import type { CurriedUtopiaRequireFn, CurriedResolveFn } from '../../custom-code/code-file'
-import type { ElementPath } from '../../../core/shared/project-file-types'
-import type { ElementInstanceMetadataMap } from '../../../core/shared/element-template'
-import type { AllElementProps, CanvasBase64Blobs } from './editor-state'
import { memoize } from '../../../core/shared/memoize'
+import { shallowEqual } from '../../../core/shared/equality-utils'
export interface RemixRoutingTable {
[rootElementUid: string]: string /* file path */
@@ -47,13 +46,10 @@ const CreateRemixDerivedDataRefs: {
routeModulesCache: { current: {} },
}
+// Important Note: When updating the params here, you must evaluate whether the change should
+// have an effect on the memoization, and if so update paramsEqualityFn below
export function createRemixDerivedData(
projectContents: ProjectContentTreeRoot,
- spyMetadata: ElementInstanceMetadataMap,
- allElementProps: AllElementProps,
- fileBlobs: CanvasBase64Blobs,
- displayNoneInstances: Array,
- hiddenInstances: Array,
curriedRequireFn: CurriedUtopiaRequireFn,
curriedResolveFn: CurriedResolveFn,
): RemixDerivedData | null {
@@ -64,25 +60,13 @@ export function createRemixDerivedData(
const assetsManifest = createAssetsManifest(routeManifest)
- const metadataCtx = {
- current: {
- spyValues: { metadata: spyMetadata, allElementProps: allElementProps },
- },
- }
-
const routesAndModulesFromManifestResult = getRoutesAndModulesFromManifest(
routeManifest,
DefaultFutureConfig,
curriedRequireFn,
curriedResolveFn,
- metadataCtx,
projectContents,
- CreateRemixDerivedDataRefs.mutableContext,
- CreateRemixDerivedDataRefs.topLevelComponentRendererComponents,
CreateRemixDerivedDataRefs.routeModulesCache.current,
- fileBlobs,
- displayNoneInstances,
- hiddenInstances,
)
if (routesAndModulesFromManifestResult == null) {
@@ -102,8 +86,31 @@ export function createRemixDerivedData(
}
}
-export const patchedCreateRemixDerivedDataMemo = memoize(createRemixDerivedData, { maxSize: 1 })
+function isProjectContentTreeRoot(v: unknown): v is ProjectContentTreeRoot {
+ if (v != null && typeof v === 'object' && !Array.isArray(v)) {
+ const firstValue = Object.values(v)[0]
+ return isProjectContentDirectory(firstValue) || isProjectContentFile(firstValue)
+ }
+
+ return false
+}
+
+function paramsEqualityFn(l: unknown, r: unknown): boolean {
+ if (isProjectContentTreeRoot(l) && isProjectContentTreeRoot(r)) {
+ return shallowEqual(l, r)
+ }
+
+ return l === r
+}
+
+export const patchedCreateRemixDerivedDataMemo = memoize(createRemixDerivedData, {
+ maxSize: 1,
+ matchesArg: paramsEqualityFn,
+})
-export const unpatchedCreateRemixDerivedDataMemo = memoize(createRemixDerivedData, { maxSize: 1 })
+export const unpatchedCreateRemixDerivedDataMemo = memoize(createRemixDerivedData, {
+ maxSize: 1,
+ matchesArg: paramsEqualityFn,
+})
export type RemixDerivedDataFactory = typeof createRemixDerivedData