Skip to content

Commit

Permalink
feature(editor) Scoped variables show destructured parameters. (#4882)
Browse files Browse the repository at this point in the history
- Added `propertiesExposedByParam` utility function.
- Added `findContainingComponent` utility function.
- `getVariablesInScope` now finds the correct component and not just the first one.
- Fixed `applyPropsParamToPassedProps` to correctly handle destructured values
  and repeated field usages.
- `createComponentRendererComponent` includes values exposed by the props param
  when building `spiedVariablesInScope`.
  • Loading branch information
seanparsons authored Feb 13, 2024
1 parent da77530 commit 0d843a1
Show file tree
Hide file tree
Showing 10 changed files with 488 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
isUtopiaJSXComponent,
isSVGElement,
isJSXElement,
propertiesExposedByParam,
} from '../../../core/shared/element-template'
import { optionalMap } from '../../../core/shared/optional-utils'
import type {
Expand Down Expand Up @@ -36,6 +37,7 @@ import { usePubSubAtomReadOnly } from '../../../core/shared/atom-with-pub-sub'
import { JSX_CANVAS_LOOKUP_FUNCTION_NAME } from '../../../core/shared/dom-utils'
import { objectMap } from '../../../core/shared/object-utils'
import type { ComponentRendererComponent } from './component-renderer-component'
import { mapArrayToDictionary } from '../../../core/shared/array-utils'

function tryToGetInstancePath(
maybePath: ElementPath | null,
Expand Down Expand Up @@ -130,6 +132,22 @@ export function createComponentRendererComponent(params: {
...appliedProps,
}

let spiedVariablesInScope: VariableData = {}
if (utopiaJsxComponent.param != null) {
spiedVariablesInScope = mapArrayToDictionary(
propertiesExposedByParam(utopiaJsxComponent.param),
(paramName) => {
return paramName
},
(paramName) => {
return {
spiedValue: scope[paramName],
insertionCeiling: instancePath,
}
},
)
}

let codeError: Error | null = null

// Protect against infinite recursion by taking the view that anything
Expand Down Expand Up @@ -161,8 +179,6 @@ export function createComponentRendererComponent(params: {
})
}

let definedWithinWithValues: MapLike<unknown> = {}

if (utopiaJsxComponent.arbitraryJSBlock != null) {
const lookupRenderer = createLookupRender(
rootElementPath,
Expand Down Expand Up @@ -194,12 +210,23 @@ export function createComponentRendererComponent(params: {
lookupRenderer,
)

definedWithinWithValues = runBlockUpdatingScope(
const definedWithinWithValues = runBlockUpdatingScope(
params.filePath,
mutableContext.requireResult,
utopiaJsxComponent.arbitraryJSBlock,
scope,
)

spiedVariablesInScope = {
...spiedVariablesInScope,
...objectMap(
(spiedValue) => ({
spiedValue: spiedValue,
insertionCeiling: null,
}),
definedWithinWithValues,
),
}
}

function buildComponentRenderResult(element: JSXElementChild): React.ReactElement {
Expand All @@ -208,14 +235,6 @@ export function createComponentRendererComponent(params: {
instancePath,
)

const spiedVariablesInScope: VariableData = objectMap(
(spiedValue) => ({
spiedValue: spiedValue,
insertionCeiling: null,
}),
definedWithinWithValues,
)

const renderedCoreElement = renderCoreElement(
element,
ownElementPath,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,39 +38,36 @@ export function applyPropsParamToPassedProps(
output[paramName] = getParamValue(value, boundParam.defaultExpression)
} else if (isDestructuredObject(boundParam)) {
if (typeof value === 'object' && !Array.isArray(value) && value !== null) {
const valueAsRecord: Record<string, unknown> = { ...value }
let remainingValues = { ...value } as Record<string, unknown>
let remainingKeys = Object.keys(remainingValues)
boundParam.parts.forEach((part) => {
for (const part of boundParam.parts) {
const { propertyName, param } = part
if (propertyName != null) {
// e.g. `{ prop: renamedProp }` or `{ prop: { /* further destructuring */ } }`
// Can't spread if we have a property name
const innerValue = remainingValues[propertyName]
applyBoundParamToOutput(innerValue, param.boundParam)
remainingKeys = remainingKeys.filter((k) => k !== propertyName)
delete remainingValues[propertyName]
} else {
if (propertyName == null) {
const { dotDotDotToken: spread, boundParam: innerBoundParam } = param
if (isRegularParam(innerBoundParam)) {
// e.g. `{ prop }` or `{ ...remainingProps }`
const { paramName } = innerBoundParam
if (spread) {
output[paramName] = remainingValues
remainingKeys = []
remainingValues = {}
} else {
output[paramName] = getParamValue(
remainingValues[paramName],
valueAsRecord[paramName],
innerBoundParam.defaultExpression,
)
remainingKeys = remainingKeys.filter((k) => k !== paramName)
delete remainingValues[paramName]
}
}
} else {
// e.g. `{ prop: renamedProp }` or `{ prop: { /* further destructuring */ } }`
// Can't spread if we have a property name
const innerValue = valueAsRecord[propertyName]
applyBoundParamToOutput(innerValue, param.boundParam)
delete remainingValues[propertyName]
// No other cases are legal
// TODO Should we throw? Users will already have a lint error
}
})
}
}
// TODO Throw, but what?
} else {
Expand Down
6 changes: 5 additions & 1 deletion editor/src/components/canvas/ui/floating-insert-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,11 @@ export function useGetInsertableComponents(
}
}, [fullPath, scopedVariables])

return insertableComponents.concat(insertableVariables)
if (insertMenuMode === 'insert') {
return insertableComponents.concat(insertableVariables)
} else {
return insertableComponents
}
}

export function useComponentSelectorStyles(): StylesConfig<InsertMenuItem, false> {
Expand Down
Loading

0 comments on commit 0d843a1

Please sign in to comment.