From 650b70609f13435f8f2a94ddcdc566afffc803c5 Mon Sep 17 00:00:00 2001 From: Balint Gabor <127662+gbalint@users.noreply.github.com> Date: Wed, 4 Sep 2024 13:16:49 +0200 Subject: [PATCH] Delete some dom walker code (#6304) **Problem:** I started from deleting the reconstructedMetadata parameter from the editorDispatchActionRunner function (mentioned here: https://github.com/concrete-utopia/utopia/pull/6240#discussion_r1740812854 ), and ended up deleting a whole bunch of old dom-walker code. Probably there is still some unnecessary code left, but I believe it was still useful to wipe some of the not running and unnecessary code. **Manual Tests:** I hereby swear that: - [x] I opened a hydrogen project and it loaded - [x] I could navigate to various routes in Preview mode --- editor/src/components/canvas/dom-walker.ts | 868 +----------------- .../canvas/editor-dispatch-flow.tsx | 47 +- .../components/canvas/ui-jsx.test-utils.tsx | 5 - .../src/components/editor/store/dispatch.tsx | 14 +- .../components/editor/store/editor-state.ts | 44 - .../src/core/model/element-metadata-utils.ts | 281 ------ editor/src/templates/editor.tsx | 4 - 7 files changed, 15 insertions(+), 1248 deletions(-) diff --git a/editor/src/components/canvas/dom-walker.ts b/editor/src/components/canvas/dom-walker.ts index b9a3e0dce3ba..a5a9a8cc3f0e 100644 --- a/editor/src/components/canvas/dom-walker.ts +++ b/editor/src/components/canvas/dom-walker.ts @@ -4,7 +4,6 @@ import * as ResizeObserverSyntheticDefault from 'resize-observer-polyfill' import * as EP from '../../core/shared/element-path' import type { DetectedLayoutSystem, - ElementInstanceMetadata, ComputedStyle, SpecialSizeMeasurements, StyleAttributeMetadata, @@ -16,9 +15,6 @@ import type { import { elementInstanceMetadata, specialSizeMeasurements, - emptySpecialSizeMeasurements, - emptyComputedStyle, - emptyAttributeMetadata, gridContainerProperties, gridElementProperties, gridAutoOrTemplateFallback, @@ -39,26 +35,12 @@ import { mapEither, } from '../../core/shared/either' import Utils from '../../utils/utils' -import type { - CanvasPoint, - CanvasRectangle, - CoordinateMarker, - InfinityRectangle, - LocalPoint, - LocalRectangle, - Rectangle, -} from '../../core/shared/math-utils' +import type { CanvasPoint, CanvasRectangle, LocalPoint } from '../../core/shared/math-utils' import { canvasPoint, - localRectangle, roundToNearestHalf, canvasRectangle, - infinityCanvasRectangle, - infinityLocalRectangle, stretchRect, - boundingRectangleArray, - infinityRectangle, - isFiniteRectangle, } from '../../core/shared/math-utils' import type { CSSNumber, CSSPosition } from '../inspector/common/css-utils' import { @@ -76,31 +58,14 @@ import { } from '../inspector/common/css-utils' import { camelCaseToDashed } from '../../core/shared/string-utils' import type { UtopiaStoreAPI } from '../editor/store/store-hook' -import { - UTOPIA_DO_NOT_TRAVERSE_KEY, - UTOPIA_PATH_KEY, - UTOPIA_SCENE_ID_KEY, - UTOPIA_VALID_PATHS, -} from '../../core/model/utopia-constants' - -import { PERFORMANCE_MARKS_ALLOWED } from '../../common/env-vars' +import { UTOPIA_SCENE_ID_KEY } from '../../core/model/utopia-constants' import { CanvasContainerID } from './canvas-types' import { emptySet } from '../../core/shared/set-utils' import type { PathWithString } from '../../core/shared/uid-utils' -import { - getDeepestPathOnDomElement, - getPathStringsOnDomElement, - getPathWithStringsOnDomElement, -} from '../../core/shared/uid-utils' -import { mapDropNulls, pluck, uniqBy } from '../../core/shared/array-utils' -import { forceNotNull, optionalMap } from '../../core/shared/optional-utils' +import { getDeepestPathOnDomElement, getPathStringsOnDomElement } from '../../core/shared/uid-utils' +import { forceNotNull } from '../../core/shared/optional-utils' import { fastForEach } from '../../core/shared/utils' -import { isFeatureEnabled } from '../../utils/feature-switches' -import type { - EditorState, - EditorStorePatched, - ElementsToRerender, -} from '../editor/store/editor-state' +import type { EditorState, EditorStorePatched } from '../editor/store/editor-state' import { shallowEqual } from '../../core/shared/equality-utils' import { pick } from '../../core/shared/object-utils' import { getFlexAlignment, getFlexJustifyContent, MaxContent } from '../inspector/inspector-common' @@ -270,25 +235,6 @@ export function getAttributesComingFromStyleSheets(element: HTMLElement): Set> = new Map() - -function getCachedAttributesComingFromStyleSheets( - invalidatedPathsForStylesheetCache: Set, - elementPath: ElementPath, - element: HTMLElement, -): Set { - const pathAsString = EP.toString(elementPath) - const invalidated = invalidatedPathsForStylesheetCache.has(pathAsString) - const inCache = AttributesFromStyleSheetsCache.has(element) - if (inCache && !invalidated) { - return AttributesFromStyleSheetsCache.get(element)! - } - invalidatedPathsForStylesheetCache.delete(pathAsString) // mutation! - const value = getAttributesComingFromStyleSheets(element) - AttributesFromStyleSheetsCache.set(element, value) - return value -} - // todo move to file export type UpdateMutableCallback = (updater: (mutableState: S) => void) => void @@ -305,16 +251,6 @@ export interface DomWalkerProps { additionalElementsToUpdate: Array } -function mergeMetadataMaps_MUTATE( - metadataToMutate: ElementInstanceMetadataMap, - otherMetadata: Readonly, -): void { - fastForEach(Object.values(otherMetadata), (elementMetadata) => { - const pathString = EP.toString(elementMetadata.elementPath) - metadataToMutate[pathString] = elementMetadata - }) -} - export interface DomWalkerMutableStateData { invalidatedPaths: Set // warning: all subtrees under each invalidated path should invalidated invalidatedPathsForStylesheetCache: Set @@ -350,235 +286,6 @@ function useDomWalkerMutableStateContext() { ) } -interface RunDomWalkerParams { - // from dom walker props - selectedViews: Array - scale: number - additionalElementsToUpdate: Array - elementsToFocusOn: ElementsToRerender - domWalkerMutableState: DomWalkerMutableStateData - rootMetadataInStateRef: { readonly current: ElementInstanceMetadataMap } -} - -interface DomWalkerInternalGlobalProps { - validPaths: Array - rootMetadataInStateRef: React.MutableRefObject - invalidatedPaths: Set - invalidatedPathsForStylesheetCache: Set - selectedViews: Array - forceInvalidated: boolean - scale: number - containerRectLazy: () => CanvasRectangle - additionalElementsToUpdate: Array - pathsCollectedMutable: Array -} - -function runSelectiveDomWalker( - elementsToFocusOn: Array, - globalProps: DomWalkerInternalGlobalProps, -): { metadata: ElementInstanceMetadataMap; cachedPaths: ElementPath[] } { - let workingMetadata: ElementInstanceMetadataMap = {} - - const canvasRootContainer = document.getElementById(CanvasContainerID) - if (canvasRootContainer != null) { - const parentPoint = canvasPoint({ x: 0, y: 0 }) - - elementsToFocusOn.forEach((path) => { - /** - * if a elementToFocusOn path points to a component instance, such as App/card-instance, the DOM will - * only contain an element with the path App/card-instance:card-root. To be able to quickly find the "rootest" element - * that belongs to a path, we use the ^= prefix search in querySelector. - * The assumption is that querySelector will return the "topmost" DOM-element with the matching prefix, - * which is the same as the "rootest" element we are looking for - */ - const foundElement = document.querySelector( - `[${UTOPIA_PATH_KEY}^="${EP.toString(path)}"]`, - ) as HTMLElement | null - - if (foundElement != null) { - const collectForElement = (element: Node) => { - if (element instanceof HTMLElement) { - const pathsWithStrings = getPathWithStringsOnDomElement(element) - if (pathsWithStrings.length == 0) { - // Keep walking until we find an element with a path - element.childNodes.forEach(collectForElement) - } else { - const foundValidPaths = pathsWithStrings.filter((pathWithString) => { - const staticPath = EP.makeLastPartOfPathStatic(pathWithString.path) - return globalProps.validPaths.some((vp) => - EP.isDescendantOfOrEqualTo(staticPath, vp), - ) - }) - globalProps.pathsCollectedMutable.push( - ...foundValidPaths.map((pathWithString) => pathWithString.path), - ) - - const { collectedMetadata } = collectAndCreateMetadataForElement( - element, - parentPoint, - path, - foundValidPaths.map((p) => p.path), - globalProps, - ) - - mergeMetadataMaps_MUTATE(workingMetadata, collectedMetadata) - } - } - } - - collectForElement(foundElement) - foundElement.childNodes.forEach(collectForElement) - } - }) - const otherElementPaths = Object.keys(globalProps.rootMetadataInStateRef.current).filter( - (path) => - Object.keys(workingMetadata).find((updatedPath) => - EP.isDescendantOfOrEqualTo(EP.fromString(path), EP.fromString(updatedPath)), - ) == null, - ) - const rootMetadataForOtherElements = pick( - otherElementPaths, - globalProps.rootMetadataInStateRef.current, - ) - mergeMetadataMaps_MUTATE(rootMetadataForOtherElements, workingMetadata) - - return { - metadata: rootMetadataForOtherElements, - cachedPaths: otherElementPaths.map(EP.fromString), - } - } - - return { - metadata: globalProps.rootMetadataInStateRef.current, - cachedPaths: Object.values(globalProps.rootMetadataInStateRef.current).map( - (p) => p.elementPath, - ), - } -} - -export interface BackfillDomMetadataResult { - updatedMetadata: ElementInstanceMetadataMap - reconstructedMetadata: ElementInstanceMetadataMap -} - -export function backfillDomMetadata( - metadata: ElementInstanceMetadataMap, -): BackfillDomMetadataResult { - // Find the paths missing from the metadata, which are ancestors of paths in the metadata which are not present in the metadata. - const missingPaths = new Set() - let parentsAndChildrenToRebuild: { [parent: string]: Array } = {} - function addPathToParentsAndChildren(pathToAdd: ElementPath): void { - const parentPath = EP.parentPath(pathToAdd) - - const parentPathString = EP.toString(parentPath) - // Build up parentsAndChildrenToRebuild. - let pathsToFillChildren: Array - if (parentPathString in parentsAndChildrenToRebuild) { - pathsToFillChildren = parentsAndChildrenToRebuild[parentPathString] - } else { - pathsToFillChildren = [] - parentsAndChildrenToRebuild[parentPathString] = pathsToFillChildren - } - pathsToFillChildren.push(EP.toString(pathToAdd)) - } - function fillPath(pathToFill: ElementPath): void { - addPathToParentsAndChildren(pathToFill) - if (!EP.isEmptyPath(pathToFill)) { - const pathStringToFill = EP.toString(pathToFill) - if (!missingPaths.has(pathStringToFill) && !(pathStringToFill in metadata)) { - missingPaths.add(pathStringToFill) - const parentPath = EP.parentPath(pathToFill) - if (parentPath != null) { - fillPath(parentPath) - } - } - } - } - for (const elementMetadata of Object.values(metadata)) { - const metadataPath = elementMetadata.elementPath - addPathToParentsAndChildren(metadataPath) - fillPath(EP.parentPath(metadataPath)) - } - - // Work from the bottommost up the paths to the topmost, filling in the metadata as we go. - let updatedMetadata: ElementInstanceMetadataMap = { ...metadata } - const pathsToFill = Array.from(missingPaths).sort().reverse() - for (const pathToFill of pathsToFill) { - const pathToFillPath = EP.fromString(pathToFill) - const childPaths = parentsAndChildrenToRebuild[pathToFill] ?? [] - const children = mapDropNulls((childPath) => updatedMetadata[childPath], childPaths) - - function getBoundingFrameFromChildren( - childrenFrames: Array | InfinityRectangle>, - ) { - const childrenNonInfinityFrames = childrenFrames.filter(isFiniteRectangle) - const childrenBoundingFrame = - childrenNonInfinityFrames.length === childrenFrames.length - ? boundingRectangleArray(childrenNonInfinityFrames) - : (infinityRectangle as InfinityRectangle) - - return childrenBoundingFrame - } - - const childrenBoundingGlobalFrame = getBoundingFrameFromChildren( - mapDropNulls((c) => c.globalFrame, children), - ) - - const childrenBoundingGlobalFrameWithTextContent = getBoundingFrameFromChildren( - mapDropNulls((c) => c.specialSizeMeasurements.globalFrameWithTextContent, children), - ) - - // Insert a default entry in for this. - if (!(pathToFill in updatedMetadata)) { - updatedMetadata[pathToFill] = elementInstanceMetadata( - pathToFillPath, - left('unknown'), - null, - null, - false, - false, - emptySpecialSizeMeasurements, - emptyComputedStyle, - emptyAttributeMetadata, - null, - null, - 'not-a-conditional', - null, - null, - null, - ) - } - - // Update the frames. - const currentMetadata = updatedMetadata[pathToFill] - updatedMetadata[pathToFill] = { - ...currentMetadata, - globalFrame: childrenBoundingGlobalFrame, - nonRoundedGlobalFrame: childrenBoundingGlobalFrame, - specialSizeMeasurements: { - ...currentMetadata.specialSizeMeasurements, - globalFrameWithTextContent: childrenBoundingGlobalFrameWithTextContent, - }, - } - } - - let updatedMetadataToReturn: ElementInstanceMetadataMap = {} - let reconstructedMetadata: ElementInstanceMetadataMap = {} - for (const [metadataKey, metadataValue] of Object.entries(updatedMetadata)) { - // Either updating existing values or squirrelling away the entries in the reconstructed metadata. - if (metadataKey in metadata) { - updatedMetadataToReturn[metadataKey] = metadataValue - } else { - reconstructedMetadata[metadataKey] = metadataValue - } - } - - return { - updatedMetadata: updatedMetadataToReturn, - reconstructedMetadata: reconstructedMetadata, - } -} - export function resubscribeObservers(domWalkerMutableState: { mutationObserver: MutationObserver resizeObserver: ResizeObserver @@ -597,114 +304,6 @@ export function resubscribeObservers(domWalkerMutableState: { } } -// Dom walker has 3 modes for performance reasons: -// Fastest is the selective mode, this runs when elementsToFocusOn is not 'rerender-all-elements'. In this case it only collects the metadata of the elements in elementsToFocusOn -// Middle speed is when initComplete is true, in this case it traverses the full dom but only collects the metadata for the not invalidated elements (stored in invalidatedPaths) -// Slowest is the full run, when elementsToFocusOn is 'rerender-all-elements' and initComplete is false -export function runDomWalker({ - domWalkerMutableState, - selectedViews, - elementsToFocusOn, - scale, - additionalElementsToUpdate, - rootMetadataInStateRef, -}: RunDomWalkerParams): { - metadata: ElementInstanceMetadataMap - reconstructedMetadata: ElementInstanceMetadataMap - cachedPaths: ElementPath[] - invalidatedPaths: string[] -} | null { - const needsWalk = - !domWalkerMutableState.initComplete || domWalkerMutableState.invalidatedPaths.size > 0 - - if (!needsWalk) { - return null // early return to save performance - } - - const LogDomWalkerPerformance = - (isFeatureEnabled('Debug – Performance Marks (Fast)') || - isFeatureEnabled('Debug – Performance Marks (Slow)')) && - PERFORMANCE_MARKS_ALLOWED - - const canvasRootContainer = document.getElementById(CanvasContainerID) - - if (canvasRootContainer != null) { - if (LogDomWalkerPerformance) { - performance.mark('DOM_WALKER_START') - } - - const invalidatedPaths = Array.from(domWalkerMutableState.invalidatedPaths) - - resubscribeObservers(domWalkerMutableState) - - // getCanvasRectangleFromElement is costly, so I made it lazy. we only need the value inside globalFrameForElement - const containerRect = lazyValue(() => { - return getCanvasRectangleFromElement( - canvasRootContainer, - scale, - 'without-text-content', - 'nearest-half', - ) - }) - - const validPaths: Array | null = optionalMap( - (paths) => paths.split(' ').map(EP.fromString), - canvasRootContainer.getAttribute(UTOPIA_VALID_PATHS), - ) - - if (validPaths == null) { - throw new Error( - 'Utopia Internal Error: Running DOM-walker without canvasRootPath or validRootPaths', - ) - } - - const globalProps: DomWalkerInternalGlobalProps = { - validPaths: validPaths, - rootMetadataInStateRef: rootMetadataInStateRef, - invalidatedPaths: domWalkerMutableState.invalidatedPaths, - invalidatedPathsForStylesheetCache: domWalkerMutableState.invalidatedPathsForStylesheetCache, - selectedViews: selectedViews, - forceInvalidated: !domWalkerMutableState.initComplete, // TODO do we run walkCanvasRootFragment with initComplete=true anymore? // TODO _should_ we ever run walkCanvasRootFragment with initComplete=false EVER, or instead can we set the canvas root as the invalidated path? - scale: scale, - containerRectLazy: containerRect, - additionalElementsToUpdate: [...additionalElementsToUpdate, ...selectedViews], - pathsCollectedMutable: [], - } - - // This assumes that the canvas root is rendering a Storyboard fragment. - // The necessary validPaths and the root fragment's template path comes from props, - // because the fragment is invisible in the DOM. - const { metadata, cachedPaths } = - // when we don't rerender all elements we just run the dom walker in selective mode: only update the metatdata - // of the currently rendered elements (for performance reasons) - elementsToFocusOn === 'rerender-all-elements' - ? walkCanvasRootFragment(canvasRootContainer, globalProps) - : runSelectiveDomWalker(elementsToFocusOn, globalProps) - - if (LogDomWalkerPerformance) { - performance.mark('DOM_WALKER_END') - performance.measure( - `DOM WALKER - cached paths: [${cachedPaths.map(EP.toString).join(', ')}]`, - 'DOM_WALKER_START', - 'DOM_WALKER_END', - ) - } - domWalkerMutableState.initComplete = true // Mutation! - - const { updatedMetadata, reconstructedMetadata } = backfillDomMetadata(metadata) - - return { - metadata: updatedMetadata, - reconstructedMetadata: reconstructedMetadata, - cachedPaths: cachedPaths, - invalidatedPaths: invalidatedPaths, - } - } else { - // TODO flip if-else - return null - } -} - function selectCanvasInteractionHappening(store: EditorStorePatched): boolean { const interactionSessionActive = store.editor.canvas.interactionSession != null return interactionSessionActive @@ -872,145 +471,6 @@ function collectMetadataForElement( } } -function isAnyPathInvalidated( - stringPathsForElement: Array, - invalidatedPaths: ReadonlySet, -): boolean { - return invalidatedPaths.size > 0 && stringPathsForElement.some((p) => invalidatedPaths.has(p)) -} - -function collectMetadata( - element: HTMLElement, - originalElementPaths: Array, - pathsForElement: Array, - stringPathsForElement: Array, - parentPoint: CanvasPoint, - closestOffsetParentPath: ElementPath, - allUnfilteredChildrenPaths: Array, - invalidated: boolean, // TODO rename invalidated from globalProps? - globalProps: DomWalkerInternalGlobalProps, -): { - collectedMetadata: ElementInstanceMetadataMap - cachedPaths: Array - collectedPaths: Array -} { - // Determine if the parent has been visited so far. - // When a child element of another element is found, we want to collect the metadata for it if - // the DOM walker hasn't visited its parent (by element path). As it still forms part of the parent's - // frame, so we want to include it. - const parentVisited = originalElementPaths.some((originalElementPath) => { - const parentPath = EP.parentPath(originalElementPath.path) - return EP.containsPath(parentPath, globalProps.pathsCollectedMutable) - }) - - if (pathsForElement.length === 0 && parentVisited) { - return { - collectedMetadata: {}, - cachedPaths: [], - collectedPaths: [], - } - } - const shouldCollect = - !parentVisited || - invalidated || - isAnyPathInvalidated(stringPathsForElement, globalProps.invalidatedPaths) || - pathsForElement.some((pathForElement) => { - return globalProps.additionalElementsToUpdate.some((additionalElementToUpdate) => - EP.pathsEqual(pathForElement, additionalElementToUpdate), - ) - }) - if (shouldCollect) { - return collectAndCreateMetadataForElement( - element, - parentPoint, - closestOffsetParentPath, - parentVisited ? pathsForElement : originalElementPaths.map((path) => path.path), - globalProps, - ) - } else { - const cachedMetadata = pick(stringPathsForElement, globalProps.rootMetadataInStateRef.current) - - if (Object.keys(cachedMetadata).length === pathsForElement.length) { - return { - collectedMetadata: cachedMetadata, - cachedPaths: pathsForElement, - collectedPaths: pathsForElement, - } - } else { - // If any path is missing cached metadata we must forcibly invalidate the element - return collectMetadata( - element, - originalElementPaths, - pathsForElement, - stringPathsForElement, - parentPoint, - closestOffsetParentPath, - allUnfilteredChildrenPaths, - true, // forcing the invalidation - globalProps, - ) - } - } -} - -function collectAndCreateMetadataForElement( - element: HTMLElement, - parentPoint: CanvasPoint, - closestOffsetParentPath: ElementPath, - pathsForElement: ElementPath[], - globalProps: DomWalkerInternalGlobalProps, -) { - const { - tagName, - globalFrame, - nonRoundedGlobalFrame, - specialSizeMeasurementsObject, - textContentsMaybe, - } = collectMetadataForElement( - element, - closestOffsetParentPath, - globalProps.scale, - globalProps.containerRectLazy, - ) - - const { computedStyle, attributeMetadata } = getComputedStyle( - element, - pathsForElement, - globalProps.invalidatedPathsForStylesheetCache, - globalProps.selectedViews, - ) - - const collectedMetadata: ElementInstanceMetadataMap = {} - pathsForElement.forEach((path) => { - const pathStr = EP.toString(path) - globalProps.invalidatedPaths.delete(pathStr) // global mutation! - - collectedMetadata[pathStr] = elementInstanceMetadata( - path, - left(tagName), - globalFrame, - nonRoundedGlobalFrame, - false, - false, - specialSizeMeasurementsObject, - computedStyle, - attributeMetadata, - null, - null, - 'not-a-conditional', - textContentsMaybe, - null, - null, - ) - }) - - return { - collectedMetadata: collectedMetadata, - cachedPaths: [], - collectedPaths: pathsForElement, - } -} - export function collectDomElementMetadataForElement( element: HTMLElement, scale: number, @@ -1043,55 +503,6 @@ export function collectDomElementMetadataForElement( ) } -function getComputedStyle( - element: HTMLElement, - paths: Array, - invalidatedPathsForStylesheetCache: Set, - selectedViews: Array, -): { - computedStyle: ComputedStyle | null - attributeMetadata: StyleAttributeMetadata | null -} { - const isSelectedOnAnyPaths = selectedViews.some((sv) => - paths.some((path) => EP.pathsEqual(sv, path)), - ) - if (!isSelectedOnAnyPaths) { - // the element is not among the selected views, skip computing the style - return { - computedStyle: null, - attributeMetadata: null, - } - } - const elementStyle = window.getComputedStyle(element) - const attributesSetByStylesheet = getCachedAttributesComingFromStyleSheets( - invalidatedPathsForStylesheetCache, - paths[0], // TODO is this sufficient to use the first path element for caching? - element, - ) - let computedStyle: ComputedStyle = {} - let attributeMetadata: StyleAttributeMetadata = {} - if (elementStyle != null) { - computedStyleKeys.forEach((key) => { - // Accessing the value directly often doesn't work, and using `getPropertyValue` requires - // using dashed case rather than camel case - const caseCorrectedKey = camelCaseToDashed(key) - const propertyValue = elementStyle.getPropertyValue(caseCorrectedKey) - if (propertyValue != '') { - computedStyle[key] = propertyValue - const isSetFromStyleSheet = attributesSetByStylesheet.has(key) - if (isSetFromStyleSheet) { - attributeMetadata[key] = { fromStyleSheet: true } - } - } - }) - } - - return { - computedStyle: computedStyle, - attributeMetadata: attributeMetadata, - } -} - function getGridContainerProperties( elementStyle: CSSStyleDeclaration | null, ): GridContainerProperties { @@ -1506,275 +917,6 @@ function globalFrameForElement( ) } -function walkCanvasRootFragment( - canvasRoot: HTMLElement, - globalProps: DomWalkerInternalGlobalProps, -): { - metadata: ElementInstanceMetadataMap - cachedPaths: Array -} { - const canvasRootPath: ElementPath | null = optionalMap( - EP.fromString, - canvasRoot.getAttribute('data-utopia-root-element-path'), - ) - - if (canvasRootPath == null) { - throw new Error('Utopia Internal Error: Running DOM-walker without canvasRootPath') - } - - globalProps.invalidatedPaths.delete(EP.toString(canvasRootPath)) // global mutation! - globalProps.pathsCollectedMutable.push(canvasRootPath) - - if ( - ObserversAvailable && - globalProps.invalidatedPaths.size === 0 && - Object.keys(globalProps.rootMetadataInStateRef.current).length > 0 && - globalProps.additionalElementsToUpdate.length === 0 && - !globalProps.forceInvalidated - ) { - // no mutation happened on the entire canvas, just return the old metadata - return { - metadata: globalProps.rootMetadataInStateRef.current, - cachedPaths: [canvasRootPath], - } - } else { - const { rootMetadata, cachedPaths } = walkSceneInner(canvasRoot, canvasRootPath, globalProps) - // The Storyboard root being a fragment means it is invisible to us in the DOM walker, - // so walkCanvasRootFragment will create a fake root ElementInstanceMetadata - // to provide a home for the the (really existing) childMetadata - const metadata: ElementInstanceMetadata = elementInstanceMetadata( - canvasRootPath, - left('Storyboard'), - infinityCanvasRectangle, - infinityCanvasRectangle, - false, - false, - emptySpecialSizeMeasurements, - emptyComputedStyle, - emptyAttributeMetadata, - null, - null, // this comes from the Spy Wrapper - 'not-a-conditional', - null, - null, - null, - ) - - rootMetadata[EP.toString(canvasRootPath)] = metadata - - return { metadata: rootMetadata, cachedPaths: cachedPaths } - } -} - -function walkScene( - scene: HTMLElement, - globalProps: DomWalkerInternalGlobalProps, -): { - metadata: ElementInstanceMetadataMap - cachedPaths: Array -} { - if (scene instanceof HTMLElement) { - // Right now this assumes that only UtopiaJSXComponents can be rendered via scenes, - // and that they can only have a single root element - const sceneIndexAttr = scene.attributes.getNamedItemNS(null, UTOPIA_SCENE_ID_KEY) - const sceneID = sceneIndexAttr?.value ?? null - const instancePath = sceneID == null ? null : EP.fromString(sceneID) - - if (sceneID != null && instancePath != null && EP.isElementPath(instancePath)) { - const invalidatedScene = - globalProps.forceInvalidated || - (ObserversAvailable && - globalProps.invalidatedPaths.size > 0 && - globalProps.invalidatedPaths.has(sceneID)) - - globalProps.invalidatedPaths.delete(sceneID) // global mutation! - - const { - childPaths: rootElements, - rootMetadata, - cachedPaths, - } = walkSceneInner(scene, instancePath, { - ...globalProps, - forceInvalidated: invalidatedScene, - }) - globalProps.pathsCollectedMutable.push(instancePath) - - const { collectedMetadata: sceneMetadata, cachedPaths: sceneCachedPaths } = collectMetadata( - scene, - [{ path: instancePath, asString: sceneID }], - [instancePath], - [sceneID], - canvasPoint({ x: 0, y: 0 }), - instancePath, - rootElements, - invalidatedScene, - globalProps, - ) - - mergeMetadataMaps_MUTATE(rootMetadata, sceneMetadata) - - return { - metadata: rootMetadata, - cachedPaths: [...cachedPaths, ...sceneCachedPaths], - } - } - } - return { metadata: {}, cachedPaths: [] } // verify -} - -function walkSceneInner( - scene: HTMLElement, - closestOffsetParentPath: ElementPath, - globalProps: DomWalkerInternalGlobalProps, -): { - childPaths: Array - rootMetadata: ElementInstanceMetadataMap - cachedPaths: Array -} { - const globalFrame: CanvasRectangle = globalFrameForElement( - scene, - globalProps.scale, - globalProps.containerRectLazy, - 'without-text-content', - 'nearest-half', - ) - - let childPaths: Array = [] - let rootMetadataAccumulator: ElementInstanceMetadataMap = {} - let cachedPathsAccumulator: Array = [] - - scene.childNodes.forEach((childNode) => { - const { - childPaths: childNodePaths, - rootMetadata, - cachedPaths, - } = walkElements(childNode, globalFrame, closestOffsetParentPath, globalProps) - - childPaths.push(...childNodePaths) - mergeMetadataMaps_MUTATE(rootMetadataAccumulator, rootMetadata) - cachedPathsAccumulator.push(...cachedPaths) - }) - - return { - childPaths: childPaths, - rootMetadata: rootMetadataAccumulator, - cachedPaths: cachedPathsAccumulator, - } -} - -// Walks through the DOM producing the structure and values from within. -function walkElements( - element: Node, - parentPoint: CanvasPoint, - closestOffsetParentPath: ElementPath, - globalProps: DomWalkerInternalGlobalProps, -): { - childPaths: ReadonlyArray - rootMetadata: ElementInstanceMetadataMap - cachedPaths: Array -} { - if (isScene(element)) { - // we found a nested scene, restart the walk - const { metadata, cachedPaths: cachedPaths } = walkScene(element, globalProps) - - const result = { - childPaths: [], - rootMetadata: metadata, - cachedPaths: cachedPaths, - } - return result - } - if (element instanceof HTMLElement) { - let closestOffsetParentPathInner: ElementPath = closestOffsetParentPath - // If this element provides bounds for absolute children, we want to update the closest offset parent path - if (isElementAContainingBlockForAbsolute(window.getComputedStyle(element))) { - const deepestPath = getDeepestPathOnDomElement(element) - if (deepestPath != null) { - closestOffsetParentPathInner = deepestPath - } - } - - let invalidatedElement = false - - const pathsWithStrings = getPathWithStringsOnDomElement(element) - for (const pathWithString of pathsWithStrings) { - invalidatedElement = - globalProps.forceInvalidated || - (ObserversAvailable && - globalProps.invalidatedPaths.size > 0 && - globalProps.invalidatedPaths.has(pathWithString.asString)) - - globalProps.invalidatedPaths.delete(pathWithString.asString) // global mutation! - } - - const doNotTraverseAttribute = getDOMAttribute(element, UTOPIA_DO_NOT_TRAVERSE_KEY) - const traverseChildren: boolean = doNotTraverseAttribute !== 'true' - - const globalFrame = globalFrameForElement( - element, - globalProps.scale, - globalProps.containerRectLazy, - 'without-text-content', - 'nearest-half', - ) - - // Check this is a path we're interested in, otherwise skip straight to the children - const foundValidPaths = pathsWithStrings.filter((pathWithString) => { - const staticPath = EP.makeLastPartOfPathStatic(pathWithString.path) - return globalProps.validPaths.some((validPath) => { - return EP.pathsEqual(staticPath, validPath) - }) - }) - globalProps.pathsCollectedMutable.push( - ...foundValidPaths.map((pathWithString) => pathWithString.path), - ) - - // Build the metadata for the children of this DOM node. - let childPaths: Array = [] - let rootMetadataAccumulator: ElementInstanceMetadataMap = {} - let cachedPathsAccumulator: Array = [] - // TODO: we should not traverse the children when all elements of this subtree will be retrieved from cache anyway - // WARNING: we need to retrieve the metadata of all elements of the subtree from the cache, because the SAVE_DOM_REPORT - // action replaces (and not merges) the full metadata map - if (traverseChildren) { - element.childNodes.forEach((child) => { - const { - childPaths: childNodePaths, - rootMetadata: rootMetadataInner, - cachedPaths, - } = walkElements(child, globalFrame, closestOffsetParentPathInner, globalProps) - childPaths.push(...childNodePaths) - mergeMetadataMaps_MUTATE(rootMetadataAccumulator, rootMetadataInner) - cachedPathsAccumulator.push(...cachedPaths) - }) - } - - const uniqueChildPaths = uniqBy(childPaths, EP.pathsEqual) - - const { collectedMetadata, cachedPaths, collectedPaths } = collectMetadata( - element, - pathsWithStrings, - pluck(foundValidPaths, 'path'), - pluck(foundValidPaths, 'asString'), - parentPoint, - closestOffsetParentPath, - uniqueChildPaths, - invalidatedElement, - globalProps, - ) - - mergeMetadataMaps_MUTATE(rootMetadataAccumulator, collectedMetadata) - cachedPathsAccumulator = [...cachedPathsAccumulator, ...cachedPaths] - return { - rootMetadata: rootMetadataAccumulator, - childPaths: collectedPaths, - cachedPaths: cachedPathsAccumulator, - } - } else { - return { childPaths: [], rootMetadata: {}, cachedPaths: [] } - } -} - function getClosestOffsetParent(element: HTMLElement): Element | null { let currentElement: HTMLElement | null = element diff --git a/editor/src/components/canvas/editor-dispatch-flow.tsx b/editor/src/components/canvas/editor-dispatch-flow.tsx index c8598f963d24..f74e035eaa3c 100644 --- a/editor/src/components/canvas/editor-dispatch-flow.tsx +++ b/editor/src/components/canvas/editor-dispatch-flow.tsx @@ -1,11 +1,10 @@ import type { EditorDispatch } from '../editor/action-types' -import { saveDOMReport, updateMetadataInEditorState } from '../editor/actions/action-creators' +import { updateMetadataInEditorState } from '../editor/actions/action-creators' import type { DispatchResult } from '../editor/store/dispatch' import { editorDispatchActionRunner } from '../editor/store/dispatch' -import type { EditorStoreFull, ElementsToRerender } from '../editor/store/editor-state' +import type { EditorStoreFull } from '../editor/store/editor-state' import { runDomSampler } from './dom-sampler' -import type { DomWalkerMutableStateData } from './dom-walker' -import { resubscribeObservers, runDomWalker } from './dom-walker' +import { resubscribeObservers } from './dom-walker' import { ElementsToRerenderGLOBAL, type UiJsxCanvasContextData } from './ui-jsx-canvas' export function carryDispatchResultFields( @@ -24,45 +23,6 @@ export function carryDispatchResultFields( } } -export function runDomWalkerAndSaveResults( - dispatch: EditorDispatch, - domWalkerMutableState: DomWalkerMutableStateData, - storedState: EditorStoreFull, - spyCollector: UiJsxCanvasContextData, - elementsToFocusOn: ElementsToRerender, -): DispatchResult | null { - const domWalkerResult = runDomWalker({ - domWalkerMutableState: domWalkerMutableState, - selectedViews: storedState.patchedEditor.selectedViews, - elementsToFocusOn: elementsToFocusOn, - scale: storedState.patchedEditor.canvas.scale, - additionalElementsToUpdate: - storedState.patchedEditor.canvas.domWalkerAdditionalElementsToUpdate, - rootMetadataInStateRef: { - current: storedState.patchedEditor.domMetadata, - }, - }) - - if (domWalkerResult == null) { - return null - } - - const dispatchResultWithMetadata = editorDispatchActionRunner( - dispatch, - [ - saveDOMReport( - domWalkerResult.metadata, - domWalkerResult.cachedPaths, - domWalkerResult.invalidatedPaths, - ), - ], - storedState, - spyCollector, - domWalkerResult.reconstructedMetadata, - ) - return dispatchResultWithMetadata -} - export function runDomSamplerAndSaveResults( boundDispatch: EditorDispatch, storedState: EditorStoreFull, @@ -92,7 +52,6 @@ export function runDomSamplerAndSaveResults( [updateMetadataInEditorState(metadataResult.metadata, metadataResult.tree)], storedStateWithNewMetadata, spyCollector, - {}, ) resubscribeObservers(domWalkerMutableState) diff --git a/editor/src/components/canvas/ui-jsx.test-utils.tsx b/editor/src/components/canvas/ui-jsx.test-utils.tsx index b25fe07b6245..88401c39ff6e 100644 --- a/editor/src/components/canvas/ui-jsx.test-utils.tsx +++ b/editor/src/components/canvas/ui-jsx.test-utils.tsx @@ -132,7 +132,6 @@ import { createDomWalkerMutableState, invalidateDomWalkerIfNecessary, resubscribeObservers, - runDomWalker, } from './dom-walker' import { flushSync } from 'react-dom' import { shouldUpdateLowPriorityUI } from '../inspector/inspector' @@ -359,7 +358,6 @@ export async function renderTestEditorWithModel( actions, workingEditorState, spyCollector, - {}, innerStrategiesToUse, ) @@ -457,7 +455,6 @@ export async function renderTestEditorWithModel( [saveDomReportAction], workingEditorState, spyCollector, - {}, // TODO delete before merge ) workingEditorState = carryDispatchResultFields(workingEditorState, editorWithNewMetadata) } @@ -473,7 +470,6 @@ export async function renderTestEditorWithModel( [{ action: 'TRUE_UP_ELEMENTS' }], workingEditorState, spyCollector, - {}, ) workingEditorState = carryDispatchResultFields( workingEditorState, @@ -524,7 +520,6 @@ export async function renderTestEditorWithModel( [saveDomReportAction], workingEditorState, spyCollector, - {}, ) workingEditorState = carryDispatchResultFields( workingEditorState, diff --git a/editor/src/components/editor/store/dispatch.tsx b/editor/src/components/editor/store/dispatch.tsx index 0ac7b4abb91a..5c7295c5ed17 100644 --- a/editor/src/components/editor/store/dispatch.tsx +++ b/editor/src/components/editor/store/dispatch.tsx @@ -7,7 +7,7 @@ import { optionalDeepFreeze } from '../../../utils/deep-freeze' import type { CanvasAction } from '../../canvas/canvas-types' import type { LocalNavigatorAction } from '../../navigator/actions' import { PreviewIframeId, projectContentsUpdateMessage } from '../../preview/preview-pane' -import type { EditorAction, EditorDispatch } from '../action-types' +import type { EditorAction, EditorDispatch, UpdateMetadataInEditorState } from '../action-types' import { isLoggedIn } from '../action-types' import { isTransientAction, @@ -30,7 +30,6 @@ import type { import { deriveState, persistentModelFromEditorModel, - reconstructJSXMetadata, storedEditorStateFromEditorState, } from './editor-state' import { @@ -466,7 +465,6 @@ export function editorDispatchActionRunner( dispatchedActions: readonly EditorAction[], storedState: EditorStoreFull, spyCollector: UiJsxCanvasContextData, - domReconstructedMetadata: ElementInstanceMetadataMap, strategiesToUse: Array = RegisteredCanvasStrategies, // only override this for tests ): DispatchResult { const actionGroupsToProcess = dispatchedActions.reduce(reducerToSplitToActionGroups, [[]]) @@ -479,7 +477,6 @@ export function editorDispatchActionRunner( working, spyCollector, strategiesToUse, - domReconstructedMetadata, ) return newStore }, @@ -837,7 +834,6 @@ function editorDispatchInner( storedState: DispatchResult, spyCollector: UiJsxCanvasContextData, strategiesToUse: Array, - domReconstructedMetadata: ElementInstanceMetadataMap, ): DispatchResult { // console.log('DISPATCH', simpleStringifyActions(dispatchedActions), dispatchedActions) @@ -880,7 +876,8 @@ function editorDispatchInner( result.unpatchedEditor.currentVariablesInScope const updateMetadataInEditorStateAction = dispatchedActions.find( - (action) => action.action === 'UPDATE_METADATA_IN_EDITOR_STATE', + (action): action is UpdateMetadataInEditorState => + action.action === 'UPDATE_METADATA_IN_EDITOR_STATE', ) const metadataChanged = domMetadataChanged || @@ -897,7 +894,10 @@ function editorDispatchInner( elementPathTree: updateMetadataInEditorStateAction.tree, } } else { - return reconstructJSXMetadata(result.unpatchedEditor, domReconstructedMetadata) + return { + metadata: { ...result.unpatchedEditor.jsxMetadata }, + elementPathTree: result.unpatchedEditor.elementPathTree, + } } } const { metadata, elementPathTree } = getMetadataSomehow() diff --git a/editor/src/components/editor/store/editor-state.ts b/editor/src/components/editor/store/editor-state.ts index 16132e4ef4c6..b874154f03d2 100644 --- a/editor/src/components/editor/store/editor-state.ts +++ b/editor/src/components/editor/store/editor-state.ts @@ -3400,50 +3400,6 @@ export function parseFailureAsErrorMessages( } } -export function reconstructJSXMetadata( - editor: EditorState, - domReconstructedMetadata: ElementInstanceMetadataMap, -): { - metadata: ElementInstanceMetadataMap - elementPathTree: ElementPathTrees -} { - const uiFile = getOpenUIJSFile(editor) - if (uiFile == null) { - return { - metadata: editor.jsxMetadata, - elementPathTree: editor.elementPathTree, - } - } else { - return foldParsedTextFile( - (_) => { - return { - metadata: editor.jsxMetadata, - elementPathTree: editor.elementPathTree, - } - }, - (success) => { - const elementsByUID = getElementsByUIDFromTopLevelElements(success.topLevelElements) - const { mergedMetadata, elementPathTree } = MetadataUtils.mergeComponentMetadata( - elementsByUID, - editor.spyMetadata, - editor.domMetadata, - domReconstructedMetadata, - ) - return { - metadata: ElementInstanceMetadataMapKeepDeepEquality(editor.jsxMetadata, mergedMetadata) - .value, - elementPathTree: elementPathTree, - } - }, - (_) => ({ - metadata: editor.jsxMetadata, - elementPathTree: editor.elementPathTree, - }), - uiFile.fileContents.parsed, - ) - } -} - export function getStoryboardElementPathFromEditorState( editor: EditorState, ): StaticElementPath | null { diff --git a/editor/src/core/model/element-metadata-utils.ts b/editor/src/core/model/element-metadata-utils.ts index ef4bc8fdd9c9..12da55cd97cc 100644 --- a/editor/src/core/model/element-metadata-utils.ts +++ b/editor/src/core/model/element-metadata-utils.ts @@ -1835,91 +1835,6 @@ export const MetadataUtils = { getDuplicationParentTargets(targets: ElementPath[]): ElementPath | null { return EP.getCommonParent(targets) }, - mergeComponentMetadata( - elementsByUID: ElementsByUID, - fromSpy: ElementInstanceMetadataMap, - fromDOM: ElementInstanceMetadataMap, - fromDOMReconstructed: ElementInstanceMetadataMap, - ): { mergedMetadata: ElementInstanceMetadataMap; elementPathTree: ElementPathTrees } { - // This logic effectively puts everything from the spy first, - // then anything missed out from the DOM right after it. - // Ideally this would function like a VCS diff inserting runs of new elements - // inbetween matching metadata, so it may be necessary to implement something - // like that in the future. But for now this is likely "good enough" that it - // wont make any difference. - let workingElements: ElementInstanceMetadataMap = { ...fromSpy } - let newlyFoundElements: Array = [] - fastForEach(Object.keys(fromDOM), (pathStr) => { - const domElem = fromDOM[pathStr] - const spyElem = fromSpy[pathStr] - - if (spyElem == null) { - workingElements[pathStr] = domElem - newlyFoundElements.push(domElem.elementPath) - } else { - let componentInstance = spyElem.componentInstance || domElem.componentInstance - let jsxElement = alternativeEither(spyElem.element, domElem.element) - - const elemUID: string | null = EP.toStaticUid(domElem.elementPath) - const possibleElement = elementsByUID[elemUID] - if (possibleElement != null) { - if (!isIntrinsicElement(possibleElement.name)) { - componentInstance = true - jsxElement = right(possibleElement) - } - } - - const elem: ElementInstanceMetadata = { - ...domElem, - element: jsxElement, - componentInstance: componentInstance, - isEmotionOrStyledComponent: spyElem.isEmotionOrStyledComponent, - label: spyElem.label, - importInfo: spyElem.importInfo, - assignedToProp: spyElem.assignedToProp, - } - workingElements[EP.toString(domElem.elementPath)] = elem - } - }) - - const spyOnlyElements = fillSpyOnlyMetadata(fromSpy, fromDOM, fromDOMReconstructed) - - workingElements = { - ...workingElements, - ...spyOnlyElements, - } - - const elementsInheritingFromAncestors = fillMissingDataFromAncestors(workingElements) - const mergedMetadata: ElementInstanceMetadataMap = { - ...workingElements, - ...elementsInheritingFromAncestors, - } - - // This should exclude entries which have been incorporated into the DOM metadata - // as a result of those elements being children of visited DOM elements but that - // did not have their parents (by element path) visited. - let trimmedMetadata: ElementInstanceMetadataMap = {} - for (const elementMetadata of Object.values(mergedMetadata)) { - const ancestorPaths = EP.getAncestors(elementMetadata.elementPath).filter( - (path) => EP.fullDepth(path) > 2, - ) - const allAncestorsExist = ancestorPaths.every((path) => EP.toString(path) in mergedMetadata) - if (allAncestorsExist || EP.fullDepth(elementMetadata.elementPath) <= 2) { - trimmedMetadata[EP.toString(elementMetadata.elementPath)] = elementMetadata - } - // In the above code we're using the fullDepth to effectively not bother checking things like - // storyboards and scenes as those will always be included. - } - - // Note: This will not necessarily be representative of the structured ordering in - // the code that produced these elements. - const pathTree = MetadataUtils.createElementPathTreeFromMetadata(trimmedMetadata) - - return { - mergedMetadata: trimmedMetadata, - elementPathTree: pathTree, - } - }, createElementPathTreeFromMetadata(metadata: ElementInstanceMetadataMap): ElementPathTrees { return buildTree(metadata) }, @@ -2628,202 +2543,6 @@ function getNonExpressionDescendantsInner( return [element] } -function fillSpyOnlyMetadata( - fromSpy: ElementInstanceMetadataMap, - fromDOM: ElementInstanceMetadataMap, - fromReconstructedDOM: ElementInstanceMetadataMap, -): ElementInstanceMetadataMap { - const childrenInDomCache: { [pathStr: string]: Array } = {} - - const findChildrenInDomRecursively = (pathStr: string): Array => { - const existing = childrenInDomCache[pathStr] - - if (existing != null) { - return existing - } - - const spyElem = fromSpy[pathStr] - - const { children: childrenFromSpy, unfurledComponents: unfurledComponentsFromSpy } = - MetadataUtils.getAllChildrenElementsIncludingUnfurledFocusedComponentsUnordered( - spyElem.elementPath, - fromSpy, - ) - const childrenAndUnfurledComponentsFromSpy = [...childrenFromSpy, ...unfurledComponentsFromSpy] - - const { children: childrenFromDom, unfurledComponents: unfurledComponentsFromDom } = - MetadataUtils.getAllChildrenElementsIncludingUnfurledFocusedComponentsUnordered( - spyElem.elementPath, - fromDOM, - ) - const { - children: childrenFromReconstructedDom, - unfurledComponents: unfurledComponentsFromReconstructedDom, - } = MetadataUtils.getAllChildrenElementsIncludingUnfurledFocusedComponentsUnordered( - spyElem.elementPath, - fromReconstructedDOM, - ) - const childrenAndUnfurledComponentsFromDom = [ - ...childrenFromDom, - ...childrenFromReconstructedDom, - ...unfurledComponentsFromDom, - ...unfurledComponentsFromReconstructedDom, - ] - - const childrenAndUnfurledComponentsNotInDom = childrenAndUnfurledComponentsFromSpy.filter( - (childNotInDom) => - childrenAndUnfurledComponentsFromDom.every( - (childInDom) => !EP.pathsEqual(childNotInDom.elementPath, childInDom.elementPath), - ), - ) - - const recursiveChildrenAndUnfurledComponents = childrenAndUnfurledComponentsNotInDom.flatMap( - (c) => { - return findChildrenInDomRecursively(EP.toString(c.elementPath)) - }, - ) - - const childrenAndUnfurledComponents = [ - ...childrenAndUnfurledComponentsFromDom, - ...childrenAndUnfurledComponentsNotInDom, - ...recursiveChildrenAndUnfurledComponents, - ] - - childrenInDomCache[pathStr] = childrenAndUnfurledComponents - - return childrenAndUnfurledComponents - } - - const elementsWithoutDomMetadata = Object.keys(fromSpy).filter((p) => fromDOM[p] == null) - - const elementsWithoutParentData = Object.keys(fromSpy).filter((p) => { - const parentLayoutSystem = fromDOM[p]?.specialSizeMeasurements.parentLayoutSystem - return parentLayoutSystem == null - }) - - // Sort and then reverse these, so that lower level elements (with longer paths) are handled ahead of their parents - // and ancestors. This means that if there are a grandparent and parent which both lack global frames - // then the parent is fixed ahead of the grandparent, which will be based on the parent. - elementsWithoutDomMetadata.sort() - elementsWithoutDomMetadata.reverse() - elementsWithoutParentData.sort() - elementsWithoutParentData.reverse() - - const workingElements: ElementInstanceMetadataMap = {} - - fastForEach(elementsWithoutDomMetadata, (pathStr) => { - const spyElem = fromSpy[pathStr] - - const children = findChildrenInDomRecursively(pathStr) - if (children.length === 0) { - return - } - - const childrenFromWorking = children.map((child) => { - const childPathStr = EP.toString(child.elementPath) - const fromWorkingElements = workingElements[childPathStr] - if (fromWorkingElements == null) { - return child - } else { - return fromWorkingElements - } - }) - - function getBoundingFrameFromChildren( - childrenFrames: Array | InfinityRectangle>, - ) { - const childrenNonInfinityFrames = childrenFrames.filter(isFiniteRectangle) - const childrenBoundingFrame = - childrenNonInfinityFrames.length === childrenFrames.length - ? boundingRectangleArray(childrenNonInfinityFrames) - : (infinityRectangle as InfinityRectangle) - - return childrenBoundingFrame - } - - const childrenBoundingGlobalFrame = getBoundingFrameFromChildren( - mapDropNulls((c) => c.globalFrame, childrenFromWorking), - ) - - const childrenBoundingGlobalFrameWithTextContent = getBoundingFrameFromChildren( - mapDropNulls( - (c) => c.specialSizeMeasurements.globalFrameWithTextContent, - childrenFromWorking, - ), - ) - - const parentPathStr = EP.toString(EP.parentPath(EP.fromString(pathStr))) - - const globalContentBoxForChildrenFromDomOrParent = - fromDOM[pathStr]?.specialSizeMeasurements.globalContentBoxForChildren ?? - workingElements[parentPathStr]?.specialSizeMeasurements.globalContentBoxForChildren ?? - null - - workingElements[pathStr] = { - ...spyElem, - globalFrame: childrenBoundingGlobalFrame, - specialSizeMeasurements: { - ...spyElem.specialSizeMeasurements, - globalContentBoxForChildren: globalContentBoxForChildrenFromDomOrParent, - globalFrameWithTextContent: childrenBoundingGlobalFrameWithTextContent, - }, - } - }) - - fastForEach(elementsWithoutParentData, (pathStr) => { - const spyElem = fromSpy[pathStr] - const sameThingFromWorkingElems = workingElements[pathStr] - const children = findChildrenInDomRecursively(pathStr) - if (children.length === 0) { - return - } - - const childrenFromWorking = children.map((child) => { - const childPathStr = EP.toString(child.elementPath) - const fromWorkingElements = workingElements[childPathStr] - if (fromWorkingElements == null) { - return child - } else { - return fromWorkingElements - } - }) - - const parentLayoutSystemFromChildren = childrenFromWorking.map( - (c) => c.specialSizeMeasurements.parentLayoutSystem, - ) - const parentFlexDirectionFromChildren = childrenFromWorking.map( - (c) => c.specialSizeMeasurements.parentFlexDirection, - ) - const immediateParentBoundsFromChildren = childrenFromWorking.map( - (c) => c.specialSizeMeasurements.immediateParentBounds, - ) - const positionForChildren = childrenFromWorking.map((c) => c.specialSizeMeasurements.position) - - workingElements[pathStr] = { - ...spyElem, - ...sameThingFromWorkingElems, - specialSizeMeasurements: { - ...spyElem.specialSizeMeasurements, - ...sameThingFromWorkingElems.specialSizeMeasurements, - parentLayoutSystem: allElemsEqual(parentLayoutSystemFromChildren) - ? parentLayoutSystemFromChildren[0] - : spyElem.specialSizeMeasurements.parentLayoutSystem, - parentFlexDirection: allElemsEqual(parentFlexDirectionFromChildren) - ? parentFlexDirectionFromChildren[0] - : spyElem.specialSizeMeasurements.parentFlexDirection, - immediateParentBounds: allElemsEqual(immediateParentBoundsFromChildren) - ? immediateParentBoundsFromChildren[0] - : spyElem.specialSizeMeasurements.immediateParentBounds, - position: allElemsEqual(positionForChildren) - ? positionForChildren[0] - : spyElem.specialSizeMeasurements.position, - }, - } - }) - - return workingElements -} - export function fillMissingDataFromAncestors(mergedMetadata: ElementInstanceMetadataMap) { return [ fillGlobalContentBoxFromAncestors, diff --git a/editor/src/templates/editor.tsx b/editor/src/templates/editor.tsx index 4787949bc9a3..16a76450b160 100644 --- a/editor/src/templates/editor.tsx +++ b/editor/src/templates/editor.tsx @@ -99,7 +99,6 @@ import { DomWalkerMutableStateCtx, createDomWalkerMutableState, invalidateDomWalkerIfNecessary, - resubscribeObservers, } from '../components/canvas/dom-walker' import { isFeatureEnabled } from '../utils/feature-switches' import { shouldUpdateLowPriorityUI } from '../components/inspector/inspector' @@ -449,7 +448,6 @@ export class Editor { dispatchedActions, oldEditorState, this.spyCollector, - {}, ) invalidateDomWalkerIfNecessary( @@ -512,7 +510,6 @@ export class Editor { [{ action: 'TRUE_UP_ELEMENTS' }], this.storedState, this.spyCollector, - {}, ) this.storedState = dispatchResultWithTruedUpGroups @@ -568,7 +565,6 @@ export class Editor { ], this.storedState, this.spyCollector, - {}, ) if (metadataUpdateDispatchResult != null) {