From 65a732b0b9f20e1c4bb0d250a049046632f9574c Mon Sep 17 00:00:00 2001
From: Sean Parsons <217400+seanparsons@users.noreply.github.com>
Date: Thu, 6 Jun 2024 12:35:44 +0100
Subject: [PATCH] Handle Some Function Wrapped Components (#5808)
- Added `functionWrapping` to `UtopiaJSXComponent`, which is an array of
`FunctionWrap` elements.
- Added utility functions to clear `FunctionWrap` values of unique IDs
and source maps.
- `looksLikeCanvasElements` has been modified to capture the functions
wrapping
the components alongside identifying the parameters.
- `createExecutionScope` now handles the wrapping of components for
execution.
- `printUtopiaJSXComponent` now handles printing out the components with
their function wrappers.
- Reordered the logic in `createExecutionScope` and extracted out the
logic for
adding a component to the scope. The new function is used for both
arbitrary
blocks to make components available to their scope and also for their
original
purpose of adding the components otherwise when created.
---
.../__snapshots__/ui-jsx-canvas.spec.tsx.snap | 1326 +++++++++++++++++
.../scene-component.tsx | 1 -
.../ui-jsx-canvas-element-renderer-utils.tsx | 2 +-
.../ui-jsx-canvas-execution-scope.tsx | 133 +-
.../components/canvas/ui-jsx-canvas.spec.tsx | 85 ++
.../editor/actions/actions.spec.tsx | 2 +
.../store-deep-equality-instances-2.spec.ts | 3 +
.../store/store-deep-equality-instances.ts | 27 +-
.../storyboard-utils.spec.ts.snap | 1 +
.../model/element-template-utils.spec.tsx | 2 +
editor/src/core/model/scene-utils.ts | 1 +
editor/src/core/model/storyboard-utils.ts | 1 +
.../core/model/test-ui-js-file.test-utils.ts | 3 +
.../property-controls-local.ts | 1 +
editor/src/core/shared/element-template.ts | 25 +-
...r-printer-arbitrary-elements.spec.tsx.snap | 2 +
.../parser-printer-dot-notation.spec.tsx.snap | 1 +
.../__snapshots__/parser-printer.spec.ts.snap | 5 +
...parser-printer-arbitrary-elements.spec.tsx | 10 +
.../parser-printer-function-wrapped.spec.tsx | 163 ++
...rser-printer-functional-components.spec.ts | 16 +
.../parser-printer-uids.spec.tsx | 1 +
.../parser-printer/parser-printer.spec.ts | 55 +
.../parser-printer.test-utils.ts | 2 +
.../workers/parser-printer/parser-printer.ts | 138 +-
editor/src/core/workers/ts/ts-worker.spec.ts | 1 +
utopia-shared/src/types/element-template.ts | 19 +
27 files changed, 1951 insertions(+), 75 deletions(-)
create mode 100644 editor/src/core/workers/parser-printer/parser-printer-function-wrapped.spec.tsx
diff --git a/editor/src/components/canvas/__snapshots__/ui-jsx-canvas.spec.tsx.snap b/editor/src/components/canvas/__snapshots__/ui-jsx-canvas.spec.tsx.snap
index b4707a65ac17..19c16a3d0eea 100644
--- a/editor/src/components/canvas/__snapshots__/ui-jsx-canvas.spec.tsx.snap
+++ b/editor/src/components/canvas/__snapshots__/ui-jsx-canvas.spec.tsx.snap
@@ -43525,6 +43525,1332 @@ export var App = (props) => {
}
`;
+exports[`UiJsxCanvas render renders a canvas with a component wrapped in React.memo 1`] = `
+"
+"
+`;
+
+exports[`UiJsxCanvas render renders a canvas with a component wrapped in React.memo 2`] = `
+Object {
+ "utopia-storyboard-uid/scene-aaa": Object {
+ "assignedToProp": null,
+ "attributeMetadatada": Object {},
+ "componentInstance": false,
+ "computedStyle": Object {},
+ "conditionValue": "not-a-conditional",
+ "earlyReturn": null,
+ "element": Object {
+ "type": "RIGHT",
+ "value": Object {
+ "children": Array [
+ Object {
+ "children": Array [],
+ "name": Object {
+ "baseVariable": "App",
+ "propertyPath": Object {
+ "propertyElements": Array [],
+ },
+ },
+ "props": Array [
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "data-uid",
+ "type": "JSX_ATTRIBUTES_ENTRY",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "ATTRIBUTE_VALUE",
+ "uid": "",
+ "value": "app-entity",
+ },
+ },
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "style",
+ "type": "JSX_ATTRIBUTES_ENTRY",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "ATTRIBUTE_VALUE",
+ "uid": "",
+ "value": Object {
+ "height": "100%",
+ "position": "absolute",
+ "width": "100%",
+ },
+ },
+ },
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "title",
+ "type": "JSX_ATTRIBUTES_ENTRY",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "ATTRIBUTE_VALUE",
+ "uid": "",
+ "value": "Hi there!",
+ },
+ },
+ ],
+ "type": "JSX_ELEMENT",
+ "uid": "",
+ },
+ ],
+ "name": Object {
+ "baseVariable": "Scene",
+ "propertyPath": Object {
+ "propertyElements": Array [],
+ },
+ },
+ "props": Array [
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "style",
+ "type": "JSX_ATTRIBUTES_ENTRY",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "ATTRIBUTE_VALUE",
+ "uid": "",
+ "value": Object {
+ "height": 200,
+ "left": 59,
+ "position": "absolute",
+ "top": 79,
+ "width": 200,
+ },
+ },
+ },
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "data-uid",
+ "type": "JSX_ATTRIBUTES_ENTRY",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "ATTRIBUTE_VALUE",
+ "uid": "",
+ "value": "scene-aaa",
+ },
+ },
+ ],
+ "type": "JSX_ELEMENT",
+ "uid": "",
+ },
+ },
+ "elementPath": Object {
+ "parts": Array [
+ Array [
+ "utopia-storyboard-uid",
+ "scene-aaa",
+ ],
+ ],
+ "type": "elementpath",
+ },
+ "globalFrame": null,
+ "importInfo": Object {
+ "exportedName": "Scene",
+ "filePath": "utopia-api",
+ "type": "IMPORTED_ORIGIN",
+ "variableName": "Scene",
+ },
+ "isEmotionOrStyledComponent": false,
+ "label": null,
+ "localFrame": null,
+ "nonRoundedGlobalFrame": null,
+ "specialSizeMeasurements": Object {
+ "alignItems": null,
+ "borderRadius": null,
+ "clientHeight": 0,
+ "clientWidth": 0,
+ "closestOffsetParentPath": Object {
+ "parts": Array [],
+ "type": "elementpath",
+ },
+ "computedHugProperty": Object {
+ "height": null,
+ "width": null,
+ },
+ "coordinateSystemBounds": null,
+ "display": "initial",
+ "flexDirection": null,
+ "float": "none",
+ "fontSize": null,
+ "fontStyle": null,
+ "fontWeight": null,
+ "gap": null,
+ "globalContentBoxForChildren": null,
+ "globalFrameWithTextContent": null,
+ "hasPositionOffset": false,
+ "hasTransform": false,
+ "htmlElementName": "div",
+ "immediateParentBounds": Object {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0,
+ },
+ "immediateParentProvidesLayout": true,
+ "justifyContent": null,
+ "layoutSystemForChildren": null,
+ "margin": Object {},
+ "naturalHeight": null,
+ "naturalWidth": null,
+ "offset": Object {
+ "x": 0,
+ "y": 0,
+ },
+ "padding": Object {},
+ "parentFlexDirection": null,
+ "parentFlexGap": 0,
+ "parentHugsOnMainAxis": false,
+ "parentJustifyContent": null,
+ "parentLayoutSystem": "flow",
+ "parentPadding": Object {},
+ "parentTextDirection": "ltr",
+ "position": "static",
+ "providesBoundsForAbsoluteChildren": false,
+ "renderedChildrenCount": 0,
+ "textBounds": null,
+ "textDecorationLine": null,
+ "usesParentBounds": false,
+ },
+ "textContent": null,
+ },
+ "utopia-storyboard-uid/scene-aaa/app-entity": Object {
+ "assignedToProp": null,
+ "attributeMetadatada": Object {},
+ "componentInstance": false,
+ "computedStyle": Object {},
+ "conditionValue": "not-a-conditional",
+ "earlyReturn": null,
+ "element": Object {
+ "type": "RIGHT",
+ "value": Object {
+ "children": Array [],
+ "name": Object {
+ "baseVariable": "App",
+ "propertyPath": Object {
+ "propertyElements": Array [],
+ },
+ },
+ "props": Array [
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "data-uid",
+ "type": "JSX_ATTRIBUTES_ENTRY",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "ATTRIBUTE_VALUE",
+ "uid": "",
+ "value": "app-entity",
+ },
+ },
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "style",
+ "type": "JSX_ATTRIBUTES_ENTRY",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "ATTRIBUTE_VALUE",
+ "uid": "",
+ "value": Object {
+ "height": "100%",
+ "position": "absolute",
+ "width": "100%",
+ },
+ },
+ },
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "title",
+ "type": "JSX_ATTRIBUTES_ENTRY",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "ATTRIBUTE_VALUE",
+ "uid": "",
+ "value": "Hi there!",
+ },
+ },
+ ],
+ "type": "JSX_ELEMENT",
+ "uid": "",
+ },
+ },
+ "elementPath": Object {
+ "parts": Array [
+ Array [
+ "utopia-storyboard-uid",
+ "scene-aaa",
+ "app-entity",
+ ],
+ ],
+ "type": "elementpath",
+ },
+ "globalFrame": null,
+ "importInfo": Object {
+ "filePath": "test.js",
+ "type": "SAME_FILE_ORIGIN",
+ "variableName": "App",
+ },
+ "isEmotionOrStyledComponent": false,
+ "label": null,
+ "localFrame": null,
+ "nonRoundedGlobalFrame": null,
+ "specialSizeMeasurements": Object {
+ "alignItems": null,
+ "borderRadius": null,
+ "clientHeight": 0,
+ "clientWidth": 0,
+ "closestOffsetParentPath": Object {
+ "parts": Array [],
+ "type": "elementpath",
+ },
+ "computedHugProperty": Object {
+ "height": null,
+ "width": null,
+ },
+ "coordinateSystemBounds": null,
+ "display": "initial",
+ "flexDirection": null,
+ "float": "none",
+ "fontSize": null,
+ "fontStyle": null,
+ "fontWeight": null,
+ "gap": null,
+ "globalContentBoxForChildren": null,
+ "globalFrameWithTextContent": null,
+ "hasPositionOffset": false,
+ "hasTransform": false,
+ "htmlElementName": "div",
+ "immediateParentBounds": Object {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0,
+ },
+ "immediateParentProvidesLayout": true,
+ "justifyContent": null,
+ "layoutSystemForChildren": null,
+ "margin": Object {},
+ "naturalHeight": null,
+ "naturalWidth": null,
+ "offset": Object {
+ "x": 0,
+ "y": 0,
+ },
+ "padding": Object {},
+ "parentFlexDirection": null,
+ "parentFlexGap": 0,
+ "parentHugsOnMainAxis": false,
+ "parentJustifyContent": null,
+ "parentLayoutSystem": "flow",
+ "parentPadding": Object {},
+ "parentTextDirection": "ltr",
+ "position": "static",
+ "providesBoundsForAbsoluteChildren": false,
+ "renderedChildrenCount": 0,
+ "textBounds": null,
+ "textDecorationLine": null,
+ "usesParentBounds": false,
+ },
+ "textContent": null,
+ },
+ "utopia-storyboard-uid/scene-aaa/app-entity:aaa": Object {
+ "assignedToProp": null,
+ "attributeMetadatada": Object {},
+ "componentInstance": false,
+ "computedStyle": Object {},
+ "conditionValue": "not-a-conditional",
+ "earlyReturn": null,
+ "element": Object {
+ "type": "RIGHT",
+ "value": Object {
+ "children": Array [
+ Object {
+ "children": Array [
+ Object {
+ "text": "hi",
+ "type": "JSX_TEXT_BLOCK",
+ "uid": "",
+ },
+ ],
+ "name": Object {
+ "baseVariable": "span",
+ "propertyPath": Object {
+ "propertyElements": Array [],
+ },
+ },
+ "props": Array [
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "style",
+ "type": "JSX_ATTRIBUTES_ENTRY",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "ATTRIBUTE_VALUE",
+ "uid": "",
+ "value": Object {
+ "position": "absolute",
+ },
+ },
+ },
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "data-uid",
+ "type": "JSX_ATTRIBUTES_ENTRY",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "ATTRIBUTE_VALUE",
+ "uid": "",
+ "value": "bbb",
+ },
+ },
+ ],
+ "type": "JSX_ELEMENT",
+ "uid": "",
+ },
+ ],
+ "name": Object {
+ "baseVariable": "div",
+ "propertyPath": Object {
+ "propertyElements": Array [],
+ },
+ },
+ "props": Array [
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "style",
+ "type": "JSX_ATTRIBUTES_ENTRY",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "content": Array [
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "SPREAD_ASSIGNMENT",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "onValue": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "name": "props",
+ "sourceMap": Object {
+ "mappings": "CAOyB",
+ "names": Array [],
+ "sources": Array [
+ "code.tsx",
+ ],
+ "sourcesContent": Array [
+ "
+ import * as React from 'react'
+ import { Storyboard, Scene } from 'utopia-api'
+
+ export var App = React.memo((props) => {
+ return (
+
+ hi
+
+ )
+ })
+ export var storyboard = (props) => {
+ return (
+
+
+
+
+
+ )
+ }
+ ",
+ ],
+ "version": 3,
+ },
+ "type": "JS_IDENTIFIER",
+ "uid": "",
+ },
+ "optionallyChained": "not-optionally-chained",
+ "originalJavascript": "props.style",
+ "property": "style",
+ "sourceMap": Object {
+ "mappings": "CAOyB",
+ "names": Array [],
+ "sources": Array [
+ "code.tsx",
+ ],
+ "sourcesContent": Array [
+ "
+ import * as React from 'react'
+ import { Storyboard, Scene } from 'utopia-api'
+
+ export var App = React.memo((props) => {
+ return (
+
+ hi
+
+ )
+ })
+ export var storyboard = (props) => {
+ return (
+
+
+
+
+
+ )
+ }
+ ",
+ ],
+ "version": 3,
+ },
+ "type": "JS_PROPERTY_ACCESS",
+ "uid": "",
+ },
+ },
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "backgroundColor",
+ "keyComments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "PROPERTY_ASSIGNMENT",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "ATTRIBUTE_VALUE",
+ "uid": "",
+ "value": "#FFFFFF",
+ },
+ },
+ ],
+ "type": "ATTRIBUTE_NESTED_OBJECT",
+ "uid": "",
+ },
+ },
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "data-uid",
+ "type": "JSX_ATTRIBUTES_ENTRY",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "ATTRIBUTE_VALUE",
+ "uid": "",
+ "value": "aaa",
+ },
+ },
+ ],
+ "type": "JSX_ELEMENT",
+ "uid": "",
+ },
+ },
+ "elementPath": Object {
+ "parts": Array [
+ Array [
+ "utopia-storyboard-uid",
+ "scene-aaa",
+ "app-entity",
+ ],
+ Array [
+ "aaa",
+ ],
+ ],
+ "type": "elementpath",
+ },
+ "globalFrame": null,
+ "importInfo": Object {
+ "filePath": "test.js",
+ "type": "SAME_FILE_ORIGIN",
+ "variableName": "div",
+ },
+ "isEmotionOrStyledComponent": false,
+ "label": null,
+ "localFrame": null,
+ "nonRoundedGlobalFrame": null,
+ "specialSizeMeasurements": Object {
+ "alignItems": null,
+ "borderRadius": null,
+ "clientHeight": 0,
+ "clientWidth": 0,
+ "closestOffsetParentPath": Object {
+ "parts": Array [],
+ "type": "elementpath",
+ },
+ "computedHugProperty": Object {
+ "height": null,
+ "width": null,
+ },
+ "coordinateSystemBounds": null,
+ "display": "initial",
+ "flexDirection": null,
+ "float": "none",
+ "fontSize": null,
+ "fontStyle": null,
+ "fontWeight": null,
+ "gap": null,
+ "globalContentBoxForChildren": null,
+ "globalFrameWithTextContent": null,
+ "hasPositionOffset": false,
+ "hasTransform": false,
+ "htmlElementName": "div",
+ "immediateParentBounds": Object {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0,
+ },
+ "immediateParentProvidesLayout": true,
+ "justifyContent": null,
+ "layoutSystemForChildren": null,
+ "margin": Object {},
+ "naturalHeight": null,
+ "naturalWidth": null,
+ "offset": Object {
+ "x": 0,
+ "y": 0,
+ },
+ "padding": Object {},
+ "parentFlexDirection": null,
+ "parentFlexGap": 0,
+ "parentHugsOnMainAxis": false,
+ "parentJustifyContent": null,
+ "parentLayoutSystem": "flow",
+ "parentPadding": Object {},
+ "parentTextDirection": "ltr",
+ "position": "static",
+ "providesBoundsForAbsoluteChildren": false,
+ "renderedChildrenCount": 0,
+ "textBounds": null,
+ "textDecorationLine": null,
+ "usesParentBounds": false,
+ },
+ "textContent": null,
+ },
+ "utopia-storyboard-uid/scene-aaa/app-entity:aaa/bbb": Object {
+ "assignedToProp": null,
+ "attributeMetadatada": Object {},
+ "componentInstance": false,
+ "computedStyle": Object {},
+ "conditionValue": "not-a-conditional",
+ "earlyReturn": null,
+ "element": Object {
+ "type": "RIGHT",
+ "value": Object {
+ "children": Array [
+ Object {
+ "text": "hi",
+ "type": "JSX_TEXT_BLOCK",
+ "uid": "",
+ },
+ ],
+ "name": Object {
+ "baseVariable": "span",
+ "propertyPath": Object {
+ "propertyElements": Array [],
+ },
+ },
+ "props": Array [
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "style",
+ "type": "JSX_ATTRIBUTES_ENTRY",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "ATTRIBUTE_VALUE",
+ "uid": "",
+ "value": Object {
+ "position": "absolute",
+ },
+ },
+ },
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "data-uid",
+ "type": "JSX_ATTRIBUTES_ENTRY",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "ATTRIBUTE_VALUE",
+ "uid": "",
+ "value": "bbb",
+ },
+ },
+ ],
+ "type": "JSX_ELEMENT",
+ "uid": "",
+ },
+ },
+ "elementPath": Object {
+ "parts": Array [
+ Array [
+ "utopia-storyboard-uid",
+ "scene-aaa",
+ "app-entity",
+ ],
+ Array [
+ "aaa",
+ "bbb",
+ ],
+ ],
+ "type": "elementpath",
+ },
+ "globalFrame": null,
+ "importInfo": Object {
+ "filePath": "test.js",
+ "type": "SAME_FILE_ORIGIN",
+ "variableName": "span",
+ },
+ "isEmotionOrStyledComponent": false,
+ "label": null,
+ "localFrame": null,
+ "nonRoundedGlobalFrame": null,
+ "specialSizeMeasurements": Object {
+ "alignItems": null,
+ "borderRadius": null,
+ "clientHeight": 0,
+ "clientWidth": 0,
+ "closestOffsetParentPath": Object {
+ "parts": Array [],
+ "type": "elementpath",
+ },
+ "computedHugProperty": Object {
+ "height": null,
+ "width": null,
+ },
+ "coordinateSystemBounds": null,
+ "display": "initial",
+ "flexDirection": null,
+ "float": "none",
+ "fontSize": null,
+ "fontStyle": null,
+ "fontWeight": null,
+ "gap": null,
+ "globalContentBoxForChildren": null,
+ "globalFrameWithTextContent": null,
+ "hasPositionOffset": false,
+ "hasTransform": false,
+ "htmlElementName": "div",
+ "immediateParentBounds": Object {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0,
+ },
+ "immediateParentProvidesLayout": true,
+ "justifyContent": null,
+ "layoutSystemForChildren": null,
+ "margin": Object {},
+ "naturalHeight": null,
+ "naturalWidth": null,
+ "offset": Object {
+ "x": 0,
+ "y": 0,
+ },
+ "padding": Object {},
+ "parentFlexDirection": null,
+ "parentFlexGap": 0,
+ "parentHugsOnMainAxis": false,
+ "parentJustifyContent": null,
+ "parentLayoutSystem": "flow",
+ "parentPadding": Object {},
+ "parentTextDirection": "ltr",
+ "position": "static",
+ "providesBoundsForAbsoluteChildren": false,
+ "renderedChildrenCount": 0,
+ "textBounds": null,
+ "textDecorationLine": null,
+ "usesParentBounds": false,
+ },
+ "textContent": null,
+ },
+}
+`;
+
+exports[`UiJsxCanvas render renders a canvas with a component wrapped in a function that wraps the component with more content 1`] = `
+"
+"
+`;
+
+exports[`UiJsxCanvas render renders a canvas with a component wrapped in a function that wraps the component with more content 2`] = `
+Object {
+ "utopia-storyboard-uid/scene-aaa": Object {
+ "assignedToProp": null,
+ "attributeMetadatada": Object {},
+ "componentInstance": false,
+ "computedStyle": Object {},
+ "conditionValue": "not-a-conditional",
+ "earlyReturn": null,
+ "element": Object {
+ "type": "RIGHT",
+ "value": Object {
+ "children": Array [
+ Object {
+ "children": Array [],
+ "name": Object {
+ "baseVariable": "App",
+ "propertyPath": Object {
+ "propertyElements": Array [],
+ },
+ },
+ "props": Array [
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "data-uid",
+ "type": "JSX_ATTRIBUTES_ENTRY",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "ATTRIBUTE_VALUE",
+ "uid": "",
+ "value": "app-entity",
+ },
+ },
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "style",
+ "type": "JSX_ATTRIBUTES_ENTRY",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "ATTRIBUTE_VALUE",
+ "uid": "",
+ "value": Object {
+ "height": "100%",
+ "position": "absolute",
+ "width": "100%",
+ },
+ },
+ },
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "title",
+ "type": "JSX_ATTRIBUTES_ENTRY",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "ATTRIBUTE_VALUE",
+ "uid": "",
+ "value": "Hi there!",
+ },
+ },
+ ],
+ "type": "JSX_ELEMENT",
+ "uid": "",
+ },
+ ],
+ "name": Object {
+ "baseVariable": "Scene",
+ "propertyPath": Object {
+ "propertyElements": Array [],
+ },
+ },
+ "props": Array [
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "style",
+ "type": "JSX_ATTRIBUTES_ENTRY",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "ATTRIBUTE_VALUE",
+ "uid": "",
+ "value": Object {
+ "height": 200,
+ "left": 59,
+ "position": "absolute",
+ "top": 79,
+ "width": 200,
+ },
+ },
+ },
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "data-uid",
+ "type": "JSX_ATTRIBUTES_ENTRY",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "ATTRIBUTE_VALUE",
+ "uid": "",
+ "value": "scene-aaa",
+ },
+ },
+ ],
+ "type": "JSX_ELEMENT",
+ "uid": "",
+ },
+ },
+ "elementPath": Object {
+ "parts": Array [
+ Array [
+ "utopia-storyboard-uid",
+ "scene-aaa",
+ ],
+ ],
+ "type": "elementpath",
+ },
+ "globalFrame": null,
+ "importInfo": Object {
+ "exportedName": "Scene",
+ "filePath": "utopia-api",
+ "type": "IMPORTED_ORIGIN",
+ "variableName": "Scene",
+ },
+ "isEmotionOrStyledComponent": false,
+ "label": null,
+ "localFrame": null,
+ "nonRoundedGlobalFrame": null,
+ "specialSizeMeasurements": Object {
+ "alignItems": null,
+ "borderRadius": null,
+ "clientHeight": 0,
+ "clientWidth": 0,
+ "closestOffsetParentPath": Object {
+ "parts": Array [],
+ "type": "elementpath",
+ },
+ "computedHugProperty": Object {
+ "height": null,
+ "width": null,
+ },
+ "coordinateSystemBounds": null,
+ "display": "initial",
+ "flexDirection": null,
+ "float": "none",
+ "fontSize": null,
+ "fontStyle": null,
+ "fontWeight": null,
+ "gap": null,
+ "globalContentBoxForChildren": null,
+ "globalFrameWithTextContent": null,
+ "hasPositionOffset": false,
+ "hasTransform": false,
+ "htmlElementName": "div",
+ "immediateParentBounds": Object {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0,
+ },
+ "immediateParentProvidesLayout": true,
+ "justifyContent": null,
+ "layoutSystemForChildren": null,
+ "margin": Object {},
+ "naturalHeight": null,
+ "naturalWidth": null,
+ "offset": Object {
+ "x": 0,
+ "y": 0,
+ },
+ "padding": Object {},
+ "parentFlexDirection": null,
+ "parentFlexGap": 0,
+ "parentHugsOnMainAxis": false,
+ "parentJustifyContent": null,
+ "parentLayoutSystem": "flow",
+ "parentPadding": Object {},
+ "parentTextDirection": "ltr",
+ "position": "static",
+ "providesBoundsForAbsoluteChildren": false,
+ "renderedChildrenCount": 0,
+ "textBounds": null,
+ "textDecorationLine": null,
+ "usesParentBounds": false,
+ },
+ "textContent": null,
+ },
+ "utopia-storyboard-uid/scene-aaa/app-entity": Object {
+ "assignedToProp": null,
+ "attributeMetadatada": Object {},
+ "componentInstance": false,
+ "computedStyle": Object {},
+ "conditionValue": "not-a-conditional",
+ "earlyReturn": null,
+ "element": Object {
+ "type": "RIGHT",
+ "value": Object {
+ "children": Array [],
+ "name": Object {
+ "baseVariable": "App",
+ "propertyPath": Object {
+ "propertyElements": Array [],
+ },
+ },
+ "props": Array [
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "data-uid",
+ "type": "JSX_ATTRIBUTES_ENTRY",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "ATTRIBUTE_VALUE",
+ "uid": "",
+ "value": "app-entity",
+ },
+ },
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "style",
+ "type": "JSX_ATTRIBUTES_ENTRY",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "ATTRIBUTE_VALUE",
+ "uid": "",
+ "value": Object {
+ "height": "100%",
+ "position": "absolute",
+ "width": "100%",
+ },
+ },
+ },
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "title",
+ "type": "JSX_ATTRIBUTES_ENTRY",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "ATTRIBUTE_VALUE",
+ "uid": "",
+ "value": "Hi there!",
+ },
+ },
+ ],
+ "type": "JSX_ELEMENT",
+ "uid": "",
+ },
+ },
+ "elementPath": Object {
+ "parts": Array [
+ Array [
+ "utopia-storyboard-uid",
+ "scene-aaa",
+ "app-entity",
+ ],
+ ],
+ "type": "elementpath",
+ },
+ "globalFrame": null,
+ "importInfo": Object {
+ "filePath": "test.js",
+ "type": "SAME_FILE_ORIGIN",
+ "variableName": "App",
+ },
+ "isEmotionOrStyledComponent": false,
+ "label": null,
+ "localFrame": null,
+ "nonRoundedGlobalFrame": null,
+ "specialSizeMeasurements": Object {
+ "alignItems": null,
+ "borderRadius": null,
+ "clientHeight": 0,
+ "clientWidth": 0,
+ "closestOffsetParentPath": Object {
+ "parts": Array [],
+ "type": "elementpath",
+ },
+ "computedHugProperty": Object {
+ "height": null,
+ "width": null,
+ },
+ "coordinateSystemBounds": null,
+ "display": "initial",
+ "flexDirection": null,
+ "float": "none",
+ "fontSize": null,
+ "fontStyle": null,
+ "fontWeight": null,
+ "gap": null,
+ "globalContentBoxForChildren": null,
+ "globalFrameWithTextContent": null,
+ "hasPositionOffset": false,
+ "hasTransform": false,
+ "htmlElementName": "div",
+ "immediateParentBounds": Object {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0,
+ },
+ "immediateParentProvidesLayout": true,
+ "justifyContent": null,
+ "layoutSystemForChildren": null,
+ "margin": Object {},
+ "naturalHeight": null,
+ "naturalWidth": null,
+ "offset": Object {
+ "x": 0,
+ "y": 0,
+ },
+ "padding": Object {},
+ "parentFlexDirection": null,
+ "parentFlexGap": 0,
+ "parentHugsOnMainAxis": false,
+ "parentJustifyContent": null,
+ "parentLayoutSystem": "flow",
+ "parentPadding": Object {},
+ "parentTextDirection": "ltr",
+ "position": "static",
+ "providesBoundsForAbsoluteChildren": false,
+ "renderedChildrenCount": 0,
+ "textBounds": null,
+ "textDecorationLine": null,
+ "usesParentBounds": false,
+ },
+ "textContent": null,
+ },
+}
+`;
+
exports[`UiJsxCanvas render renders a component used in an arbitrary block correctly 1`] = `
"
) => {
const colorTheme = useColorTheme()
- const canvasIsLive = false
const updateInvalidatedPaths = usePubSubAtomReadOnly(
DomWalkerInvalidatePathsCtxAtom,
AlwaysTrue,
diff --git a/editor/src/components/canvas/ui-jsx-canvas-renderer/ui-jsx-canvas-element-renderer-utils.tsx b/editor/src/components/canvas/ui-jsx-canvas-renderer/ui-jsx-canvas-element-renderer-utils.tsx
index e23621d57f8c..e8e557494e6d 100644
--- a/editor/src/components/canvas/ui-jsx-canvas-renderer/ui-jsx-canvas-element-renderer-utils.tsx
+++ b/editor/src/components/canvas/ui-jsx-canvas-renderer/ui-jsx-canvas-element-renderer-utils.tsx
@@ -957,7 +957,7 @@ export function utopiaCanvasJSXLookup(
}
}
-function runJSExpression(
+export function runJSExpression(
block: JSExpression,
elementPath: ElementPath | null,
currentScope: MapLike
,
diff --git a/editor/src/components/canvas/ui-jsx-canvas-renderer/ui-jsx-canvas-execution-scope.tsx b/editor/src/components/canvas/ui-jsx-canvas-renderer/ui-jsx-canvas-execution-scope.tsx
index f188450fd076..cd40fbbbf6f4 100644
--- a/editor/src/components/canvas/ui-jsx-canvas-renderer/ui-jsx-canvas-execution-scope.tsx
+++ b/editor/src/components/canvas/ui-jsx-canvas-renderer/ui-jsx-canvas-execution-scope.tsx
@@ -19,7 +19,12 @@ import {
updateMutableUtopiaCtxRefWithNewProps,
UtopiaProjectCtxAtom,
} from './ui-jsx-canvas-contexts'
-import { createLookupRender, utopiaCanvasJSXLookup } from './ui-jsx-canvas-element-renderer-utils'
+import type { RenderContext } from './ui-jsx-canvas-element-renderer-utils'
+import {
+ createLookupRender,
+ runJSExpression,
+ utopiaCanvasJSXLookup,
+} from './ui-jsx-canvas-element-renderer-utils'
import { runBlockUpdatingScope } from './ui-jsx-canvas-scope-utils'
import * as EP from '../../../core/shared/element-path'
import type {
@@ -70,16 +75,6 @@ export function createExecutionScope(
topLevelJsxComponents: Map
requireResult: MapLike
} {
- const filePathMappings = getFilePathMappings(projectContents)
- if (!(filePath in topLevelComponentRendererComponents.current)) {
- // we make sure that the ref has an entry for this filepath
- topLevelComponentRendererComponents.current[filePath] = {}
- }
- let topLevelComponentRendererComponentsForFile =
- topLevelComponentRendererComponents.current[filePath]
-
- const fileBlobsForFile = defaultIfNull(emptyFileBlobs, fileBlobs[filePath])
-
const { topLevelElements, imports, jsxFactoryFunction, combinedTopLevelArbitraryBlock } =
getParseSuccessForFilePath(filePath, projectContents)
const requireResult: MapLike = importResultFromImports(filePath, imports, customRequire)
@@ -105,57 +100,97 @@ export function createExecutionScope(
process: process,
...requireResult,
}
+ const filePathMappings = getFilePathMappings(projectContents)
+ if (!(filePath in topLevelComponentRendererComponents.current)) {
+ // we make sure that the ref has an entry for this filepath
+ topLevelComponentRendererComponents.current[filePath] = {}
+ }
+ let topLevelComponentRendererComponentsForFile =
+ topLevelComponentRendererComponents.current[filePath]
+
+ function addComponentToScope(component: UtopiaJSXComponent): void {
+ const elementName = component.name ?? 'default'
+ topLevelJsxComponents.set(component.name, component)
+ if (elementName in topLevelComponentRendererComponentsForFile) {
+ executionScope[elementName] = topLevelComponentRendererComponentsForFile[elementName]
+ } else {
+ const baseComponent = createComponentRendererComponent({
+ topLevelElementName: component.name,
+ mutableContextRef: mutableContextRef,
+ filePath: filePath,
+ })
+ const wrappedComponent = component.functionWrapping.reduceRight((workingComponent, wrap) => {
+ switch (wrap.type) {
+ case 'SIMPLE_FUNCTION_WRAP':
+ const functionExpression = runJSExpression(
+ wrap.functionExpression,
+ null,
+ executionScope,
+ renderContext,
+ wrap.functionExpression.uid,
+ null,
+ null,
+ )
+ return functionExpression(workingComponent)
+ }
+ }, baseComponent)
+ topLevelComponentRendererComponentsForFile[elementName] = wrappedComponent
+ executionScope[elementName] = wrappedComponent
+ }
+ }
+
+ const fileBlobsForFile = defaultIfNull(emptyFileBlobs, fileBlobs[filePath])
+
// TODO All of this is run on every interaction o_O
let topLevelJsxComponents: Map = new Map()
- // Make sure there is something in scope for all of the top level components
- fastForEach(topLevelElements, (topLevelElement) => {
- if (isUtopiaJSXComponent(topLevelElement)) {
- topLevelJsxComponents.set(topLevelElement.name, topLevelElement)
- const elementName = topLevelElement.name ?? 'default'
- if (!(elementName in topLevelComponentRendererComponentsForFile)) {
- topLevelComponentRendererComponentsForFile[elementName] = createComponentRendererComponent({
- topLevelElementName: topLevelElement.name,
- mutableContextRef: mutableContextRef,
- filePath: filePath,
- })
- }
- }
- })
-
- executionScope = {
- ...executionScope,
- ...topLevelComponentRendererComponentsForFile,
+ const renderContext: RenderContext = {
+ rootScope: executionScope,
+ parentComponentInputProps: {},
+ requireResult: requireResult,
+ hiddenInstances: hiddenInstances,
+ displayNoneInstances: displayNoneInstances,
+ fileBlobs: fileBlobsForFile,
+ validPaths: new Set(),
+ reactChildren: undefined,
+ metadataContext: metadataContext,
+ updateInvalidatedPaths: updateInvalidatedPaths,
+ jsxFactoryFunctionName: jsxFactoryFunction,
+ shouldIncludeCanvasRootInTheSpy: shouldIncludeCanvasRootInTheSpy,
+ filePath: filePath,
+ imports: imports,
+ code: '',
+ highlightBounds: {},
+ editedText: editedText,
+ variablesInScope: {},
+ filePathMappings: filePathMappings,
}
let spiedVariablesInRoot: VariableData = {}
// First make sure everything is in scope
if (combinedTopLevelArbitraryBlock != null && openStoryboardFileNameKILLME != null) {
+ for (const definedElsewhereValue of combinedTopLevelArbitraryBlock.definedElsewhere) {
+ const componentFromThisFile = topLevelElements.find(
+ (topLevelElement): topLevelElement is UtopiaJSXComponent => {
+ return (
+ isUtopiaJSXComponent(topLevelElement) && topLevelElement.name === definedElsewhereValue
+ )
+ },
+ )
+ if (componentFromThisFile != null) {
+ addComponentToScope(componentFromThisFile)
+ }
+ }
const { highlightBounds, code } = getCodeAndHighlightBoundsForFile(filePath, projectContents)
const propertiesFromParams = propertiesExposedByParams(combinedTopLevelArbitraryBlock.params)
const lookupRenderer = createLookupRender(
EP.emptyElementPath,
{
- rootScope: executionScope,
- parentComponentInputProps: {},
- requireResult: requireResult,
- hiddenInstances: hiddenInstances,
- displayNoneInstances: displayNoneInstances,
- fileBlobs: fileBlobsForFile,
- validPaths: new Set(),
- reactChildren: undefined,
- metadataContext: metadataContext,
- updateInvalidatedPaths: updateInvalidatedPaths,
- jsxFactoryFunctionName: jsxFactoryFunction,
- shouldIncludeCanvasRootInTheSpy: shouldIncludeCanvasRootInTheSpy,
- filePath: openStoryboardFileNameKILLME,
- imports: imports,
+ ...renderContext,
code: code,
highlightBounds: highlightBounds,
- editedText: editedText,
- variablesInScope: {},
filePathMappings: filePathMappings,
},
null,
@@ -179,6 +214,14 @@ export function createExecutionScope(
)
spiedVariablesInRoot = spiedVariablesDeclaredWithinBlock
}
+
+ // Make sure there is something in scope for all of the top level components
+ fastForEach(topLevelElements, (topLevelElement) => {
+ if (isUtopiaJSXComponent(topLevelElement)) {
+ addComponentToScope(topLevelElement)
+ }
+ })
+
// WARNING: mutating the mutableContextRef
updateMutableUtopiaCtxRefWithNewProps(mutableContextRef, {
...mutableContextRef.current,
diff --git a/editor/src/components/canvas/ui-jsx-canvas.spec.tsx b/editor/src/components/canvas/ui-jsx-canvas.spec.tsx
index 2c9c1c937373..aac365459d04 100644
--- a/editor/src/components/canvas/ui-jsx-canvas.spec.tsx
+++ b/editor/src/components/canvas/ui-jsx-canvas.spec.tsx
@@ -156,6 +156,91 @@ export { ToBeDefaultExported as default }`,
)
})
+ it('renders a canvas with a component wrapped in React.memo', () => {
+ testCanvasRender(
+ null,
+ `
+ import * as React from 'react'
+ import { Storyboard, Scene } from 'utopia-api'
+
+ export var App = React.memo((props) => {
+ return (
+
+ hi
+
+ )
+ })
+ export var ${BakedInStoryboardVariableName} = (props) => {
+ return (
+
+
+
+
+
+ )
+ }
+ `,
+ )
+ })
+
+ it('renders a canvas with a component wrapped in a function that wraps the component with more content', () => {
+ testCanvasRender(
+ null,
+ `
+ import * as React from 'react'
+ import { Storyboard, Scene } from 'utopia-api'
+
+ function wrapComponent(Component) {
+ return () => {
+ return
+ }
+ }
+
+ export var App = wrapComponent(() => {
+ return (
+
+ hi
+
+ )
+ })
+ export var ${BakedInStoryboardVariableName} = (props) => {
+ return (
+
+
+
+
+
+ )
+ }
+ `,
+ )
+ })
+
it('renders a canvas defined by a utopia storyboard component', () => {
testCanvasRender(
null,
diff --git a/editor/src/components/editor/actions/actions.spec.tsx b/editor/src/components/editor/actions/actions.spec.tsx
index 5bb199009b99..23e68963d126 100644
--- a/editor/src/components/editor/actions/actions.spec.tsx
+++ b/editor/src/components/editor/actions/actions.spec.tsx
@@ -154,6 +154,7 @@ function storyboardComponent(numberOfScenes: number): UtopiaJSXComponent {
false,
'var',
'block',
+ [],
null,
[],
jsxElement(
@@ -186,6 +187,7 @@ const originalModel = deepFreeze(
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
jsxElement(
diff --git a/editor/src/components/editor/store/store-deep-equality-instances-2.spec.ts b/editor/src/components/editor/store/store-deep-equality-instances-2.spec.ts
index dab15fd7e549..428d06f16905 100644
--- a/editor/src/components/editor/store/store-deep-equality-instances-2.spec.ts
+++ b/editor/src/components/editor/store/store-deep-equality-instances-2.spec.ts
@@ -788,6 +788,7 @@ describe('UtopiaJSXComponentKeepDeepEquality', () => {
isFunction: true,
declarationSyntax: 'const',
blockOrExpression: 'block',
+ functionWrapping: [],
param: {
type: 'PARAM',
dotDotDotToken: false,
@@ -837,6 +838,7 @@ describe('UtopiaJSXComponentKeepDeepEquality', () => {
isFunction: true,
declarationSyntax: 'const',
blockOrExpression: 'block',
+ functionWrapping: [],
param: {
type: 'PARAM',
dotDotDotToken: false,
@@ -886,6 +888,7 @@ describe('UtopiaJSXComponentKeepDeepEquality', () => {
isFunction: true,
declarationSyntax: 'const',
blockOrExpression: 'block',
+ functionWrapping: [],
param: {
type: 'PARAM',
dotDotDotToken: false,
diff --git a/editor/src/components/editor/store/store-deep-equality-instances.ts b/editor/src/components/editor/store/store-deep-equality-instances.ts
index 90c52ff49778..c2a87be51c3f 100644
--- a/editor/src/components/editor/store/store-deep-equality-instances.ts
+++ b/editor/src/components/editor/store/store-deep-equality-instances.ts
@@ -604,6 +604,8 @@ import type { OnlineState } from '../online-status'
import { onlineState } from '../online-status'
import type { NavigatorRow } from '../../navigator/navigator-row'
import { condensedNavigatorRow, regularNavigatorRow } from '../../navigator/navigator-row'
+import type { SimpleFunctionWrap, FunctionWrap } from 'utopia-shared/src/types'
+import { simpleFunctionWrap, isSimpleFunctionWrap } from 'utopia-shared/src/types'
export function ElementPropertyPathKeepDeepEquality(): KeepDeepEqualityCall {
return combine2EqualityCalls(
@@ -1570,8 +1572,29 @@ export function JSXElementChildKeepDeepEquality(): KeepDeepEqualityCall =
+ combine1EqualityCall(
+ (wrap) => wrap.functionExpression,
+ JSExpressionKeepDeepEqualityCall,
+ simpleFunctionWrap,
+ )
+
+export const FunctionWrapKeepDeepEquality: KeepDeepEqualityCall = (
+ oldValue,
+ newValue,
+) => {
+ switch (oldValue.type) {
+ case 'SIMPLE_FUNCTION_WRAP':
+ if (isSimpleFunctionWrap(newValue)) {
+ return SimpleFunctionWrapKeepDeepEquality(oldValue, newValue)
+ }
+ break
+ }
+ return keepDeepEqualityResult(newValue, false)
+}
+
export const UtopiaJSXComponentKeepDeepEquality: KeepDeepEqualityCall =
- combine10EqualityCalls(
+ combine11EqualityCalls(
(component) => component.name,
createCallWithTripleEquals(),
(component) => component.isFunction,
@@ -1580,6 +1603,8 @@ export const UtopiaJSXComponentKeepDeepEquality: KeepDeepEqualityCall component.blockOrExpression,
createCallWithTripleEquals(),
+ (component) => component.functionWrapping,
+ arrayDeepEquality(FunctionWrapKeepDeepEquality),
(component) => component.param,
nullableDeepEquality(ParamKeepDeepEquality()),
(component) => component.propsUsed,
diff --git a/editor/src/core/model/__snapshots__/storyboard-utils.spec.ts.snap b/editor/src/core/model/__snapshots__/storyboard-utils.spec.ts.snap
index 9d067e4dce25..e1545668b90d 100644
--- a/editor/src/core/model/__snapshots__/storyboard-utils.spec.ts.snap
+++ b/editor/src/core/model/__snapshots__/storyboard-utils.spec.ts.snap
@@ -12,6 +12,7 @@ Array [
"arbitraryJSBlock": null,
"blockOrExpression": "block",
"declarationSyntax": "var",
+ "functionWrapping": Array [],
"isFunction": false,
"name": "storyboard",
"param": null,
diff --git a/editor/src/core/model/element-template-utils.spec.tsx b/editor/src/core/model/element-template-utils.spec.tsx
index a7e46db78aa7..b37102c9fdeb 100644
--- a/editor/src/core/model/element-template-utils.spec.tsx
+++ b/editor/src/core/model/element-template-utils.spec.tsx
@@ -192,6 +192,7 @@ describe('removeJSXElementChild', () => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
jsxElement(
@@ -212,6 +213,7 @@ describe('removeJSXElementChild', () => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
jsxElement(
diff --git a/editor/src/core/model/scene-utils.ts b/editor/src/core/model/scene-utils.ts
index b10dacf9193a..fbbe93583339 100644
--- a/editor/src/core/model/scene-utils.ts
+++ b/editor/src/core/model/scene-utils.ts
@@ -116,6 +116,7 @@ export function convertScenesToUtopiaCanvasComponent(
false,
'var',
'block',
+ [],
null,
[],
jsxElement(
diff --git a/editor/src/core/model/storyboard-utils.ts b/editor/src/core/model/storyboard-utils.ts
index 46211943d6d7..c8bf0942475e 100644
--- a/editor/src/core/model/storyboard-utils.ts
+++ b/editor/src/core/model/storyboard-utils.ts
@@ -384,6 +384,7 @@ function addStoryboardFileForComponent(
false,
'var',
'block',
+ [],
null,
[],
storyboardElement,
diff --git a/editor/src/core/model/test-ui-js-file.test-utils.ts b/editor/src/core/model/test-ui-js-file.test-utils.ts
index 6829a626dc91..9bdb51c0b620 100644
--- a/editor/src/core/model/test-ui-js-file.test-utils.ts
+++ b/editor/src/core/model/test-ui-js-file.test-utils.ts
@@ -61,6 +61,7 @@ const mainComponentForTests = utopiaJSXComponent(
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
jsxElement(
@@ -332,6 +333,7 @@ const scene = utopiaJSXComponent(
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
jsxElement(
@@ -398,6 +400,7 @@ const TestStoryboard = utopiaJSXComponent(
false,
'var',
'block',
+ [],
null,
[],
jsxElement(
diff --git a/editor/src/core/property-controls/property-controls-local.ts b/editor/src/core/property-controls/property-controls-local.ts
index f688a5f9378b..340f7cf073b3 100644
--- a/editor/src/core/property-controls/property-controls-local.ts
+++ b/editor/src/core/property-controls/property-controls-local.ts
@@ -117,6 +117,7 @@ import type { ScriptLine } from '../../third-party/react-error-overlay/utils/sta
import { intrinsicHTMLElementNamesAsStrings } from '../shared/dom-utils'
import { valueOrArrayToArray } from '../shared/array-utils'
import { optionalMap } from '../shared/optional-utils'
+import type { RenderContext } from 'src/components/canvas/ui-jsx-canvas-renderer/ui-jsx-canvas-element-renderer-utils'
const exportedNameSymbol = Symbol('__utopia__exportedName')
const moduleNameSymbol = Symbol('__utopia__moduleName')
diff --git a/editor/src/core/shared/element-template.ts b/editor/src/core/shared/element-template.ts
index af6d66b0fc11..c52695b3d1bd 100644
--- a/editor/src/core/shared/element-template.ts
+++ b/editor/src/core/shared/element-template.ts
@@ -111,6 +111,7 @@ import type {
HugProperty,
HugPropertyWidthHeight,
ElementsByUID,
+ FunctionWrap,
} from 'utopia-shared/src/types/element-template'
import type { VariableData } from '../../components/canvas/ui-jsx-canvas'
@@ -198,6 +199,7 @@ import {
emptyComments,
emptyComputedStyle,
emptyAttributeMetadata,
+ simpleFunctionWrap,
} from 'utopia-shared/src/types/element-template'
export { emptyComments, emptyComputedStyle, emptyAttributeMetadata }
@@ -1981,6 +1983,7 @@ export function utopiaJSXComponent(
isFunction: boolean,
declarationSyntax: FunctionDeclarationSyntax,
blockOrExpression: BlockOrExpression,
+ functionWrapping: Array,
param: Param | null,
propsUsed: Array,
rootElement: JSXElementChild,
@@ -1994,6 +1997,7 @@ export function utopiaJSXComponent(
isFunction: isFunction,
declarationSyntax: declarationSyntax,
blockOrExpression: blockOrExpression,
+ functionWrapping: functionWrapping,
param: param,
propsUsed: propsUsed,
rootElement: rootElement,
@@ -2179,8 +2183,6 @@ export function propertiesExposedByParam(param: Param): Array {
}
}
-// FIXME we need to inject data-uids using insertDataUIDsIntoCode
-
export function clearArbitraryJSBlockUniqueIDs(block: ArbitraryJSBlock): ArbitraryJSBlock {
return {
...block,
@@ -2302,6 +2304,24 @@ export function clearParamUniqueIDs(param: Param): Param {
return functionParam(param.dotDotDotToken, clearBoundParamUniqueIDs(param.boundParam))
}
+export function clearFunctionWrapUniqueIDs(wrap: FunctionWrap): FunctionWrap {
+ switch (wrap.type) {
+ case 'SIMPLE_FUNCTION_WRAP':
+ return simpleFunctionWrap(clearExpressionUniqueIDs(wrap.functionExpression))
+ }
+}
+
+export function clearFunctionWrapSourceMaps(wrap: FunctionWrap): FunctionWrap {
+ switch (wrap.type) {
+ case 'SIMPLE_FUNCTION_WRAP':
+ return simpleFunctionWrap(clearExpressionSourceMaps(wrap.functionExpression))
+ }
+}
+
+export function clearFunctionWrapUniqueIDsAndSourceMaps(wrap: FunctionWrap): FunctionWrap {
+ return clearFunctionWrapSourceMaps(clearFunctionWrapUniqueIDs(wrap))
+}
+
// FIXME: Should only really be in test code.
export function clearTopLevelElementUniqueIDs(element: UtopiaJSXComponent): UtopiaJSXComponent
export function clearTopLevelElementUniqueIDs(element: ArbitraryJSBlock): ArbitraryJSBlock
@@ -2312,6 +2332,7 @@ export function clearTopLevelElementUniqueIDs(element: TopLevelElement): TopLeve
let updatedComponent: UtopiaJSXComponent = {
...element,
rootElement: clearJSXElementChildUniqueIDs(element.rootElement),
+ functionWrapping: element.functionWrapping.map(clearFunctionWrapUniqueIDsAndSourceMaps),
}
if (updatedComponent.arbitraryJSBlock != null) {
updatedComponent.arbitraryJSBlock = clearArbitraryJSBlockUniqueIDs(
diff --git a/editor/src/core/workers/parser-printer/__snapshots__/parser-printer-arbitrary-elements.spec.tsx.snap b/editor/src/core/workers/parser-printer/__snapshots__/parser-printer-arbitrary-elements.spec.tsx.snap
index 07cd2a31dd54..6c9a32b345b4 100644
--- a/editor/src/core/workers/parser-printer/__snapshots__/parser-printer-arbitrary-elements.spec.tsx.snap
+++ b/editor/src/core/workers/parser-printer/__snapshots__/parser-printer-arbitrary-elements.spec.tsx.snap
@@ -603,6 +603,7 @@ export var storyboard = (
"arbitraryJSBlock": null,
"blockOrExpression": "block",
"declarationSyntax": "var",
+ "functionWrapping": Array [],
"isFunction": true,
"name": "App",
"param": Object {
@@ -987,6 +988,7 @@ export var storyboard = (
"arbitraryJSBlock": null,
"blockOrExpression": "parenthesized-expression",
"declarationSyntax": "var",
+ "functionWrapping": Array [],
"isFunction": false,
"name": "storyboard",
"param": null,
diff --git a/editor/src/core/workers/parser-printer/__snapshots__/parser-printer-dot-notation.spec.tsx.snap b/editor/src/core/workers/parser-printer/__snapshots__/parser-printer-dot-notation.spec.tsx.snap
index f89abc4ca6a5..02d5c59eea75 100644
--- a/editor/src/core/workers/parser-printer/__snapshots__/parser-printer-dot-notation.spec.tsx.snap
+++ b/editor/src/core/workers/parser-printer/__snapshots__/parser-printer-dot-notation.spec.tsx.snap
@@ -48,6 +48,7 @@ Array [
"arbitraryJSBlock": null,
"blockOrExpression": "block",
"declarationSyntax": "var",
+ "functionWrapping": Array [],
"isFunction": true,
"name": "App",
"param": Object {
diff --git a/editor/src/core/workers/parser-printer/__snapshots__/parser-printer.spec.ts.snap b/editor/src/core/workers/parser-printer/__snapshots__/parser-printer.spec.ts.snap
index eeae8f05557a..24d0718487bd 100644
--- a/editor/src/core/workers/parser-printer/__snapshots__/parser-printer.spec.ts.snap
+++ b/editor/src/core/workers/parser-printer/__snapshots__/parser-printer.spec.ts.snap
@@ -566,6 +566,7 @@ export var whatever = (props) => {
"arbitraryJSBlock": null,
"blockOrExpression": "block",
"declarationSyntax": "var",
+ "functionWrapping": Array [],
"isFunction": true,
"name": "whatever",
"param": Object {
@@ -952,6 +953,7 @@ Object {
"arbitraryJSBlock": null,
"blockOrExpression": "expression",
"declarationSyntax": "const",
+ "functionWrapping": Array [],
"isFunction": true,
"name": "a",
"param": Object {
@@ -1108,6 +1110,7 @@ const b = (n) => n > 0 ? n
: a(10)
"arbitraryJSBlock": null,
"blockOrExpression": "expression",
"declarationSyntax": "var",
+ "functionWrapping": Array [],
"isFunction": true,
"name": "whatever",
"param": Object {
@@ -1166,6 +1169,7 @@ const b = (n) => n > 0 ? n
: a(10)
"arbitraryJSBlock": null,
"blockOrExpression": "expression",
"declarationSyntax": "const",
+ "functionWrapping": Array [],
"isFunction": true,
"name": "b",
"param": Object {
@@ -1656,6 +1660,7 @@ export var App = (props) =>
"arbitraryJSBlock": null,
"blockOrExpression": "expression",
"declarationSyntax": "var",
+ "functionWrapping": Array [],
"isFunction": true,
"name": "App",
"param": Object {
diff --git a/editor/src/core/workers/parser-printer/parser-printer-arbitrary-elements.spec.tsx b/editor/src/core/workers/parser-printer/parser-printer-arbitrary-elements.spec.tsx
index 46df412fa758..6392ee2f2409 100644
--- a/editor/src/core/workers/parser-printer/parser-printer-arbitrary-elements.spec.tsx
+++ b/editor/src/core/workers/parser-printer/parser-printer-arbitrary-elements.spec.tsx
@@ -320,6 +320,7 @@ export var whatever = props => (
true,
'var',
'expression',
+ [],
defaultPropsParam,
[],
jsxElement(
@@ -367,6 +368,7 @@ export var whatever = props => (
true,
'var',
'parenthesized-expression',
+ [],
defaultPropsParam,
[],
view,
@@ -507,6 +509,7 @@ export var whatever = (props) => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -670,6 +673,7 @@ export var whatever = (props) => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -817,6 +821,7 @@ export var whatever = (props) => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -901,6 +906,7 @@ export var whatever = (props) => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -1013,6 +1019,7 @@ return { arr: arr };`
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -1097,6 +1104,7 @@ export var whatever = (props) => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -1207,6 +1215,7 @@ return { arr: arr };`
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -1466,6 +1475,7 @@ export var storyboard = (
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
rootElement,
diff --git a/editor/src/core/workers/parser-printer/parser-printer-function-wrapped.spec.tsx b/editor/src/core/workers/parser-printer/parser-printer-function-wrapped.spec.tsx
new file mode 100644
index 000000000000..52ce6f129b36
--- /dev/null
+++ b/editor/src/core/workers/parser-printer/parser-printer-function-wrapped.spec.tsx
@@ -0,0 +1,163 @@
+import {
+ isArbitraryJSBlock,
+ type ArbitraryJSBlock,
+ type UtopiaJSXComponent,
+ isUtopiaJSXComponent,
+} from '../../shared/element-template'
+import { isParseSuccess } from '../../shared/project-file-types'
+import { printCode, printCodeOptions } from './parser-printer'
+import {
+ clearParseResultSourceMapsUniqueIDsAndEmptyBlocks,
+ clearParseResultUniqueIDsAndEmptyBlocks,
+ testParseCode,
+} from './parser-printer.test-utils'
+
+function getLoneMainTopLevelElement(code: string): UtopiaJSXComponent | ArbitraryJSBlock {
+ const parseResult = clearParseResultSourceMapsUniqueIDsAndEmptyBlocks(testParseCode(code))
+ if (isParseSuccess(parseResult)) {
+ const topLevelElements = parseResult.topLevelElements
+ const mainTopLevelElements = topLevelElements.filter(
+ (element): element is UtopiaJSXComponent | ArbitraryJSBlock =>
+ isUtopiaJSXComponent(element) || isArbitraryJSBlock(element),
+ )
+ switch (mainTopLevelElements.length) {
+ case 0:
+ throw new Error(`No main top level elements found.`)
+ case 1:
+ return mainTopLevelElements[0]
+ default:
+ throw new Error(`More than one main top level element found: ${topLevelElements}`)
+ }
+ } else {
+ throw new Error(`Not a successful parse: ${parseResult}`)
+ }
+}
+
+function testParsePrintParse(code: string): string {
+ const firstParse = clearParseResultUniqueIDsAndEmptyBlocks(testParseCode(code))
+
+ if (!isParseSuccess(firstParse)) {
+ throw new Error(JSON.stringify(firstParse))
+ }
+
+ const firstAsParseSuccess = firstParse
+
+ const printed = printCode(
+ '/index.js',
+ printCodeOptions(false, true, true),
+ firstAsParseSuccess.imports,
+ firstAsParseSuccess.topLevelElements,
+ firstAsParseSuccess.jsxFactoryFunction,
+ firstAsParseSuccess.exportsDetail,
+ )
+
+ const secondParse = clearParseResultUniqueIDsAndEmptyBlocks(testParseCode(printed))
+
+ if (!isParseSuccess(secondParse)) {
+ throw new Error(JSON.stringify(secondParse))
+ }
+
+ const secondAsParseSuccess = firstParse
+ expect(secondAsParseSuccess.topLevelElements).toEqual(firstAsParseSuccess.topLevelElements)
+
+ return printed
+}
+
+const simpleReactMemoComponentCode = `import React from "react";
+export const Wrapped = React.memo(() => {
+ return
+})`
+
+describe('parseCode', () => {
+ it('simple function component wrapped in a React.memo', () => {
+ const element = getLoneMainTopLevelElement(simpleReactMemoComponentCode)
+ expect(element.type).toEqual('UTOPIA_JSX_COMPONENT')
+ expect(element).toMatchInlineSnapshot(`
+ Object {
+ "arbitraryJSBlock": null,
+ "blockOrExpression": "block",
+ "declarationSyntax": "const",
+ "functionWrapping": Array [
+ Object {
+ "functionExpression": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "onValue": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "name": "React",
+ "sourceMap": null,
+ "type": "JS_IDENTIFIER",
+ "uid": "",
+ },
+ "optionallyChained": "not-optionally-chained",
+ "originalJavascript": "React.memo",
+ "property": "memo",
+ "sourceMap": null,
+ "type": "JS_PROPERTY_ACCESS",
+ "uid": "",
+ },
+ "type": "SIMPLE_FUNCTION_WRAP",
+ },
+ ],
+ "isFunction": true,
+ "name": "Wrapped",
+ "param": null,
+ "propsUsed": Array [],
+ "returnStatementComments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "rootElement": Object {
+ "children": Array [],
+ "name": Object {
+ "baseVariable": "div",
+ "propertyPath": Object {
+ "propertyElements": Array [],
+ },
+ },
+ "props": Array [
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "data-uid",
+ "type": "JSX_ATTRIBUTES_ENTRY",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "ATTRIBUTE_VALUE",
+ "uid": "",
+ "value": "wrapped",
+ },
+ },
+ ],
+ "type": "JSX_ELEMENT",
+ "uid": "",
+ },
+ "type": "UTOPIA_JSX_COMPONENT",
+ "usedInReactDOMRender": false,
+ }
+ `)
+ })
+})
+
+describe('printCode', () => {
+ it('simple function component wrapped in a React.memo', () => {
+ const actualResult = testParsePrintParse(simpleReactMemoComponentCode)
+ expect(actualResult).toMatchInlineSnapshot(`
+ "import React from 'react'
+ export const Wrapped = React.memo(() => {
+ return
+ })
+ "
+ `)
+ })
+})
diff --git a/editor/src/core/workers/parser-printer/parser-printer-functional-components.spec.ts b/editor/src/core/workers/parser-printer/parser-printer-functional-components.spec.ts
index 7d09434dd43d..cfc9a8836b59 100644
--- a/editor/src/core/workers/parser-printer/parser-printer-functional-components.spec.ts
+++ b/editor/src/core/workers/parser-printer/parser-printer-functional-components.spec.ts
@@ -196,6 +196,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -251,6 +252,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
propsParam,
[],
view,
@@ -289,6 +291,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
propsParam,
[],
view,
@@ -331,6 +334,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
propsParam,
['prop'],
view,
@@ -388,6 +392,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
propsParam,
['prop'],
view,
@@ -431,6 +436,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
propsParam,
['prop'],
view,
@@ -488,6 +494,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
propsParam,
['prop'],
view,
@@ -532,6 +539,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
propsParam,
['prop'],
view,
@@ -571,6 +579,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
propsParam,
[],
view,
@@ -625,6 +634,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
propsParam,
[],
view,
@@ -668,6 +678,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
propsParam,
[],
view,
@@ -724,6 +735,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
outerDestructuredObject,
['arrayPart'],
view,
@@ -812,6 +824,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
outerDestructuredObject,
['arrayPart'],
view,
@@ -851,6 +864,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -894,6 +908,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -950,6 +965,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
defaultPropsParam,
['showA'],
expect.objectContaining({
diff --git a/editor/src/core/workers/parser-printer/parser-printer-uids.spec.tsx b/editor/src/core/workers/parser-printer/parser-printer-uids.spec.tsx
index 926750fd95f3..e1835f8b11b9 100644
--- a/editor/src/core/workers/parser-printer/parser-printer-uids.spec.tsx
+++ b/editor/src/core/workers/parser-printer/parser-printer-uids.spec.tsx
@@ -410,6 +410,7 @@ export var app = (props) => {
tle.isFunction,
tle.declarationSyntax,
tle.blockOrExpression,
+ tle.functionWrapping,
tle.param,
tle.propsUsed,
updatedRootElement,
diff --git a/editor/src/core/workers/parser-printer/parser-printer.spec.ts b/editor/src/core/workers/parser-printer/parser-printer.spec.ts
index a590ad8db935..97b5d7a120b7 100644
--- a/editor/src/core/workers/parser-printer/parser-printer.spec.ts
+++ b/editor/src/core/workers/parser-printer/parser-printer.spec.ts
@@ -165,6 +165,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
['leftOfTheCake'],
view,
@@ -230,6 +231,7 @@ export var whatever = () =>
true,
'var',
'expression',
+ [],
null,
[],
view,
@@ -323,6 +325,7 @@ export function whatever(props) {
true,
'function',
'block',
+ [],
defaultPropsParam,
['leftOfTheCake'],
view,
@@ -392,6 +395,7 @@ export function whatever() {
true,
'function',
'block',
+ [],
null,
[],
view,
@@ -485,6 +489,7 @@ export default function whatever(props) {
true,
'function',
'block',
+ [],
defaultPropsParam,
['leftOfTheCake'],
view,
@@ -554,6 +559,7 @@ export default function whatever() {
true,
'function',
'block',
+ [],
null,
[],
view,
@@ -644,6 +650,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
['leftOfTheCake'],
view,
@@ -774,6 +781,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
['leftOfTheCake'],
view,
@@ -863,6 +871,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
['leftOfTheCake'],
view,
@@ -987,6 +996,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
['leftOfTheCake', 'rightOfTheCake'],
view,
@@ -1085,6 +1095,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
['leftOfTheCake'],
view,
@@ -1183,6 +1194,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
[],
view,
@@ -1373,6 +1385,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
['leftOfTheCake'],
view,
@@ -1524,6 +1537,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
['leftOfTheCake'],
view,
@@ -1684,6 +1698,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
['leftOfTheCake'],
view,
@@ -1774,6 +1789,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
[],
view,
@@ -1951,6 +1967,7 @@ export var whatever = (props) => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -2058,6 +2075,7 @@ export var whatever = (props) => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -2148,6 +2166,7 @@ export var whatever = (props) => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -2243,6 +2262,7 @@ export var whatever = (props) => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -2340,6 +2360,7 @@ export var whatever = (props) => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -2461,6 +2482,7 @@ export var whatever = (props) => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -2637,6 +2659,7 @@ export var whatever = (props) => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -2708,6 +2731,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
[],
view,
@@ -2815,6 +2839,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
[],
view,
@@ -2898,6 +2923,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
[],
view,
@@ -3029,6 +3055,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
['left'],
view,
@@ -3208,6 +3235,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
[],
view,
@@ -3349,6 +3377,7 @@ export var whatever = props => (
true,
'var',
'block',
+ [],
defaultPropsParam,
['layout'],
rootDiv,
@@ -3373,6 +3402,7 @@ export var whatever = props => (
true,
'var',
'parenthesized-expression',
+ [],
defaultPropsParam,
[],
view,
@@ -3514,6 +3544,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
['color'],
view,
@@ -3584,6 +3615,7 @@ export var whatever = () =>
true,
'var',
'expression',
+ [],
null,
[],
view,
@@ -3646,6 +3678,7 @@ export var App = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
[],
view,
@@ -3714,6 +3747,7 @@ export var App = (props) =>
true,
'var',
'parenthesized-expression',
+ [],
null,
[],
view,
@@ -3786,6 +3820,7 @@ export var App = (props) =>
true,
'var',
'parenthesized-expression',
+ [],
defaultPropsParam,
[],
view,
@@ -3967,6 +4002,7 @@ var spacing = 20`
true,
'var',
'parenthesized-expression',
+ [],
null,
[],
view,
@@ -4105,6 +4141,7 @@ export var whatever = props => {
true,
'var',
'parenthesized-expression',
+ [],
defaultPropsParam,
[],
view,
@@ -4172,6 +4209,7 @@ export var whatever = props => {
true,
'var',
'parenthesized-expression',
+ [],
defaultPropsParam,
[],
view,
@@ -4272,6 +4310,7 @@ export var whatever = props => {
true,
'var',
'parenthesized-expression',
+ [],
defaultPropsParam,
['color', 'shadowValue', 'there'],
view,
@@ -4417,6 +4456,7 @@ export var whatever = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
jsxElement(
@@ -4494,6 +4534,7 @@ export var whatever = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
jsxElement(
@@ -4556,6 +4597,7 @@ export var whatever = props => {
false,
'var',
'parenthesized-expression',
+ [],
null,
[],
jsxElement(
@@ -4622,6 +4664,7 @@ export var App = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
jsxElement(
@@ -4709,6 +4752,7 @@ export var App = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
jsxElement(
@@ -4763,6 +4807,7 @@ export var App = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
jsxElement(
@@ -4837,6 +4882,7 @@ export var App = props => {
true,
'var',
'parenthesized-expression',
+ [],
defaultPropsParam,
[],
jsxElement(
@@ -5054,6 +5100,7 @@ export var App = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
['layout'],
jsxElement(
@@ -5252,6 +5299,7 @@ export var App = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
jsxElement(
@@ -5311,6 +5359,7 @@ export var whatever = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -5361,6 +5410,7 @@ export var whatever = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -5469,6 +5519,7 @@ export var whatever = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -5585,6 +5636,7 @@ export var whatever = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -5713,6 +5765,7 @@ export var whatever = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -5879,6 +5932,7 @@ export var whatever = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -5916,6 +5970,7 @@ export var whatever = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
diff --git a/editor/src/core/workers/parser-printer/parser-printer.test-utils.ts b/editor/src/core/workers/parser-printer/parser-printer.test-utils.ts
index cfd9fa9fb1a1..52ef75e7c185 100644
--- a/editor/src/core/workers/parser-printer/parser-printer.test-utils.ts
+++ b/editor/src/core/workers/parser-printer/parser-printer.test-utils.ts
@@ -873,6 +873,7 @@ export function utopiaJSXComponentArbitrary(): Arbitrary {
isFunction,
declarationSyntax,
blockOrExpression,
+ [],
defaultPropsParam,
[],
rootElement,
@@ -1477,6 +1478,7 @@ export function clearTopLevelElementSourceMapsUniqueIDsAndEmptyBlocks(
return clearArbitraryJSBlockSourceMaps({
...withoutUID,
javascript: blockCode,
+ sourceMap: null,
})
}
case 'IMPORT_STATEMENT':
diff --git a/editor/src/core/workers/parser-printer/parser-printer.ts b/editor/src/core/workers/parser-printer/parser-printer.ts
index 2655271ed5b5..011e1a4613c6 100644
--- a/editor/src/core/workers/parser-printer/parser-printer.ts
+++ b/editor/src/core/workers/parser-printer/parser-printer.ts
@@ -109,6 +109,7 @@ import {
markedAsExported,
markedAsDefault,
parseParams,
+ parseAttributeExpression,
} from './parser-printer-parsing'
import { jsonToExpression } from './json-to-expression'
import { difference } from '../../shared/set-utils'
@@ -122,6 +123,8 @@ import type { Optic } from '../../../core/shared/optics/optics'
import { modify } from '../../../core/shared/optics/optic-utilities'
import { identifyValuesDefinedInNode } from './parser-printer-expressions'
import { isParseableFile } from '../../shared/file-utils'
+import type { FunctionWrap } from 'utopia-shared/src/types/element-template'
+import { simpleFunctionWrap } from 'utopia-shared/src/types/element-template'
const BakedInStoryboardVariableName = 'storyboard'
@@ -722,6 +725,25 @@ function getModifersForComponent(
return result
}
+function printFunctionWrapping(
+ printOptions: PrintCodeOptions,
+ imports: Imports,
+ statement: TS.Expression,
+ functionWrapping: Array,
+): TS.Expression {
+ return functionWrapping.reduceRight((workingStatement, wrap) => {
+ switch (wrap.type) {
+ case 'SIMPLE_FUNCTION_WRAP':
+ const functionExpression = jsxAttributeToExpression(
+ wrap.functionExpression,
+ imports,
+ printOptions.stripUIDs,
+ )
+ return TS.createCall(functionExpression, undefined, [workingStatement])
+ }
+ }, statement)
+}
+
function printUtopiaJSXComponent(
printOptions: PrintCodeOptions,
imports: Imports,
@@ -800,10 +822,21 @@ function printUtopiaJSXComponent(
undefined,
bodyForArrowFunction(),
)
+ const wrappedArrowFunction = printFunctionWrapping(
+ printOptions,
+ imports,
+ arrowFunction,
+ element.functionWrapping,
+ )
if (element.name == null) {
- elementNode = TS.createExportAssignment(undefined, modifiers, undefined, arrowFunction)
+ elementNode = TS.createExportAssignment(
+ undefined,
+ modifiers,
+ undefined,
+ wrappedArrowFunction,
+ )
} else {
- const varDec = TS.createVariableDeclaration(element.name, undefined, arrowFunction)
+ const varDec = TS.createVariableDeclaration(element.name, undefined, wrappedArrowFunction)
const varDecList = TS.createVariableDeclarationList([varDec], nodeFlags)
elementNode = TS.createVariableStatement(modifiers, varDecList)
}
@@ -901,10 +934,6 @@ function optionallyCreateDotDotDotToken(createIt: boolean): TS.DotDotDotToken |
return createIt ? TS.createToken(TS.SyntaxKind.DotDotDotToken) : undefined
}
-function printRawComment(comment: Comment): string {
- return `${comment.rawText}${comment.trailingNewLine ? '\n' : ''}`
-}
-
function printArbitraryJSBlock(block: ArbitraryJSBlock): TS.Node {
return TS.createUnparsedSourceFile(block.javascript)
}
@@ -1092,18 +1121,21 @@ interface PossibleCanvasContentsExpression {
name: string
initializer: TS.Expression
declarationSyntax: FunctionDeclarationSyntax
+ functionWrapping: Array
}
function possibleCanvasContentsExpression(
name: string,
initializer: TS.Expression,
declarationSyntax: FunctionDeclarationSyntax,
+ functionWrapping: Array,
): PossibleCanvasContentsExpression {
return {
type: 'POSSIBLE_CANVAS_CONTENTS_EXPRESSION',
name: name,
initializer: initializer,
declarationSyntax: declarationSyntax,
+ functionWrapping: functionWrapping,
}
}
@@ -1113,6 +1145,7 @@ interface PossibleCanvasContentsFunction {
parameters: TS.NodeArray
body: TS.ConciseBody
declarationSyntax: FunctionDeclarationSyntax
+ functionWrapping: Array
}
function possibleCanvasContentsFunction(
@@ -1120,6 +1153,7 @@ function possibleCanvasContentsFunction(
parameters: TS.NodeArray,
body: TS.ConciseBody,
declarationSyntax: FunctionDeclarationSyntax,
+ functionWrapping: Array,
): PossibleCanvasContentsFunction {
return {
type: 'POSSIBLE_CANVAS_CONTENTS_FUNCTION',
@@ -1127,6 +1161,7 @@ function possibleCanvasContentsFunction(
parameters: parameters,
body: body,
declarationSyntax: declarationSyntax,
+ functionWrapping: functionWrapping,
}
}
@@ -1165,37 +1200,77 @@ function nodeFlagsForVarLetOrConst(varLetOrConst: VarLetOrConst): TS.NodeFlags {
export function looksLikeCanvasElements(
sourceFile: TS.SourceFile,
node: TS.Node,
+ sourceText: string,
+ filename: string,
+ imports: Imports,
+ topLevelNames: Array,
+ propsObjectName: string | null,
+ existingHighlightBounds: Readonly,
+ alreadyExistingUIDs: Set,
+ applySteganography: SteganographyMode,
): Either {
if (TS.isVariableStatement(node)) {
const variableDeclaration = node.declarationList.declarations[0]
if (variableDeclaration != null) {
if (variableDeclaration.initializer != null) {
const name = variableDeclaration.name.getText(sourceFile)
- const initializer = variableDeclaration.initializer
const varLetOrConst = getVarLetOrConst(node.declarationList)
- if (TS.isArrowFunction(initializer)) {
- return right(
- possibleCanvasContentsFunction(
- name,
- initializer.parameters,
- initializer.body,
- varLetOrConst,
- ),
- )
- } else if (name === BakedInStoryboardVariableName) {
- // FIXME The below case should result in a parsed top level *element*, but instead it is treated
- // as a *component*. Unfortunately our use of the storyboard incorrectly relies on it being treated
- // as a component. See https://github.com/concrete-utopia/utopia/issues/1718 for more info
- return right(
- possibleCanvasContentsExpression(name, variableDeclaration.initializer, varLetOrConst),
- )
+ const initializer = variableDeclaration.initializer
+
+ function processVariable(
+ currentNode: TS.Node,
+ functionWrapping: Array,
+ ): Either {
+ if (TS.isCallExpression(currentNode)) {
+ const expressionFromCall = parseAttributeExpression(
+ sourceFile,
+ sourceText,
+ filename,
+ imports,
+ topLevelNames,
+ propsObjectName,
+ currentNode.expression,
+ existingHighlightBounds,
+ alreadyExistingUIDs,
+ [],
+ applySteganography,
+ 'outermost-expression',
+ )
+ if (isRight(expressionFromCall) && currentNode.arguments.length === 1) {
+ const newFunctionWrapping = [
+ ...functionWrapping,
+ simpleFunctionWrap(expressionFromCall.value.value),
+ ]
+ return processVariable(currentNode.arguments[0], newFunctionWrapping)
+ }
+ } else if (TS.isArrowFunction(currentNode)) {
+ return right(
+ possibleCanvasContentsFunction(
+ name,
+ currentNode.parameters,
+ currentNode.body,
+ varLetOrConst,
+ functionWrapping,
+ ),
+ )
+ } else if (name === BakedInStoryboardVariableName) {
+ // FIXME The below case should result in a parsed top level *element*, but instead it is treated
+ // as a *component*. Unfortunately our use of the storyboard incorrectly relies on it being treated
+ // as a component. See https://github.com/concrete-utopia/utopia/issues/1718 for more info
+ return right(
+ possibleCanvasContentsExpression(name, initializer, varLetOrConst, functionWrapping),
+ )
+ }
+ return left(node)
}
+
+ return processVariable(initializer, [])
}
}
} else if (TS.isFunctionDeclaration(node)) {
if (node.body != null) {
const name = node.name == null ? null : node.name.getText(sourceFile)
- return right(possibleCanvasContentsFunction(name, node.parameters, node.body, 'function'))
+ return right(possibleCanvasContentsFunction(name, node.parameters, node.body, 'function', []))
}
}
@@ -1537,7 +1612,18 @@ export function parseCode(
pushImportStatement(rawImportStatement)
}
} else {
- const possibleDeclaration = looksLikeCanvasElements(sourceFile, topLevelElement)
+ const possibleDeclaration = looksLikeCanvasElements(
+ sourceFile,
+ topLevelElement,
+ sourceText,
+ filePath,
+ imports,
+ topLevelNames,
+ null,
+ highlightBounds,
+ alreadyExistingUIDs_MUTABLE,
+ applySteganography,
+ )
if (isRight(possibleDeclaration)) {
const canvasContents = possibleDeclaration.value
const { name } = canvasContents
@@ -1658,6 +1744,7 @@ export function parseCode(
isFunction,
canvasContents.declarationSyntax,
contents.blockOrExpression,
+ canvasContents.functionWrapping,
foldEither(
(_) => null,
(param) => param?.value ?? null,
@@ -1799,6 +1886,7 @@ export function parseCode(
topLevelElement.isFunction,
topLevelElement.declarationSyntax,
topLevelElement.blockOrExpression,
+ topLevelElement.functionWrapping,
topLevelElement.param,
topLevelElement.propsUsed,
topLevelElement.rootElement,
diff --git a/editor/src/core/workers/ts/ts-worker.spec.ts b/editor/src/core/workers/ts/ts-worker.spec.ts
index 6a7007598f83..bef9006c3b66 100644
--- a/editor/src/core/workers/ts/ts-worker.spec.ts
+++ b/editor/src/core/workers/ts/ts-worker.spec.ts
@@ -153,6 +153,7 @@ const SampleInitTSWorkerMessage: IncomingWorkerMessage = {
isFunction: true,
declarationSyntax: 'var',
blockOrExpression: 'block',
+ functionWrapping: [],
param: {
type: 'PARAM',
dotDotDotToken: false,
diff --git a/utopia-shared/src/types/element-template.ts b/utopia-shared/src/types/element-template.ts
index 5adf0419099b..01dd7d49e3ff 100644
--- a/utopia-shared/src/types/element-template.ts
+++ b/utopia-shared/src/types/element-template.ts
@@ -320,6 +320,24 @@ export type VarLetOrConst = 'var' | 'let' | 'const'
export type FunctionDeclarationSyntax = 'function' | VarLetOrConst
export type BlockOrExpression = 'block' | 'parenthesized-expression' | 'expression'
+export interface SimpleFunctionWrap {
+ type: 'SIMPLE_FUNCTION_WRAP'
+ functionExpression: JSExpression
+}
+
+export function simpleFunctionWrap(functionExpression: JSExpression): SimpleFunctionWrap {
+ return {
+ type: 'SIMPLE_FUNCTION_WRAP',
+ functionExpression: functionExpression,
+ }
+}
+
+export type FunctionWrap = SimpleFunctionWrap
+
+export function isSimpleFunctionWrap(wrap: FunctionWrap): wrap is SimpleFunctionWrap {
+ return wrap.type === 'SIMPLE_FUNCTION_WRAP'
+}
+
export interface UtopiaJSXComponent {
type: 'UTOPIA_JSX_COMPONENT'
name: string | null
@@ -331,6 +349,7 @@ export interface UtopiaJSXComponent {
isFunction: boolean
declarationSyntax: FunctionDeclarationSyntax
blockOrExpression: BlockOrExpression
+ functionWrapping: Array
param: Param | null
propsUsed: Array
rootElement: JSXElementChild