Skip to content

Commit

Permalink
Data Selector Popup (#5755)
Browse files Browse the repository at this point in the history
* wip - with static data

* rows

* wip - clicking on stuff

* correct navigation

* update variable on apply

* use correct colors

* do not traverse when clicking in the left hand column

* disable back button when current value path is empty

* only traverse on double click

* focusedVariableChildren is no longer a state

* separating selected vs navigatedTo path

* showing the selected path in the breadcrumbs

* ElementSelector uses zero-based indexes

* delete selection on navigation

* add role to data reference cartouche

* use the cartouche ui

* top bar for primitive variables

* show selection on cartouche

* grid column

* temporarily disable the back button

* border color + selected

* assorted navigation fixes

* home button too

* be able to select primitiveVars and folderVars

* tooltip wraps icon too

* implementing CartoucheUI datatype prop

* subdue the "folder" border color

* hover

* removing currentHoveredPath

* apply button

* using mckayla's new icons

* introducing pickTargetType

* Revert "introducing pickTargetType"

This reverts commit 42e8703.

* only matching elements get a cartouche

* wip - value preview styles

* do not show monospaced font for information role cartouche

* wip - top bar

* Fix to X

* tweaking the breadcrumbs to look like an input field

* tweaking the "input field" css

* the valuePreviewText observes hover

* organize imports

* Update data-selector-modal.tsx

* Update data-selector-modal.tsx

* Update data-selector-modal.tsx

* Update data-selector-modal.tsx

* basc test

* update snapshots

* cleanup

* Update data-selector-modal.tsx

* check children length in childvars

---------

Co-authored-by: Balazs Bajorics <[email protected]>
  • Loading branch information
bkrmendy and balazsbajorics authored May 29, 2024
1 parent 1e418ad commit 1cb14ef
Show file tree
Hide file tree
Showing 8 changed files with 819 additions and 76 deletions.
Original file line number Diff line number Diff line change
@@ -1,22 +1,44 @@
import React from 'react'
import { FlexRow, Icn, Icons, Tooltip, UtopiaStyles, useColorTheme } from '../../../../uuiui'
import type { IcnColor } from '../../../../uuiui'
import { FlexRow, Icn, Tooltip, UtopiaStyles, useColorTheme } from '../../../../uuiui'
import { when } from '../../../../utils/react-conditionals'

export interface HoverHandlers {
onMouseEnter: (e: React.MouseEvent) => void
onMouseLeave: (e: React.MouseEvent) => void
}

export type CartoucheUIProps = React.PropsWithChildren<{
tooltip: string
source: 'internal' | 'external' | 'literal'
role: 'selection' | 'information' | 'folder'
datatype: 'renderable' | 'boolean' | 'array' | 'object'
inverted: boolean
selected: boolean
testId: string
preview?: boolean
onDelete?: (e: React.MouseEvent) => void
onClick?: (e: React.MouseEvent) => void
onDoubleClick?: (e: React.MouseEvent) => void
onHover?: HoverHandlers
}>

export const CartoucheUI = React.forwardRef(
(props: CartoucheUIProps, ref: React.Ref<HTMLDivElement>) => {
const { tooltip, onClick, onDoubleClick, onDelete, children, source, inverted, selected } =
props
const {
tooltip,
onClick,
onDoubleClick,
onDelete,
children,
source,
inverted,
selected,
role,
datatype,
onHover,
preview = false,
} = props

const colorTheme = useColorTheme()

Expand All @@ -30,6 +52,8 @@ export const CartoucheUI = React.forwardRef(

const borderColor = inverted
? colorTheme.white.value
: role === 'folder' && !selected
? colorTheme.verySubduedForeground.value
: source === 'external'
? colorTheme.green.value
: colorTheme.selectionBlue.value
Expand All @@ -42,44 +66,61 @@ export const CartoucheUI = React.forwardRef(

const foregroundColor = inverted
? colorTheme.white.value
: source === 'literal'
: source === 'literal' || role === 'information' || role === 'folder'
? colorTheme.neutralForeground.value
: primaryForegoundColorToUse

const backgroundColor =
source === 'literal' ? colorTheme.fg0Opacity10.value : primaryBackgroundColorToUse
role === 'information' || role === 'folder'
? colorTheme.neutralBackground.value
: source === 'literal'
? colorTheme.fg0Opacity10.value
: primaryBackgroundColorToUse

const wrappedOnClick = useStopPropagation(onClick)
const wrappedOnDoubleClick = useStopPropagation(onDoubleClick)
const wrappedOnDelete = useStopPropagation(onDelete)

const shouldShowBorder = selected || role === 'folder'

return (
<div
onClick={wrappedOnClick}
onDoubleClick={wrappedOnDoubleClick}
onMouseEnter={onHover?.onMouseEnter}
onMouseLeave={onHover?.onMouseLeave}
style={{
minWidth: 0, // this ensures that the div can never expand the allocated grid space
}}
ref={ref}
>
<FlexRow
style={{
cursor: 'pointer',
fontSize: 10,
fontWeight: 400,
color: foregroundColor,
backgroundColor: backgroundColor,
border: selected ? '1px solid ' + borderColor : '1px solid transparent',
padding: '0px 6px 0 4px',
borderRadius: 4,
height: 20,
display: 'flex',
flex: 1,
gap: 4,
}}
>
{source === 'literal' ? null : <Icons.NavigatorData color={cartoucheIconColor} />}
<Tooltip title={tooltip}>
<Tooltip title={tooltip}>
<FlexRow
style={{
cursor: 'pointer',
fontSize: 10,
fontWeight: 400,
color: foregroundColor,
backgroundColor: backgroundColor,
border: shouldShowBorder ? '1px solid ' + borderColor : '1px solid transparent',
padding: '0px 6px 0 4px',
borderRadius: 4,
height: 20,
display: 'flex',
flex: 1,
gap: 4,
opacity: preview ? 0.5 : 1,
}}
>
{source === 'literal' ? null : (
<Icn
category='navigator-element'
type={dataTypeToIconType(datatype)}
color={cartoucheIconColor}
width={12}
height={12}
/>
)}
<div
style={{
flex: 1,
Expand All @@ -92,32 +133,50 @@ export const CartoucheUI = React.forwardRef(
/* Beginning of string */
direction: source === 'literal' ? 'ltr' : 'rtl', // TODO we need a better way to ellipsize the beginnign because rtl eats ' " marks
textAlign: 'left',
...UtopiaStyles.fontStyles.monospaced,
...(role !== 'information' ? UtopiaStyles.fontStyles.monospaced : {}),
}}
>
{children}
&lrm;
{/* the &lrm; non-printing character is added to fix the punctuation marks disappearing because of direction: rtl */}
</div>
</Tooltip>
{when(
onDelete != null,
<Icn
category='semantic'
type='cross'
color={cartoucheIconColor}
width={12}
height={12}
data-testid={`delete-${props.testId}`}
onClick={wrappedOnDelete}
/>,
)}
</FlexRow>
{when(
datatype === 'object' && role === 'folder',
// a trailing ellipsis is added to indicate that the object can be traversed
<span></span>,
)}
{when(
onDelete != null,
<Icn
category='semantic'
type='cross'
color={cartoucheIconColor}
width={12}
height={12}
data-testid={`delete-${props.testId}`}
onClick={wrappedOnDelete}
/>,
)}
</FlexRow>
</Tooltip>
</div>
)
},
)

function dataTypeToIconType(dataType: CartoucheUIProps['datatype']): string {
switch (dataType) {
case 'renderable':
return 'data'
case 'boolean':
return '👻'
case 'array':
return 'array'
case 'object':
return 'object'
}
}

function useStopPropagation(callback: ((e: React.MouseEvent) => void) | undefined) {
return React.useCallback(
(e: React.MouseEvent) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ import { getRegisteredComponent } from '../../../../core/property-controls/prope
import type { EditorAction } from '../../../editor/action-types'
import { useVariablesInScopeForSelectedElement } from './variables-in-scope-utils'
import { useAtom } from 'jotai'
import { DataSelectorModal } from './data-selector-modal'

export const VariableFromScopeOptionTestId = (idx: string) => `variable-from-scope-${idx}`
export const DataPickerPopupButtonTestId = `data-picker-popup-button-test-id`
Expand All @@ -111,6 +112,7 @@ export interface PropertyLabelAndPlusButtonProps {
popupIsOpen: boolean
isHovered: boolean
isConnectedToData: boolean
testId: string
}

export function PropertyLabelAndPlusButton(
Expand All @@ -125,6 +127,7 @@ export function PropertyLabelAndPlusButton(
handleMouseEnter,
handleMouseLeave,
children,
testId,
} = props

return (
Expand All @@ -148,6 +151,7 @@ export function PropertyLabelAndPlusButton(
<span onClick={openPopup}>{title}</span>
{children}
<div
data-testid={testId}
onClick={openPopup}
style={{
opacity: isHovered || popupIsOpen ? 1 : 0,
Expand Down Expand Up @@ -348,7 +352,14 @@ export function useDataPickerButton(
{
name: 'offset',
options: {
offset: [0, 8],
offset: [8, 8],
},
},
{
name: 'preventOverflow',
options: {
altAxis: true,
padding: 20,
},
},
],
Expand All @@ -360,12 +371,12 @@ export function useDataPickerButton(

const DataPickerComponent = React.useMemo(
() => (
<DataPickerPopup
<DataSelectorModal
{...popper.attributes.popper}
style={popper.styles.popper}
closePopup={closePopup}
ref={setPopperElement}
onTweakProperty={onPropertyPicked}
onPropertyPicked={onPropertyPicked}
variablesInScope={variablesInScope}
/>
),
Expand Down Expand Up @@ -490,6 +501,7 @@ const RowForBaseControl = React.memo((props: RowForBaseControlProps) => {
popupIsOpen={dataPickerButtonData.popupIsOpen}
isHovered={isHovered}
isConnectedToData={isConnectedToData}
testId={`plus-button-${title}`}
/>
</PropertyLabel>
) : (
Expand Down Expand Up @@ -964,6 +976,7 @@ const RowForObjectControl = React.memo((props: RowForObjectControlProps) => {
popupIsOpen={dataPickerButtonData.popupIsOpen}
isHovered={isHovered}
isConnectedToData={isConnectedToData}
testId={`plus-button-${title}`}
>
{unless(props.disableToggling, <ObjectIndicator open={open} />)}
</PropertyLabelAndPlusButton>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,12 @@ export const DataCartoucheInner = React.forwardRef(
onDelete={onDelete}
onClick={onClick}
onDoubleClick={onDoubleClick}
datatype='renderable' // TODO actually feed the real value here
selected={selected}
inverted={inverted}
testId={testId}
tooltip={contentsToDisplay.label ?? 'DATA'}
role='selection'
source={source}
ref={ref}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import * as EP from '../../../../core/shared/element-path'
import { selectComponentsForTest } from '../../../../utils/utils.test-utils'
import { mouseClickAtPoint } from '../../../canvas/event-helpers.test-utils'
import { renderTestEditorWithCode } from '../../../canvas/ui-jsx.test-utils'

describe('data selector modal', () => {
it('can be opened', async () => {
const editor = await renderTestEditorWithCode(project, 'await-first-dom-report')
await selectComponentsForTest(editor, [EP.fromString('sb/scene/pg:contents')])

await mouseClickAtPoint(editor.renderedDOM.getByTestId('plus-button-title'), { x: 1, y: 1 })

expect(editor.renderedDOM.getByText('Apply')).not.toBeNull()
})
})

const project = `import * as React from 'react'
import {
Scene,
Storyboard,
FlexCol,
FlexRow,
} from 'utopia-api'
const Contents = ({ children, style, title }) => {
return (
<FlexCol data-uid='c-root'>
<h1 data-uid='c-title'>{title}</h1>
<FlexRow {...style} data-uid='c-row'>
{children}
</FlexRow>
</FlexCol>
)
}
export var Playground = ({ style }) => {
const testimonials = [
{ name: 'Bob', review: 'Very OK' },
{ name: 'Bob', review: 'Very OK' },
{ name: 'Bob', review: 'Very OK' },
{ name: 'Bob', review: 'Very OK' },
{ name: 'Bob', review: 'Very OK' },
]
const reviews = {
length: 12,
nodes: [
{ user: 'bob', contents: 'super' },
{ user: 'bob', contents: 'super' },
{ user: 'bob', contents: 'super' },
{ user: 'bob', contents: 'super' },
],
}
const loaded = true
const header = {
title: 'Hello',
subtitle: 'These are the reviews',
}
return <Contents data-uid='contents' title='Hello' />
}
export var storyboard = (
<Storyboard data-uid='sb'>
<Scene
id='playground-scene'
commentId='playground-scene'
style={{
width: 700,
height: 759,
position: 'absolute',
left: 212,
top: 128,
}}
data-label='Playground'
data-uid='scene'
>
<Playground style={{}} data-uid='pg' />
</Scene>
</Storyboard>
)
`
Loading

0 comments on commit 1cb14ef

Please sign in to comment.