Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unholy/welp #5380

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion editor/resources/editor/css/ReactContexify.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
position: fixed;
opacity: 0;
user-select: none;
border-radius: 3px;
border-radius: 7px;
background-color: var(--utopitheme-contextMenuBackground);
box-sizing: border-box;
/* the same as boxShadow: UtopiaStyles.shadowStyles.mid.boxShadow */
Expand Down
2 changes: 1 addition & 1 deletion editor/src/components/canvas/ui/floating-insert-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export type InsertMenuItem = {
value: InsertMenuItemValue
}

type InsertMenuItemGroup = {
export type InsertMenuItemGroup = {
label: string
options: Array<InsertMenuItem>
}
Expand Down
1 change: 1 addition & 0 deletions editor/src/components/editor/action-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export interface InsertJSXElement {
jsxElement: JSXElement
parent: ElementPath | null
importsToAdd: Imports
elementToReplace: ElementPath | null
}

export interface InsertAttributeOtherJavascriptIntoElement {
Expand Down
2 changes: 2 additions & 0 deletions editor/src/components/editor/actions/action-creators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,12 +270,14 @@ export function insertJSXElement(
element: JSXElement,
parent: ElementPath | null,
importsToAdd: Imports,
elementToReplace: ElementPath | null = null,
): InsertJSXElement {
return {
action: 'INSERT_JSX_ELEMENT',
jsxElement: element,
parent: parent,
importsToAdd: importsToAdd,
elementToReplace: elementToReplace,
}
}

Expand Down
26 changes: 21 additions & 5 deletions editor/src/components/editor/actions/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ import { assertNever, fastForEach, getProjectLockedKey, identity } from '../../.
import { mergeImports } from '../../../core/workers/common/project-file-utils'
import type { UtopiaTsWorkers } from '../../../core/workers/common/worker-types'
import type { IndexPosition } from '../../../utils/utils'
import Utils from '../../../utils/utils'
import Utils, { absolute } from '../../../utils/utils'
import type { ProjectContentTreeRoot } from '../../assets'
import {
isProjectContentFile,
Expand Down Expand Up @@ -2296,7 +2296,7 @@ export const UPDATE_FNS = {
editor,
(element) => element,
(success, _, underlyingFilePath) => {
const utopiaComponents = getUtopiaJSXComponentsFromSuccess(success)
const startingComponents = getUtopiaJSXComponentsFromSuccess(success)
const targetParent =
action.parent == null
? // action.parent == null means Canvas, which means storyboard root element
Expand All @@ -2311,9 +2311,25 @@ export const UPDATE_FNS = {
return success
}

const insertionIndex =
action.elementToReplace == null
? -1
: getIndexInParent(
success.topLevelElements,
EP.dynamicPathToStaticPath(action.elementToReplace),
)

const withReplacedElementDeleted =
action.elementToReplace == null
? {
components: startingComponents,
imports: success.imports,
}
: removeElementAtPath(action.elementToReplace, startingComponents, success.imports)

const updatedImports = mergeImports(
underlyingFilePath,
success.imports,
withReplacedElementDeleted.imports,
action.importsToAdd,
)

Expand All @@ -2324,8 +2340,8 @@ export const UPDATE_FNS = {
const withInsertedElement = insertJSXElementChildren(
childInsertionPath(targetParent),
[renamedJsxElement],
utopiaComponents,
null,
withReplacedElementDeleted.components,
insertionIndex >= 0 ? absolute(insertionIndex) : null,
)

const uid = getUtopiaID(renamedJsxElement)
Expand Down
39 changes: 38 additions & 1 deletion editor/src/components/editor/store/editor-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ import {
ElementInstanceMetadataMapKeepDeepEquality,
InvalidOverrideNavigatorEntryKeepDeepEquality,
RenderPropNavigatorEntryKeepDeepEquality,
RenderPropValueNavigatorEntryKeepDeepEquality,
SyntheticNavigatorEntryKeepDeepEquality,
} from './store-deep-equality-instances'

Expand Down Expand Up @@ -2153,7 +2154,7 @@ export function syntheticNavigatorEntriesEqual(

export interface RenderPropNavigatorEntry {
type: 'RENDER_PROP'
elementPath: ElementPath
elementPath: ElementPath // path of the element containing this render prop
propName: string
}

Expand All @@ -2175,6 +2176,36 @@ export function renderPropNavigatorEntriesEqual(
return RenderPropNavigatorEntryKeepDeepEquality(first, second).areEqual
}

export interface RenderPropValueNavigatorEntry {
type: 'RENDER_PROP_VALUE'
elementPath: ElementPath // path of the actual element being used inside a render prop
prop: string
}

export function renderPropValueNavigatorEntry(
elementPath: ElementPath,
prop: string,
): RenderPropValueNavigatorEntry {
return {
type: 'RENDER_PROP_VALUE',
elementPath: elementPath,
prop: prop,
}
}

export function isRenderPropValueNavigatorEntry(
v: NavigatorEntry,
): v is RenderPropValueNavigatorEntry {
return v.type === 'RENDER_PROP_VALUE'
}

export function renderPropValueNavigatorEntriesEqual(
first: RenderPropValueNavigatorEntry,
second: RenderPropValueNavigatorEntry,
): boolean {
return RenderPropValueNavigatorEntryKeepDeepEquality(first, second).areEqual
}

export interface InvalidOverrideNavigatorEntry {
type: 'INVALID_OVERRIDE'
elementPath: ElementPath
Expand Down Expand Up @@ -2211,6 +2242,7 @@ export type NavigatorEntry =
| SyntheticNavigatorEntry
| InvalidOverrideNavigatorEntry
| RenderPropNavigatorEntry
| RenderPropValueNavigatorEntry
| SlotNavigatorEntry

export function navigatorEntriesEqual(
Expand Down Expand Up @@ -2249,6 +2281,9 @@ export function navigatorEntryToKey(entry: NavigatorEntry): string {
case 'RENDER_PROP': {
return `render-prop-${EP.toComponentId(entry.elementPath)}-${entry.propName}`
}
case 'RENDER_PROP_VALUE': {
return `render-prop-value-${EP.toComponentId(entry.elementPath)}-${entry.prop}`
}
case 'INVALID_OVERRIDE':
return `error-${EP.toComponentId(entry.elementPath)}`
case 'SLOT':
Expand All @@ -2271,6 +2306,8 @@ export function varSafeNavigatorEntryToKey(entry: NavigatorEntry): string {
return `synthetic_${EP.toVarSafeComponentId(entry.elementPath)}_${childOrAttributeDetails}`
case 'RENDER_PROP':
return `renderprop_${EP.toVarSafeComponentId(entry.elementPath)}_${entry.propName}`
case 'RENDER_PROP_VALUE':
return `renderpropvalue_${EP.toVarSafeComponentId(entry.elementPath)}_${entry.prop}`
case 'INVALID_OVERRIDE':
return `error_${EP.toVarSafeComponentId(entry.elementPath)}`
case 'SLOT':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -351,13 +351,15 @@ import type {
TrueUpHuggingElement,
RenderPropNavigatorEntry,
SlotNavigatorEntry,
RenderPropValueNavigatorEntry,
} from './editor-state'
import {
trueUpGroupElementChanged,
trueUpChildrenOfGroupChanged,
invalidOverrideNavigatorEntry,
trueUpHuggingElement,
renderPropNavigatorEntry,
renderPropValueNavigatorEntry,
} from './editor-state'
import {
editorStateNodeModules,
Expand Down Expand Up @@ -690,6 +692,15 @@ export const RenderPropNavigatorEntryKeepDeepEquality: KeepDeepEqualityCall<Rend
renderPropNavigatorEntry,
)

export const RenderPropValueNavigatorEntryKeepDeepEquality: KeepDeepEqualityCall<RenderPropValueNavigatorEntry> =
combine2EqualityCalls(
(entry) => entry.elementPath,
ElementPathKeepDeepEquality,
(entry) => entry.prop,
StringKeepDeepEquality,
renderPropValueNavigatorEntry,
)

export const InvalidOverrideNavigatorEntryKeepDeepEquality: KeepDeepEqualityCall<InvalidOverrideNavigatorEntry> =
combine2EqualityCalls(
(entry) => entry.elementPath,
Expand Down Expand Up @@ -733,6 +744,11 @@ export const NavigatorEntryKeepDeepEquality: KeepDeepEqualityCall<NavigatorEntry
return RenderPropNavigatorEntryKeepDeepEquality(oldValue, newValue)
}
break
case 'RENDER_PROP_VALUE':
if (oldValue.type === newValue.type) {
return RenderPropValueNavigatorEntryKeepDeepEquality(oldValue, newValue)
}
break
case 'INVALID_OVERRIDE':
if (oldValue.type === newValue.type) {
return InvalidOverrideNavigatorEntryKeepDeepEquality(oldValue, newValue)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import {
import { type Icon } from 'utopia-api'
import { getRegisteredComponent } from '../../../core/property-controls/property-controls-utils'
import { defaultImportsForComponentModule } from '../../../core/property-controls/property-controls-local'
import { useGetInsertableComponents } from '../../canvas/ui/floating-insert-menu'
import { getInsertableGroupLabel } from '../../shared/project-components'

function getIconForComponent(
targetName: string,
Expand All @@ -52,11 +54,14 @@ interface PreferredChildComponentDescriptorWithIcon extends PreferredChildCompon

const usePreferredChildrenForTargetProp = (
target: ElementPath,
prop?: string,
prop: string | null,
replacesTarget: boolean,
): Array<PreferredChildComponentDescriptorWithIcon> => {
const actualTarget = replacesTarget && prop == null ? EP.parentPath(target) : target

const targetElement = useEditorState(
Substores.metadata,
(store) => MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, target),
(store) => MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, actualTarget),
'usePreferredChildrenForTargetProp targetElement',
)

Expand Down Expand Up @@ -221,7 +226,8 @@ function insertPreferredChild(
target: ElementPath,
projectContents: ProjectContentTreeRoot,
dispatch: EditorDispatch,
prop?: string,
prop: string | null,
replacesTarget: boolean,
) {
const uniqueIds = new Set(getAllUniqueUids(projectContents).uniqueIDs)
const uid = generateConsistentUID('prop', uniqueIds)
Expand All @@ -233,9 +239,17 @@ function insertPreferredChild(
throw new Error('only JSX elements are supported as preferred components')
}

const targetParent = replacesTarget ? EP.parentPath(target) : target
const elementToReplace = replacesTarget ? target : null

const insertionAction =
prop == null
? insertJSXElement(element, target, preferredChildToInsert.additionalImports ?? undefined)
? insertJSXElement(
element,
targetParent,
preferredChildToInsert.additionalImports ?? undefined,
elementToReplace,
)
: setProp_UNSAFE(
target,
PP.create(prop),
Expand All @@ -248,9 +262,10 @@ function insertPreferredChild(

interface ComponentPickerContextMenuProps {
target: ElementPath
prop?: string
prop: string | null
key: string
id: string
replacesTarget: boolean
}

function iconPropsForIcon(icon: Icon): IcnProps {
Expand Down Expand Up @@ -287,10 +302,14 @@ export function labelTestIdForComponentIcon(
}

const ComponentPickerContextMenuSimple = React.memo<ComponentPickerContextMenuProps>(
({ id, target, prop }) => {
({ id, target, prop, replacesTarget }) => {
const { showComponentPickerContextMenu } = useShowComponentPickerContextMenu(`${id}-full`)

const preferredChildrenForTargetProp = usePreferredChildrenForTargetProp(target, prop)
const preferredChildrenForTargetProp = usePreferredChildrenForTargetProp(
target,
prop,
replacesTarget,
)

const dispatch = useDispatch()

Expand All @@ -304,8 +323,9 @@ const ComponentPickerContextMenuSimple = React.memo<ComponentPickerContextMenuPr
projectContentsRef.current,
dispatch,
prop,
replacesTarget,
),
[dispatch, projectContentsRef, prop, target],
[dispatch, projectContentsRef, prop, target, replacesTarget],
)
const wrapperRef = React.useRef<HTMLDivElement>(null)

Expand Down Expand Up @@ -359,10 +379,19 @@ const ComponentPickerContextMenuSimple = React.memo<ComponentPickerContextMenuPr
)

const ComponentPickerContextMenuFull = React.memo<ComponentPickerContextMenuProps>(
({ id, target, prop }) => {
({ id, target, prop, replacesTarget }) => {
const { hideComponentPickerContextMenu } = useShowComponentPickerContextMenu(`${id}-full`)

const preferredChildrenForTargetProp = usePreferredChildrenForTargetProp(target, prop)
const preferredChildrenForTargetProp = usePreferredChildrenForTargetProp(
target,
prop,
replacesTarget,
)

const allInsertableComponents = useGetInsertableComponents('insert').flatMap((g) => ({
label: g.label,
options: g.options,
}))

const dispatch = useDispatch()

Expand All @@ -379,9 +408,10 @@ const ComponentPickerContextMenuFull = React.memo<ComponentPickerContextMenuProp
projectContentsRef.current,
dispatch,
prop,
replacesTarget,
)
},
[dispatch, projectContentsRef, prop, target],
[dispatch, projectContentsRef, prop, target, replacesTarget],
)

const squashEvents = React.useCallback((e: React.MouseEvent<unknown>) => {
Expand All @@ -391,13 +421,12 @@ const ComponentPickerContextMenuFull = React.memo<ComponentPickerContextMenuProp
if (preferredChildrenForTargetProp == null) {
return null
}

return (
<Menu key={id} id={id} animation={false} style={{ width: 457 }} onClick={squashEvents}>
<Menu key={id} id={id} animation={false} style={{ width: 260 }} onClick={squashEvents}>
<ComponentPicker
insertionTargetName={prop ?? 'Child'}
preferredComponents={preferredChildrenForTargetProp}
allComponents={preferredChildrenForTargetProp}
allComponents={allInsertableComponents}
onItemClick={onItemClick}
onClickCloseButton={hideComponentPickerContextMenu}
/>
Expand All @@ -407,15 +436,22 @@ const ComponentPickerContextMenuFull = React.memo<ComponentPickerContextMenuProp
)

export const ComponentPickerContextMenu = React.memo<ComponentPickerContextMenuProps>(
({ id, target, prop }) => {
({ id, target, prop, replacesTarget }) => {
return (
<React.Fragment>
<ComponentPickerContextMenuSimple target={target} key={id} id={id} prop={prop} />
<ComponentPickerContextMenuSimple
target={target}
key={id}
id={id}
prop={prop}
replacesTarget={replacesTarget}
/>
<ComponentPickerContextMenuFull
target={target}
key={`${id}-full`}
id={`${id}-full`}
prop={prop}
replacesTarget={replacesTarget}
/>
</React.Fragment>
)
Expand Down
Loading
Loading