Skip to content

Commit a689345

Browse files
authored
fix(canvas) Handle Changing Imports In Canvas (#6567)
- Removed the previous handling of `shouldRerenderRef`. - Implemented `projectContentsSameForRefreshRequire`. - Changed out old `haveProjectImportsChanged` call to use the new `projectContentsSameForRefreshRequire`.
1 parent 7ee1484 commit a689345

File tree

4 files changed

+102
-21
lines changed

4 files changed

+102
-21
lines changed

editor/src/components/canvas/canvas-strategies/strategies/grid-draw-to-insert-strategy.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ const gridDrawToInsertStrategyInner =
171171
),
172172
updateHighlightedViews('mid-interaction', [targetParent]),
173173
],
174-
[targetParent],
174+
[],
175175
)
176176
}
177177

editor/src/components/canvas/canvas-utils.ts

+81-2
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,12 @@ import type {
6363
HighlightBoundsForUids,
6464
ExportsDetail,
6565
} from '../../core/shared/project-file-types'
66-
import { isExportDefault, isParseSuccess, isTextFile } from '../../core/shared/project-file-types'
66+
import {
67+
importsEquals,
68+
isExportDefault,
69+
isParseSuccess,
70+
isTextFile,
71+
} from '../../core/shared/project-file-types'
6772
import {
6873
applyUtopiaJSXComponentsChanges,
6974
getDefaultExportedTopLevelElement,
@@ -140,7 +145,11 @@ import { getStoryboardUID } from '../../core/model/scene-utils'
140145
import { optionalMap } from '../../core/shared/optional-utils'
141146
import { assertNever, fastForEach } from '../../core/shared/utils'
142147
import type { ProjectContentTreeRoot } from '../assets'
143-
import { getProjectFileByFilePath } from '../assets'
148+
import {
149+
getProjectFileByFilePath,
150+
isProjectContentDirectory,
151+
isProjectContentFile,
152+
} from '../assets'
144153
import type { CSSNumber } from '../inspector/common/css-utils'
145154
import { parseCSSLengthPercent, printCSSNumber } from '../inspector/common/css-utils'
146155
import { getTopLevelName, importedFromWhere } from '../editor/import-utils'
@@ -2180,3 +2189,73 @@ export function canvasPanelOffsets(): {
21802189
right: inspector?.clientWidth ?? 0,
21812190
}
21822191
}
2192+
2193+
export function projectContentsSameForRefreshRequire(
2194+
oldProjectContents: ProjectContentTreeRoot,
2195+
newProjectContents: ProjectContentTreeRoot,
2196+
): boolean {
2197+
if (oldProjectContents === newProjectContents) {
2198+
// Identical references, so the imports are the same.
2199+
return true
2200+
} else {
2201+
for (const [filename, oldProjectTree] of Object.entries(oldProjectContents)) {
2202+
const newProjectTree = newProjectContents[filename]
2203+
// No need to check these further if they have the same reference.
2204+
if (oldProjectTree === newProjectTree) {
2205+
continue
2206+
}
2207+
// If the file can't be found in the other tree, the imports are not the same.
2208+
if (newProjectTree == null) {
2209+
return false
2210+
}
2211+
if (isProjectContentFile(oldProjectTree) && isProjectContentFile(newProjectTree)) {
2212+
// Both entries are files.
2213+
const oldContent = oldProjectTree.content
2214+
const newContent = newProjectTree.content
2215+
if (isTextFile(oldContent) || isTextFile(newContent)) {
2216+
if (isTextFile(oldContent) && isTextFile(newContent)) {
2217+
const oldParsed = oldContent.fileContents.parsed
2218+
const newParsed = newContent.fileContents.parsed
2219+
if (isParseSuccess(oldParsed) || isParseSuccess(newParsed)) {
2220+
if (isParseSuccess(oldParsed) && isParseSuccess(newParsed)) {
2221+
if (
2222+
!importsEquals(oldParsed.imports, newParsed.imports) ||
2223+
oldParsed.combinedTopLevelArbitraryBlock !==
2224+
newParsed.combinedTopLevelArbitraryBlock ||
2225+
oldParsed.exportsDetail !== newParsed.exportsDetail
2226+
) {
2227+
// For the same file the imports, combined top
2228+
// level arbitrary block or exports have changed.
2229+
return false
2230+
}
2231+
} else {
2232+
// One of the files is a parse success but the other is not.
2233+
return false
2234+
}
2235+
}
2236+
} else {
2237+
// One of the files is a text file but the other is not.
2238+
return false
2239+
}
2240+
}
2241+
} else if (
2242+
isProjectContentDirectory(oldProjectTree) &&
2243+
isProjectContentDirectory(newProjectTree)
2244+
) {
2245+
// Both entries are directories.
2246+
if (
2247+
!projectContentsSameForRefreshRequire(oldProjectTree.children, newProjectTree.children)
2248+
) {
2249+
// The imports of the subdirectories differ.
2250+
return false
2251+
}
2252+
} else {
2253+
// One of the entries is a file and the other is a directory.
2254+
return false
2255+
}
2256+
}
2257+
}
2258+
2259+
// If nothing differs, return true.
2260+
return true
2261+
}

editor/src/components/canvas/ui-jsx-canvas-renderer/ui-jsx-canvas-code-execution.spec.browser2.tsx

+9-4
Original file line numberDiff line numberDiff line change
@@ -215,13 +215,18 @@ describe('Re-mounting is avoided when', () => {
215215

216216
await switchToLiveMode(renderResult)
217217

218+
function checkClicky(expectedContentText: string): void {
219+
const clicky = renderResult.renderedDOM.getByTestId('clicky')
220+
expect(clicky.innerText).toEqual(expectedContentText)
221+
}
222+
218223
// Ensure we can find the original text
219-
expect(renderResult.renderedDOM.queryByText('Clicked 0 times')).not.toBeNull()
224+
checkClicky('Clicked 0 times')
220225

221226
await clickButton(renderResult)
222227

223228
// Ensure it has been updated
224-
expect(renderResult.renderedDOM.queryByText('Clicked 1 times')).not.toBeNull()
229+
checkClicky('Clicked 1 times')
225230

226231
// Update the top level arbitrary JS block
227232
await updateCode(
@@ -231,7 +236,7 @@ describe('Re-mounting is avoided when', () => {
231236
)
232237

233238
// Check that it has updated without resetting the state
234-
expect(renderResult.renderedDOM.queryByText('Clicked: 1 times')).not.toBeNull()
239+
checkClicky('Clicked: 1 times')
235240

236241
// Update the component itself
237242
await updateCode(
@@ -241,7 +246,7 @@ describe('Re-mounting is avoided when', () => {
241246
)
242247

243248
// Check again that it has updated without resetting the state
244-
expect(renderResult.renderedDOM.queryByText('Clicked: 1 times!')).not.toBeNull()
249+
checkClicky('Clicked: 1 times!')
245250
})
246251

247252
it('arbitrary JS or a component is edited in a remix project', async () => {

editor/src/components/canvas/ui-jsx-canvas.tsx

+11-14
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,11 @@ import {
7575
} from './ui-jsx-canvas-renderer/ui-jsx-canvas-execution-scope'
7676
import { applyUIDMonkeyPatch } from '../../utils/canvas-react-utils'
7777
import type { RemixValidPathsGenerationContext } from './canvas-utils'
78-
import { getParseSuccessForFilePath, getValidElementPaths } from './canvas-utils'
78+
import {
79+
projectContentsSameForRefreshRequire,
80+
getParseSuccessForFilePath,
81+
getValidElementPaths,
82+
} from './canvas-utils'
7983
import { arrayEqualsByValue, fastForEach, NO_OP } from '../../core/shared/utils'
8084
import {
8185
AlwaysFalse,
@@ -360,20 +364,13 @@ export const UiJsxCanvas = React.memo<UiJsxCanvasPropsWithErrorCallback>((props)
360364

361365
useClearSpyMetadataOnRemount(props.invalidatedCanvasData, isRemounted, metadataContext)
362366

363-
const elementsToRerenderRef = React.useRef(ElementsToRerenderGLOBAL.current)
364-
const shouldRerenderRef = React.useRef(false)
365-
shouldRerenderRef.current =
366-
ElementsToRerenderGLOBAL.current === 'rerender-all-elements' ||
367-
elementsToRerenderRef.current === 'rerender-all-elements' || // TODO this means the first drag frame will still be slow, figure out a nicer way to immediately switch to true. probably this should live in a dedicated a function
368-
!arrayEqualsByValue(
369-
ElementsToRerenderGLOBAL.current,
370-
elementsToRerenderRef.current,
371-
EP.pathsEqual,
372-
) // once we get here, we know that both `ElementsToRerenderGLOBAL.current` and `elementsToRerenderRef.current` are arrays
373-
elementsToRerenderRef.current = ElementsToRerenderGLOBAL.current
374-
375367
const maybeOldProjectContents = React.useRef(projectContents)
376-
if (shouldRerenderRef.current) {
368+
369+
const projectContentsSimilarEnough = projectContentsSameForRefreshRequire(
370+
maybeOldProjectContents.current,
371+
projectContents,
372+
)
373+
if (!projectContentsSimilarEnough) {
377374
maybeOldProjectContents.current = projectContents
378375
}
379376

0 commit comments

Comments
 (0)