+
+ contentClassName={tooltipContentClassName}
+ >
+ {showRichContent ? (
+
+ ) : (
+
+ )}
+
)
})}
@@ -269,6 +339,12 @@ export function Toolbar({
)
}
+interface ToolbarItemContentsProps extends React.PropsWithChildren {
+ itemConfig: ToolbarItemResolved
+ configCallbackProps: ToolbarItemCallbackProps
+ wrapperClassName?: string
+ contentClassName?: string
+}
/**
* The single button and dropdown button share content, so we extract it here
* It contains a tooltip with the title, description, and links
@@ -277,12 +353,10 @@ export function Toolbar({
const ToolbarItemTooltip = memo(function ToolbarItemContents({
itemConfig,
configCallbackProps,
-}: {
- itemConfig: ToolbarItemResolved
- configCallbackProps: ToolbarItemCallbackProps
-}) {
- const { state } = useModelingContext()
-
+ wrapperClassName = '',
+ contentClassName = '',
+ children,
+}: ToolbarItemContentsProps) {
useHotkeys(
itemConfig.hotkey || '',
() => {
@@ -305,11 +379,50 @@ const ToolbarItemTooltip = memo(function ToolbarItemContents({
? ({ '-webkit-app-region': 'no-drag' } as React.CSSProperties)
: {}
}
+ hoverOnly
position="bottom"
- wrapperClassName="!p-4 !pointer-events-auto"
- contentClassName="!text-left text-wrap !text-xs !p-0 !pb-2 flex gap-2 !max-w-none !w-72 flex-col items-stretch"
+ wrapperClassName={'!p-4 !pointer-events-auto ' + wrapperClassName}
+ contentClassName={contentClassName}
+ delay={0}
>
+ {children}
+
+ )
+})
+
+const ToolbarItemTooltipShortContent = ({
+ status,
+ title,
+ hotkey,
+}: {
+ status: string
+ title: string
+ hotkey?: string | string[]
+}) => (
+
+ {title}
+ {hotkey && (
+ {hotkey}
+ )}
+
+)
+
+const ToolbarItemTooltipRichContent = ({
+ itemConfig,
+}: {
+ itemConfig: ToolbarItemResolved
+}) => {
+ const { state } = useModelingContext()
+ return (
+ <>
+ {itemConfig.icon && (
+
+ )}
>
)}
-
+ >
)
-})
+}
diff --git a/src/clientSideScene/ClientSideSceneComp.tsx b/src/clientSideScene/ClientSideSceneComp.tsx
index 6d2bfebd9..5132b431f 100644
--- a/src/clientSideScene/ClientSideSceneComp.tsx
+++ b/src/clientSideScene/ClientSideSceneComp.tsx
@@ -25,13 +25,13 @@ import {
CallExpression,
PathToNode,
Program,
- SourceRange,
Expr,
parse,
recast,
defaultSourceRange,
resultIsOk,
ProgramMemory,
+ topLevelRange,
} from 'lang/wasm'
import { CustomIcon, CustomIconName } from 'components/CustomIcon'
import { ConstrainInfo } from 'lang/std/stdTypes'
@@ -46,8 +46,8 @@ import {
} from 'lang/modifyAst'
import { ActionButton } from 'components/ActionButton'
import { err, reportRejection, trap } from 'lib/trap'
-import { useCommandsContext } from 'hooks/useCommandsContext'
import { Node } from 'wasm-lib/kcl/bindings/Node'
+import { commandBarActor } from 'machines/commandBarMachine'
function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } {
const [isCamMoving, setIsCamMoving] = useState(false)
@@ -510,7 +510,6 @@ const ConstraintSymbol = ({
constrainInfo: ConstrainInfo
verticalPosition: 'top' | 'bottom'
}) => {
- const { commandBarSend } = useCommandsContext()
const { context } = useModelingContext()
const varNameMap: {
[key in ConstrainInfo['type']]: {
@@ -600,8 +599,8 @@ const ConstraintSymbol = ({
if (err(_node)) return
const node = _node.node
- const range: SourceRange = node
- ? [node.start, node.end, true]
+ const range = node
+ ? topLevelRange(node.start, node.end)
: defaultSourceRange()
if (_type === 'intersectionTag') return null
@@ -630,7 +629,7 @@ const ConstraintSymbol = ({
// disabled={implicitDesc} TODO why does this change styles that are hard to override?
onClick={toSync(async () => {
if (!isConstrained) {
- commandBarSend({
+ commandBarActor.send({
type: 'Find and select command',
data: {
name: 'Constrain with named value',
@@ -756,7 +755,6 @@ export const CamDebugSettings = () => {
sceneInfra.camControls.reactCameraProperties
)
const [fov, setFov] = useState(12)
- const { commandBarSend } = useCommandsContext()
useEffect(() => {
sceneInfra.camControls.setReactCameraPropertiesCallback(setCamSettings)
@@ -775,7 +773,7 @@ export const CamDebugSettings = () => {
type="checkbox"
checked={camSettings.type === 'perspective'}
onChange={() =>
- commandBarSend({
+ commandBarActor.send({
type: 'Find and select command',
data: {
groupId: 'settings',
diff --git a/src/clientSideScene/sceneEntities.ts b/src/clientSideScene/sceneEntities.ts
index b5544f8a8..66cd0b594 100644
--- a/src/clientSideScene/sceneEntities.ts
+++ b/src/clientSideScene/sceneEntities.ts
@@ -59,6 +59,7 @@ import {
sourceRangeFromRust,
resultIsOk,
SourceRange,
+ topLevelRange,
} from 'lang/wasm'
import { calculate_circle_from_3_points } from '../wasm-lib/pkg/wasm_lib'
import {
@@ -628,7 +629,7 @@ export class SceneEntities {
const startRange = _node1.node.start
const endRange = _node1.node.end
- const sourceRange: SourceRange = [startRange, endRange, true]
+ const sourceRange = topLevelRange(startRange, endRange)
const selection: Selections = computeSelectionFromSourceRangeAndAST(
sourceRange,
maybeModdedAst
@@ -1397,23 +1398,23 @@ export class SceneEntities {
const arg0 = arg(kclCircle3PointArgs[0])
if (!arg0) return kclManager.ast
- arg0[0].value = points[0].x
+ arg0[0].value = { value: points[0].x, suffix: 'None' }
arg0[0].raw = points[0].x.toString()
- arg0[1].value = points[0].y
+ arg0[1].value = { value: points[0].y, suffix: 'None' }
arg0[1].raw = points[0].y.toString()
const arg1 = arg(kclCircle3PointArgs[1])
if (!arg1) return kclManager.ast
- arg1[0].value = points[1].x
+ arg1[0].value = { value: points[1].x, suffix: 'None' }
arg1[0].raw = points[1].x.toString()
- arg1[1].value = points[1].y
+ arg1[1].value = { value: points[1].y, suffix: 'None' }
arg1[1].raw = points[1].y.toString()
const arg2 = arg(kclCircle3PointArgs[2])
if (!arg2) return kclManager.ast
- arg2[0].value = points[2].x
+ arg2[0].value = { value: points[2].x, suffix: 'None' }
arg2[0].raw = points[2].x.toString()
- arg2[1].value = points[2].y
+ arg2[1].value = { value: points[2].y, suffix: 'None' }
arg2[1].raw = points[2].y.toString()
const astSnapshot = structuredClone(kclManager.ast)
@@ -2012,7 +2013,7 @@ export class SceneEntities {
kclManager.programMemory,
{
type: 'sourceRange',
- sourceRange: [node.start, node.end, true],
+ sourceRange: topLevelRange(node.start, node.end),
},
getChangeSketchInput()
)
@@ -2050,8 +2051,8 @@ export class SceneEntities {
)
if (!(sk instanceof Reason)) {
sketch = sk
- } else if ((maybeSketch as Solid).sketch) {
- sketch = (maybeSketch as Solid).sketch
+ } else if (maybeSketch && (maybeSketch.value as Solid)?.sketch) {
+ sketch = (maybeSketch.value as Solid).sketch
}
if (!sketch) return
@@ -2263,7 +2264,7 @@ export class SceneEntities {
)
if (trap(_node, { suppress: true })) return
const node = _node.node
- editorManager.setHighlightRange([[node.start, node.end, true]])
+ editorManager.setHighlightRange([topLevelRange(node.start, node.end)])
const yellow = 0xffff00
colorSegment(selected, yellow)
const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE)
@@ -2540,7 +2541,7 @@ export function sketchFromPathToNode({
const varDec = _varDec.node
const result = programMemory.get(varDec?.id?.name || '')
if (result?.type === 'Solid') {
- return result.sketch
+ return result.value.sketch
}
const sg = sketchFromKclValue(result, varDec?.id?.name)
if (err(sg)) {
diff --git a/src/clientSideScene/segments.ts b/src/clientSideScene/segments.ts
index 0a152a62b..dfb3b6802 100644
--- a/src/clientSideScene/segments.ts
+++ b/src/clientSideScene/segments.ts
@@ -61,6 +61,7 @@ import { SegmentInputs } from 'lang/std/stdTypes'
import { err } from 'lib/trap'
import { editorManager, sceneInfra } from 'lib/singletons'
import { Selections } from 'lib/selections'
+import { commandBarActor } from 'machines/commandBarMachine'
interface CreateSegmentArgs {
input: SegmentInputs
@@ -847,7 +848,7 @@ function createLengthIndicator({
})
// Command Bar
- editorManager.commandBarSend({
+ commandBarActor.send({
type: 'Find and select command',
data: {
name: 'Constrain length',
diff --git a/src/components/ActionButtonDropdown.tsx b/src/components/ActionButtonDropdown.tsx
index 21ee82f91..18973fb51 100644
--- a/src/components/ActionButtonDropdown.tsx
+++ b/src/components/ActionButtonDropdown.tsx
@@ -1,9 +1,11 @@
import { Popover } from '@headlessui/react'
import { ActionButtonProps } from './ActionButton'
import { CustomIcon } from './CustomIcon'
+import Tooltip from './Tooltip'
type ActionButtonSplitProps = ActionButtonProps & { Element: 'button' } & {
name?: string
+ dropdownTooltipText?: string
splitMenuItems: {
id: string
label: string
@@ -17,6 +19,7 @@ type ActionButtonSplitProps = ActionButtonProps & { Element: 'button' } & {
export function ActionButtonDropdown({
splitMenuItems,
className,
+ dropdownTooltipText = 'More tools',
children,
...props
}: ActionButtonSplitProps) {
@@ -26,7 +29,14 @@ export function ActionButtonDropdown({
{({ close }) => (
<>
{children}
-
+
{props.name ? props.name + ': ' : ''}open menu
+
+ {dropdownTooltipText}
+
{
- editorManager.setHighlightRange([[obj?.start || 0, obj.end, true]])
+ editorManager.setHighlightRange([
+ topLevelRange(obj?.start || 0, obj.end),
+ ])
e.stopPropagation()
}}
onMouseMove={(e) => {
e.stopPropagation()
- editorManager.setHighlightRange([[obj?.start || 0, obj.end, true]])
+ editorManager.setHighlightRange([
+ topLevelRange(obj?.start || 0, obj.end),
+ ])
}}
onClick={(e) => {
- const range: [number, number, boolean] = [
- obj?.start || 0,
- obj.end || 0,
- true,
- ]
+ const range = topLevelRange(obj?.start || 0, obj.end || 0)
const idInfo = codeToIdSelections([
{ codeRef: codeRefFromRange(range, kclManager.ast) },
])[0]
diff --git a/src/components/CommandBar/CommandArgOptionInput.tsx b/src/components/CommandBar/CommandArgOptionInput.tsx
index 0391ae479..990a31162 100644
--- a/src/components/CommandBar/CommandArgOptionInput.tsx
+++ b/src/components/CommandBar/CommandArgOptionInput.tsx
@@ -1,8 +1,8 @@
import { Combobox } from '@headlessui/react'
import { useSelector } from '@xstate/react'
import Fuse from 'fuse.js'
-import { useCommandsContext } from 'hooks/useCommandsContext'
import { CommandArgument, CommandArgumentOption } from 'lib/commandTypes'
+import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
import { useEffect, useMemo, useRef, useState } from 'react'
import { AnyStateMachine, StateFrom } from 'xstate'
@@ -23,7 +23,7 @@ function CommandArgOptionInput({
placeholder?: string
}) {
const actorContext = useSelector(arg.machineActor, contextSelector)
- const { commandBarSend, commandBarState } = useCommandsContext()
+ const commandBarState = useCommandBarState()
const resolvedOptions = useMemo(
() =>
typeof arg.options === 'function'
@@ -134,6 +134,7 @@ function CommandArgOptionInput({
!event.target.disabled && setQuery(event.target.value)
@@ -141,7 +142,7 @@ function CommandArgOptionInput({
className="flex-grow px-2 py-1 border-b border-b-chalkboard-100 dark:border-b-chalkboard-80 !bg-transparent focus:outline-none"
onKeyDown={(event) => {
if (event.metaKey && event.key === 'k')
- commandBarSend({ type: 'Close' })
+ commandBarActor.send({ type: 'Close' })
if (event.key === 'Backspace' && !event.currentTarget.value) {
stepBack()
}
diff --git a/src/components/CommandBar/CommandBar.tsx b/src/components/CommandBar/CommandBar.tsx
index b2a30a8b9..c9c819737 100644
--- a/src/components/CommandBar/CommandBar.tsx
+++ b/src/components/CommandBar/CommandBar.tsx
@@ -1,6 +1,5 @@
import { Dialog, Popover, Transition } from '@headlessui/react'
import { Fragment, useEffect } from 'react'
-import { useCommandsContext } from 'hooks/useCommandsContext'
import CommandBarArgument from './CommandBarArgument'
import CommandComboBox from '../CommandComboBox'
import CommandBarReview from './CommandBarReview'
@@ -8,12 +7,13 @@ import { useLocation } from 'react-router-dom'
import useHotkeyWrapper from 'lib/hotkeyWrapper'
import { CustomIcon } from 'components/CustomIcon'
import Tooltip from 'components/Tooltip'
+import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
export const COMMAND_PALETTE_HOTKEY = 'mod+k'
export const CommandBar = () => {
const { pathname } = useLocation()
- const { commandBarState, commandBarSend } = useCommandsContext()
+ const commandBarState = useCommandBarState()
const {
context: { selectedCommand, currentArgument, commands },
} = commandBarState
@@ -23,16 +23,16 @@ export const CommandBar = () => {
// Close the command bar when navigating
useEffect(() => {
if (commandBarState.matches('Closed')) return
- commandBarSend({ type: 'Close' })
+ commandBarActor.send({ type: 'Close' })
}, [pathname])
// Hook up keyboard shortcuts
useHotkeyWrapper([COMMAND_PALETTE_HOTKEY], () => {
if (commandBarState.context.commands.length === 0) return
if (commandBarState.matches('Closed')) {
- commandBarSend({ type: 'Open' })
+ commandBarActor.send({ type: 'Open' })
} else {
- commandBarSend({ type: 'Close' })
+ commandBarActor.send({ type: 'Close' })
}
})
@@ -52,14 +52,14 @@ export const CommandBar = () => {
...entries[entries.length - 1][1],
}
- commandBarSend({
+ commandBarActor.send({
type: 'Edit argument',
data: {
arg: currentArg,
},
})
} else {
- commandBarSend({ type: 'Deselect command' })
+ commandBarActor.send({ type: 'Deselect command' })
}
} else {
const entries = Object.entries(selectedCommand?.args || {})
@@ -68,9 +68,9 @@ export const CommandBar = () => {
)
if (index === 0) {
- commandBarSend({ type: 'Deselect command' })
+ commandBarActor.send({ type: 'Deselect command' })
} else {
- commandBarSend({
+ commandBarActor.send({
type: 'Change current argument',
data: {
arg: { name: entries[index - 1][0], ...entries[index - 1][1] },
@@ -85,14 +85,14 @@ export const CommandBar = () => {
show={!commandBarState.matches('Closed') || false}
afterLeave={() => {
if (selectedCommand?.onCancel) selectedCommand.onCancel()
- commandBarSend({ type: 'Clear' })
+ commandBarActor.send({ type: 'Clear' })
}}
as={Fragment}
>
{
- commandBarSend({ type: 'Close' })
+ commandBarActor.send({ type: 'Close' })
}}
className={
'fixed inset-0 z-50 overflow-y-auto pb-4 pt-1 ' +
@@ -122,7 +122,7 @@ export const CommandBar = () => {
)
)}
diff --git a/src/components/ModelingSidebar/ModelingPanes/KclEditorMenu.module.css b/src/components/ModelingSidebar/ModelingPanes/KclEditorMenu.module.css
index 00e01f105..7c23bade9 100644
--- a/src/components/ModelingSidebar/ModelingPanes/KclEditorMenu.module.css
+++ b/src/components/ModelingSidebar/ModelingPanes/KclEditorMenu.module.css
@@ -3,6 +3,7 @@
@apply font-mono !no-underline text-xs font-bold select-none text-chalkboard-90;
@apply ui-active:bg-primary/10 ui-active:text-primary ui-active:text-inherit;
@apply transition-colors ease-out;
+ @apply m-0;
}
:global(.dark) .button {
diff --git a/src/components/ModelingSidebar/ModelingPanes/KclEditorMenu.tsx b/src/components/ModelingSidebar/ModelingPanes/KclEditorMenu.tsx
index bda5c983f..ba462d5d4 100644
--- a/src/components/ModelingSidebar/ModelingPanes/KclEditorMenu.tsx
+++ b/src/components/ModelingSidebar/ModelingPanes/KclEditorMenu.tsx
@@ -9,12 +9,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { kclManager } from 'lib/singletons'
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
import { reportRejection } from 'lib/trap'
-import { useCommandsContext } from 'hooks/useCommandsContext'
+import { commandBarActor } from 'machines/commandBarMachine'
export const KclEditorMenu = ({ children }: PropsWithChildren) => {
const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } =
useConvertToVariable()
- const { commandBarSend } = useCommandsContext()
return (