import { WheelEvent, useRef, useMemo } from 'react' import { isCursorInSketchCommandRange } from 'lang/util' import { engineCommandManager, kclManager } from 'lib/singletons' import { useModelingContext } from 'hooks/useModelingContext' import { useCommandsContext } from 'hooks/useCommandsContext' import { useNetworkContext } from 'hooks/useNetworkContext' import { NetworkHealthState } from 'hooks/useNetworkStatus' import { ActionButton } from 'components/ActionButton' import { isSingleCursorInPipe } from 'lang/queryAst' import { useKclContext } from 'lang/KclProvider' import { useStore } from 'useStore' import { ActionButtonDropdown } from 'components/ActionButtonDropdown' import { useHotkeys } from 'react-hotkeys-hook' import Tooltip from 'components/Tooltip' export function Toolbar({ className = '', ...props }: React.HTMLAttributes) { const { state, send, context } = useModelingContext() const { commandBarSend } = useCommandsContext() const iconClassName = 'group-disabled:text-chalkboard-50 group-enabled:group-hover:!text-primary dark:group-enabled:group-hover:!text-inherit group-pressed:!text-chalkboard-10 group-ui-open:!text-chalkboard-10 dark:group-ui-open:!text-chalkboard-10' const bgClassName = 'group-disabled:!bg-transparent group-enabled:group-hover:bg-primary/10 dark:group-enabled:group-hover:bg-primary group-pressed:bg-primary group-ui-open:bg-primary' const buttonClassName = 'bg-chalkboard-10 dark:bg-chalkboard-100 enabled:hover:bg-chalkboard-10 dark:enabled:hover:bg-chalkboard-100 pressed:!border-primary ui-open:!border-primary' const pathId = useMemo(() => { if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast)) { return false } return isCursorInSketchCommandRange( engineCommandManager.artifactMap, context.selectionRanges ) }, [engineCommandManager.artifactMap, context.selectionRanges]) const toolbarButtonsRef = useRef(null) const { overallState } = useNetworkContext() const { isExecuting } = useKclContext() const { isStreamReady } = useStore((s) => ({ isStreamReady: s.isStreamReady, })) const disableAllButtons = (overallState !== NetworkHealthState.Ok && overallState !== NetworkHealthState.Weak) || isExecuting || !isStreamReady useHotkeys( 'l', () => state.matches('Sketch.Line tool') ? send('CancelSketch') : send('Equip Line tool'), { enabled: !disableAllButtons, scopes: ['sketch'] } ) useHotkeys( 'a', () => state.matches('Sketch.Tangential arc to') ? send('CancelSketch') : send('Equip tangential arc to'), { enabled: !disableAllButtons, scopes: ['sketch'] } ) useHotkeys( 'r', () => state.matches('Sketch.Rectangle tool') ? send('CancelSketch') : send('Equip rectangle tool'), { enabled: !disableAllButtons, scopes: ['sketch'] } ) useHotkeys( 's', () => state.nextEvents.includes('Enter sketch') && pathId ? send({ type: 'Enter sketch' }) : send({ type: 'Enter sketch', data: { forceNewSketch: true } }), { enabled: !disableAllButtons, scopes: ['modeling'] } ) useHotkeys( 'esc', () => state.matches('Sketch.SketchIdle') ? send('Cancel') : send('CancelSketch'), { enabled: !disableAllButtons, scopes: ['sketch'] } ) useHotkeys( 'e', () => commandBarSend({ type: 'Find and select command', data: { name: 'Extrude', ownerMachine: 'modeling' }, }), { enabled: !disableAllButtons, scopes: ['modeling'] } ) function handleToolbarButtonsWheelEvent(ev: WheelEvent) { const span = toolbarButtonsRef.current if (!span) { return } span.scrollLeft = span.scrollLeft += ev.deltaY } const nextEvents = useMemo(() => state.nextEvents, [state.nextEvents]) const splitMenuItems = useMemo( () => nextEvents .filter( (eventName) => eventName.includes('Make segment') || eventName.includes('Constrain') ) .sort((a, b) => { const aisEnabled = nextEvents .filter((event) => state.can(event as any)) .includes(a) const bIsEnabled = nextEvents .filter((event) => state.can(event as any)) .includes(b) if (aisEnabled && !bIsEnabled) { return -1 } if (!aisEnabled && bIsEnabled) { return 1 } return 0 }) .map((eventName) => ({ label: eventName .replace('Make segment ', '') .replace('Constrain ', ''), onClick: () => send(eventName), disabled: !nextEvents .filter((event) => state.can(event as any)) .includes(eventName) || disableAllButtons, })), [JSON.stringify(nextEvents), state] ) return (
    {nextEvents.includes('Enter sketch') && (
  • send({ type: 'Enter sketch', data: { forceNewSketch: true } }) } iconStart={{ icon: 'sketch', iconClassName, bgClassName, }} disabled={disableAllButtons} > Start Sketch Shortcut: S
  • )} {nextEvents.includes('Enter sketch') && pathId && (
  • send({ type: 'Enter sketch' })} iconStart={{ icon: 'sketch', iconClassName, bgClassName, }} disabled={disableAllButtons} > Edit Sketch Shortcut: S
  • )} {nextEvents.includes('Cancel') && !state.matches('idle') && (
  • send({ type: 'Cancel' })} iconStart={{ icon: 'arrowLeft', iconClassName, bgClassName, }} disabled={disableAllButtons} > Exit Sketch Shortcut: Esc
  • )} {state.matches('Sketch') && !state.matches('idle') && ( <>
  • state?.matches('Sketch.Line tool') ? send('CancelSketch') : send('Equip Line tool') } aria-pressed={state?.matches('Sketch.Line tool')} iconStart={{ icon: 'line', iconClassName, bgClassName, }} disabled={disableAllButtons} > Line Shortcut: L
  • state.matches('Sketch.Tangential arc to') ? send('CancelSketch') : send('Equip tangential arc to') } aria-pressed={state.matches('Sketch.Tangential arc to')} iconStart={{ icon: 'arc', iconClassName, bgClassName, }} disabled={ (!state.can('Equip tangential arc to') && !state.matches('Sketch.Tangential arc to')) || disableAllButtons } > Tangential Arc Shortcut: A
  • state.matches('Sketch.Rectangle tool') ? send('CancelSketch') : send('Equip rectangle tool') } aria-pressed={state.matches('Sketch.Rectangle tool')} iconStart={{ icon: 'rectangle', iconClassName, bgClassName, }} disabled={ (!state.can('Equip rectangle tool') && !state.matches('Sketch.Rectangle tool')) || disableAllButtons } title={ state.can('Equip rectangle tool') ? 'Rectangle' : 'Can only be used when a sketch is empty currently' } > Rectangle Shortcut: R
  • )} {state.matches('Sketch.SketchIdle') && nextEvents.filter( (eventName) => eventName.includes('Make segment') || eventName.includes('Constrain') ).length > 0 && ( Constrain )} {state.matches('idle') && (
  • commandBarSend({ type: 'Find and select command', data: { name: 'Extrude', ownerMachine: 'modeling' }, }) } disabled={!state.can('Extrude') || disableAllButtons} title={ state.can('Extrude') ? 'extrude' : 'sketches need to be closed, or not already extruded' } iconStart={{ icon: 'extrude', iconClassName, bgClassName, }} > Extrude Shortcut: E
  • )}
) }