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

Feautre/cartouche context menu #5993

Merged
merged 8 commits into from
Jun 19, 2024
Merged
46 changes: 44 additions & 2 deletions editor/src/components/context-menu-wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import {
Submenu as SubmenuComponent,
useContextMenu,
} from 'react-contexify'
import { colorTheme, Icons, UtopiaStyles } from '../uuiui'
import { colorTheme, Icons, OnClickOutsideHOC, UtopiaStyles } from '../uuiui'
import { getControlStyles } from '../uuiui-deps'
import type { ContextMenuItem } from './context-menu-items'
import type { EditorDispatch } from './editor/action-types'
import type { WindowPoint } from '../core/shared/math-utils'
import { windowPoint } from '../core/shared/math-utils'
import { addOpenMenuId, removeOpenMenuId } from '../core/shared/menu-state'
import { createPortal } from 'react-dom'
import { CanvasContextMenuPortalTargetID } from '../core/shared/utils'

interface Submenu<T> {
items: Item<T>[]
Expand Down Expand Up @@ -176,7 +178,8 @@ export const ContextMenu = <T,>({ dispatch, getData, id, items }: ContextMenuPro
)
}

export const ContextMenuWrapper = <T,>({
/** @deprecated use ContextMenuWrapper instead, which is Portaled */
export const ContextMenuWrapper_DEPRECATED = <T,>({
children,
className = '',
data,
Expand Down Expand Up @@ -290,6 +293,7 @@ export const MenuProvider = ({

const onContextMenu = React.useCallback(
(event: React.MouseEvent<HTMLDivElement>) => {
event.stopPropagation()
if (itemsLength <= 0) {
return
}
Expand All @@ -311,3 +315,41 @@ export const MenuProvider = ({
</div>
)
}

export const ContextMenuWrapper = <T,>({
children,
data,
dispatch,
id,
items,
style,
}: {
children?: React.ReactNode
data: T
dispatch?: EditorDispatch
id: string
items: ContextMenuItem<T>[]
style?: React.CSSProperties
}) => {
const { hideAll } = useContextMenu({ id })

const getData = React.useCallback(() => data, [data])

const portalTarget = document.getElementById(CanvasContextMenuPortalTargetID)

return (
<React.Fragment>
<MenuProvider id={id} itemsLength={items.length} key={`${id}-provider`} style={style}>
{children}
</MenuProvider>
{portalTarget != null
? createPortal(
<OnClickOutsideHOC onClickOutside={hideAll}>
<ContextMenu dispatch={dispatch} getData={getData} id={id} items={items} key={id} />
</OnClickOutsideHOC>,
portalTarget,
)
: null}
</React.Fragment>
)
}
6 changes: 3 additions & 3 deletions editor/src/components/filebrowser/fileitem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { Clipboard } from '../../utils/clipboard'
import Utils from '../../utils/utils'
import type { ContextMenuItem } from '../context-menu-items'
import { requireDispatch } from '../context-menu-items'
import { ContextMenuWrapper } from '../context-menu-wrapper'
import { ContextMenuWrapper_DEPRECATED } from '../context-menu-wrapper'
import type { EditorAction, EditorDispatch } from '../editor/action-types'
import * as EditorActions from '../editor/actions/action-creators'
import { ExpandableIndicator } from '../navigator/navigator-item/expandable-indicator'
Expand Down Expand Up @@ -887,14 +887,14 @@ class FileBrowserItemInner extends React.PureComponent<
style={{ width: '100%' }}
key={`${contextMenuID}-wrapper`}
>
<ContextMenuWrapper
<ContextMenuWrapper_DEPRECATED
id={contextMenuID}
dispatch={this.props.dispatch}
items={items}
data={{}}
>
{fileBrowserItem}
</ContextMenuWrapper>
</ContextMenuWrapper_DEPRECATED>
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
RegularControlDescription,
} from '../../../custom-code/internal-property-controls'
import type { ElementPath } from '../../../../core/shared/project-file-types'
import * as EP from '../../../../core/shared/element-path'
import * as PP from '../../../../core/shared/property-path'
import { iconForControlType } from '../../../../uuiui'
import type { ControlForPropProps } from './property-control-controls'
Expand Down Expand Up @@ -66,7 +67,7 @@ export function useChildrenPropOverride(
matchType='partial'
onOpenDataPicker={props.onOpenDataPicker}
onDeleteCartouche={props.onDeleteCartouche}
testId={`cartouche-${PP.toString(props.propPath)}`}
testId={`cartouche-${EP.toString(props.elementPath)}-${PP.toString(props.propPath)}`}
propertyPath={props.propPath}
safeToDelete={props.safeToDelete}
elementPath={props.elementPath}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import type { CartoucheDataType, CartoucheHighlight, CartoucheUIProps } from './
import { CartoucheUI } from './cartouche-ui'
import * as PP from '../../../../core/shared/property-path'
import { AllHtmlEntities } from 'html-entities'
import { ContextMenuWrapper } from '../../../context-menu-wrapper'
import type { ContextMenuItem } from '../../../context-menu-items'
import { optionalMap } from '../../../../core/shared/optional-utils'

const htmlEntities = new AllHtmlEntities()
Expand Down Expand Up @@ -185,7 +187,7 @@ export const DataReferenceCartoucheControl = React.memo(
export type DataReferenceCartoucheContentType = 'value-literal' | 'object-literal' | 'reference'
interface DataCartoucheInnerProps {
onClick: (e: React.MouseEvent) => void
onDoubleClick: (e: React.MouseEvent) => void
onDoubleClick: () => void
selected: boolean
contentsToDisplay: {
type: DataReferenceCartoucheContentType
Expand Down Expand Up @@ -216,6 +218,8 @@ export const DataCartoucheInner = React.forwardRef(
datatype,
} = props

const dispatch = useDispatch()

const onDeleteInner = React.useCallback(
(e: React.MouseEvent) => {
e.stopPropagation()
Expand All @@ -236,26 +240,79 @@ export const DataCartoucheInner = React.forwardRef(
: 'internal'

return (
<CartoucheUI
onDelete={onDelete}
onClick={onClick}
onDoubleClick={onDoubleClick}
datatype={datatype}
selected={selected}
highlight={highlight}
testId={testId}
tooltip={contentsToDisplay.label ?? contentsToDisplay.shortLabel ?? 'DATA'}
role='selection'
source={source}
ref={ref}
badge={props.badge}
<ContextMenuWrapper<ContextMenuItemsData>
id={`cartouche-context-menu-${props.testId}`}
dispatch={dispatch}
items={contextMenuItems}
data={{ openDataPicker: onDoubleClick, deleteCartouche: onDeleteCallback }}
>
{contentsToDisplay.shortLabel ?? contentsToDisplay.label ?? 'DATA'}
</CartoucheUI>
<CartoucheUI
onDelete={onDelete}
onClick={onClick}
onDoubleClick={onDoubleClick}
datatype={datatype}
selected={selected}
highlight={highlight}
testId={testId}
tooltip={contentsToDisplay.label ?? contentsToDisplay.shortLabel ?? 'DATA'}
role='selection'
source={source}
ref={ref}
badge={props.badge}
>
{contentsToDisplay.shortLabel ?? contentsToDisplay.label ?? 'DATA'}
</CartoucheUI>
</ContextMenuWrapper>
)
},
)

type ContextMenuItemsData = {
openDataPicker?: () => void
deleteCartouche?: () => void
}

const Separator = {
name: <div key='separator' className='contexify_separator' />,
enabled: false,
action: NO_OP,
isSeparator: true,
} as const

const contextMenuItems: Array<ContextMenuItem<ContextMenuItemsData>> = [
{
name: 'Replace...',
enabled: (data) => data.openDataPicker != null,
action: (data) => {
data.openDataPicker?.()
},
},
{
name: 'Remove',
enabled: false,
action: (data) => {
data.deleteCartouche?.()
},
},
Separator,
{
name: 'Edit value',
enabled: false,
action: (data) => {},
},
{
name: 'Open in external CMS',
enabled: false,
action: (data) => {},
},
Separator,
{
name: 'Open in code editor',
enabled: false,
action: (data) => {},
},
]

export function getTextContentOfElement(
element: JSXElementChild,
metadata: ElementInstanceMetadata | null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ export const DataSelectorModal = React.memo(
onChange={onSearchFieldValueChange}
ref={searchBoxRef}
value={searchTerm ?? ''}
data-testId='data-selector-modal-search-input'
data-testid='data-selector-modal-search-input'
placeholder='Search data'
style={{
outline: 'none',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
UIRow,
Icn,
} from '../../../../uuiui'
import { ContextMenuWrapper } from '../../../../uuiui-deps'
import { ContextMenuWrapper_DEPRECATED } from '../../../../uuiui-deps'
import { useDispatch } from '../../../editor/store/dispatch-context'
import { useEditorState } from '../../../editor/store/store-hook'
import { ExpandableIndicator } from '../../../navigator/navigator-item/expandable-indicator'
Expand Down Expand Up @@ -299,7 +299,7 @@ const TargetListItem = React.memo((props: TargetListItemProps) => {
}, [onDeleteByIndex, itemIndex])

return (
<ContextMenuWrapper
<ContextMenuWrapper_DEPRECATED
id={`${id}-contextMenu`}
items={[
{
Expand Down Expand Up @@ -366,7 +366,7 @@ const TargetListItem = React.memo((props: TargetListItemProps) => {
</React.Fragment>
)}
</UIRow>
</ContextMenuWrapper>
</ContextMenuWrapper_DEPRECATED>
)
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { traceDataFromElement, dataPathSuccess } from '../../../../core/data-tra
import type { CartoucheDataType } from '../component-section/cartouche-ui'
import { CartoucheInspectorWrapper } from '../component-section/cartouche-control'
import { MapCounter } from '../../../navigator/navigator-item/map-counter'
import * as EP from '../../../../core/shared/element-path'

interface MapListSourceCartoucheProps {
target: ElementPath
Expand Down Expand Up @@ -174,7 +175,7 @@ const MapListSourceCartoucheInner = React.memo(
onDelete={NO_OP}
selected={props.selected}
safeToDelete={false}
testId='list-source-cartouche'
testId={`list-source-cartouche-${EP.toString(target)}`}
contentIsComingFromServer={isDataComingFromHookResult}
datatype={cartoucheDataType}
badge={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import type { PreferredChildComponentDescriptor } from '../../custom-code/intern
import { fixUtopiaElement, generateConsistentUID } from '../../../core/shared/uid-utils'
import { getAllUniqueUids } from '../../../core/model/get-unique-ids'
import { elementFromInsertMenuItem } from '../../editor/insert-callbacks'
import { ContextMenuWrapper } from '../../context-menu-wrapper'
import { ContextMenuWrapper_DEPRECATED } from '../../context-menu-wrapper'
import { BodyMenuOpenClass, assertNever } from '../../../core/shared/utils'
import { type ContextMenuItem } from '../../context-menu-items'
import { FlexRow, Icn, type IcnProps } from '../../../uuiui'
Expand Down Expand Up @@ -848,7 +848,7 @@ const ComponentPickerContextMenuSimple = React.memo<ComponentPickerContextMenuPr
const submenuLabel = (
<FlexRow
style={{ gap: 10, width: 228 }}
data-testId={labelTestIdForComponentIcon(data.name, data.moduleName ?? '', data.icon)}
data-testid={labelTestIdForComponentIcon(data.name, data.moduleName ?? '', data.icon)}
>
<Icn {...iconProps} width={12} height={12} />
{data.name}
Expand All @@ -868,7 +868,12 @@ const ComponentPickerContextMenuSimple = React.memo<ComponentPickerContextMenuPr
.concat([separatorItem, moreItem(wrapperRef, showFullMenu)])

return (
<ContextMenuWrapper items={items} data={{}} id={PreferredMenuId} forwardRef={wrapperRef} />
<ContextMenuWrapper_DEPRECATED
items={items}
data={{}}
id={PreferredMenuId}
forwardRef={wrapperRef}
/>
)
},
)
Expand Down
Loading
Loading