Refactor: decouple command palette actor from React (#5108)
* Convert commandBarMachine to standalone actor * Switch all uses of CommandBarProvider pattern to use actor and selector snapshots directly
This commit is contained in:
		@ -31,7 +31,6 @@ import {
 | 
			
		||||
  settingsLoader,
 | 
			
		||||
  telemetryLoader,
 | 
			
		||||
} from 'lib/routeLoaders'
 | 
			
		||||
import { CommandBarProvider } from 'components/CommandBar/CommandBarProvider'
 | 
			
		||||
import SettingsAuthProvider from 'components/SettingsAuthProvider'
 | 
			
		||||
import LspProvider from 'components/LspProvider'
 | 
			
		||||
import { KclContextProvider } from 'lang/KclProvider'
 | 
			
		||||
@ -58,23 +57,21 @@ const router = createRouter([
 | 
			
		||||
    /* Make sure auth is the outermost provider or else we will have
 | 
			
		||||
     * inefficient re-renders, use the react profiler to see. */
 | 
			
		||||
    element: (
 | 
			
		||||
      <CommandBarProvider>
 | 
			
		||||
        <RouteProvider>
 | 
			
		||||
          <SettingsAuthProvider>
 | 
			
		||||
            <LspProvider>
 | 
			
		||||
              <ProjectsContextProvider>
 | 
			
		||||
                <KclContextProvider>
 | 
			
		||||
                  <AppStateProvider>
 | 
			
		||||
                    <MachineManagerProvider>
 | 
			
		||||
                      <Outlet />
 | 
			
		||||
                    </MachineManagerProvider>
 | 
			
		||||
                  </AppStateProvider>
 | 
			
		||||
                </KclContextProvider>
 | 
			
		||||
              </ProjectsContextProvider>
 | 
			
		||||
            </LspProvider>
 | 
			
		||||
          </SettingsAuthProvider>
 | 
			
		||||
        </RouteProvider>
 | 
			
		||||
      </CommandBarProvider>
 | 
			
		||||
      <RouteProvider>
 | 
			
		||||
        <SettingsAuthProvider>
 | 
			
		||||
          <LspProvider>
 | 
			
		||||
            <ProjectsContextProvider>
 | 
			
		||||
              <KclContextProvider>
 | 
			
		||||
                <AppStateProvider>
 | 
			
		||||
                  <MachineManagerProvider>
 | 
			
		||||
                    <Outlet />
 | 
			
		||||
                  </MachineManagerProvider>
 | 
			
		||||
                </AppStateProvider>
 | 
			
		||||
              </KclContextProvider>
 | 
			
		||||
            </ProjectsContextProvider>
 | 
			
		||||
          </LspProvider>
 | 
			
		||||
        </SettingsAuthProvider>
 | 
			
		||||
      </RouteProvider>
 | 
			
		||||
    ),
 | 
			
		||||
    errorElement: <ErrorPage />,
 | 
			
		||||
    children: [
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,6 @@ import { useRef, useMemo, memo, useCallback, useState } 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'
 | 
			
		||||
@ -22,13 +21,13 @@ import {
 | 
			
		||||
} from 'lib/toolbar'
 | 
			
		||||
import { isDesktop } from 'lib/isDesktop'
 | 
			
		||||
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
 | 
			
		||||
import { commandBarActor } from 'machines/commandBarMachine'
 | 
			
		||||
 | 
			
		||||
export function Toolbar({
 | 
			
		||||
  className = '',
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.HTMLAttributes<HTMLElement>) {
 | 
			
		||||
  const { state, send, context } = useModelingContext()
 | 
			
		||||
  const { commandBarSend } = useCommandsContext()
 | 
			
		||||
  const iconClassName =
 | 
			
		||||
    'group-disabled:text-chalkboard-50 !text-inherit dark:group-enabled:group-hover:!text-inherit'
 | 
			
		||||
  const bgClassName = '!bg-transparent'
 | 
			
		||||
@ -71,10 +70,9 @@ export function Toolbar({
 | 
			
		||||
    () => ({
 | 
			
		||||
      modelingState: state,
 | 
			
		||||
      modelingSend: send,
 | 
			
		||||
      commandBarSend,
 | 
			
		||||
      sketchPathId,
 | 
			
		||||
    }),
 | 
			
		||||
    [state, send, commandBarSend, sketchPathId]
 | 
			
		||||
    [state, send, commandBarActor.send, sketchPathId]
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  const tooltipContentClassName = !showRichContent
 | 
			
		||||
 | 
			
		||||
@ -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']]: {
 | 
			
		||||
@ -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',
 | 
			
		||||
 | 
			
		||||
@ -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',
 | 
			
		||||
 | 
			
		||||
@ -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'
 | 
			
		||||
@ -142,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()
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
@ -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}
 | 
			
		||||
    >
 | 
			
		||||
      <WrapperComponent
 | 
			
		||||
        open={!commandBarState.matches('Closed') || isSelectionArgument}
 | 
			
		||||
        onClose={() => {
 | 
			
		||||
          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 = () => {
 | 
			
		||||
              )
 | 
			
		||||
            )}
 | 
			
		||||
            <button
 | 
			
		||||
              onClick={() => commandBarSend({ type: 'Close' })}
 | 
			
		||||
              onClick={() => commandBarActor.send({ type: 'Close' })}
 | 
			
		||||
              className="group block !absolute left-auto right-full top-[-3px] m-2.5 p-0 border-none bg-transparent hover:bg-transparent"
 | 
			
		||||
            >
 | 
			
		||||
              <CustomIcon
 | 
			
		||||
 | 
			
		||||
@ -2,13 +2,13 @@ import CommandArgOptionInput from './CommandArgOptionInput'
 | 
			
		||||
import CommandBarBasicInput from './CommandBarBasicInput'
 | 
			
		||||
import CommandBarSelectionInput from './CommandBarSelectionInput'
 | 
			
		||||
import { CommandArgument } from 'lib/commandTypes'
 | 
			
		||||
import { useCommandsContext } from 'hooks/useCommandsContext'
 | 
			
		||||
import CommandBarHeader from './CommandBarHeader'
 | 
			
		||||
import CommandBarKclInput from './CommandBarKclInput'
 | 
			
		||||
import CommandBarTextareaInput from './CommandBarTextareaInput'
 | 
			
		||||
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
 | 
			
		||||
 | 
			
		||||
function CommandBarArgument({ stepBack }: { stepBack: () => void }) {
 | 
			
		||||
  const { commandBarState, commandBarSend } = useCommandsContext()
 | 
			
		||||
  const commandBarState = useCommandBarState()
 | 
			
		||||
  const {
 | 
			
		||||
    context: { currentArgument },
 | 
			
		||||
  } = commandBarState
 | 
			
		||||
@ -16,7 +16,7 @@ function CommandBarArgument({ stepBack }: { stepBack: () => void }) {
 | 
			
		||||
  function onSubmit(data: unknown) {
 | 
			
		||||
    if (!currentArgument) return
 | 
			
		||||
 | 
			
		||||
    commandBarSend({
 | 
			
		||||
    commandBarActor.send({
 | 
			
		||||
      type: 'Submit argument',
 | 
			
		||||
      data: {
 | 
			
		||||
        [currentArgument.name]: data,
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import { useCommandsContext } from 'hooks/useCommandsContext'
 | 
			
		||||
import { CommandArgument } from 'lib/commandTypes'
 | 
			
		||||
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
 | 
			
		||||
import { useEffect, useRef } from 'react'
 | 
			
		||||
import { useHotkeys } from 'react-hotkeys-hook'
 | 
			
		||||
 | 
			
		||||
@ -15,8 +15,8 @@ function CommandBarBasicInput({
 | 
			
		||||
  stepBack: () => void
 | 
			
		||||
  onSubmit: (event: unknown) => void
 | 
			
		||||
}) {
 | 
			
		||||
  const { commandBarSend, commandBarState } = useCommandsContext()
 | 
			
		||||
  useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' }))
 | 
			
		||||
  const commandBarState = useCommandBarState()
 | 
			
		||||
  useHotkeys('mod + k, mod + /', () => commandBarActor.send({ type: 'Close' }))
 | 
			
		||||
  const inputRef = useRef<HTMLInputElement>(null)
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,3 @@
 | 
			
		||||
import { useCommandsContext } from 'hooks/useCommandsContext'
 | 
			
		||||
import { CustomIcon } from '../CustomIcon'
 | 
			
		||||
import React, { useState } from 'react'
 | 
			
		||||
import { ActionButton } from '../ActionButton'
 | 
			
		||||
@ -7,9 +6,10 @@ import { useHotkeys } from 'react-hotkeys-hook'
 | 
			
		||||
import { KclCommandValue, KclExpressionWithVariable } from 'lib/commandTypes'
 | 
			
		||||
import Tooltip from 'components/Tooltip'
 | 
			
		||||
import { roundOff } from 'lib/utils'
 | 
			
		||||
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
 | 
			
		||||
 | 
			
		||||
function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
 | 
			
		||||
  const { commandBarState, commandBarSend } = useCommandsContext()
 | 
			
		||||
  const commandBarState = useCommandBarState()
 | 
			
		||||
  const {
 | 
			
		||||
    context: { selectedCommand, currentArgument, argumentsToSubmit },
 | 
			
		||||
  } = commandBarState
 | 
			
		||||
@ -49,7 +49,7 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
 | 
			
		||||
        ]
 | 
			
		||||
        const arg = selectedCommand?.args[argName]
 | 
			
		||||
        if (!argName || !arg) return
 | 
			
		||||
        commandBarSend({
 | 
			
		||||
        commandBarActor.send({
 | 
			
		||||
          type: 'Change current argument',
 | 
			
		||||
          data: { arg: { ...arg, name: argName } },
 | 
			
		||||
        })
 | 
			
		||||
@ -100,7 +100,7 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
 | 
			
		||||
                    }
 | 
			
		||||
                    disabled={!isReviewing && currentArgument?.name === argName}
 | 
			
		||||
                    onClick={() => {
 | 
			
		||||
                      commandBarSend({
 | 
			
		||||
                      commandBarActor.send({
 | 
			
		||||
                        type: isReviewing
 | 
			
		||||
                          ? 'Edit argument'
 | 
			
		||||
                          : 'Change current argument',
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,6 @@ import {
 | 
			
		||||
} from '@codemirror/autocomplete'
 | 
			
		||||
import { EditorView, keymap, ViewUpdate } from '@codemirror/view'
 | 
			
		||||
import { CustomIcon } from 'components/CustomIcon'
 | 
			
		||||
import { useCommandsContext } from 'hooks/useCommandsContext'
 | 
			
		||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
 | 
			
		||||
import { CommandArgument, KclCommandValue } from 'lib/commandTypes'
 | 
			
		||||
import { getSystemTheme } from 'lib/theme'
 | 
			
		||||
@ -20,6 +19,7 @@ import styles from './CommandBarKclInput.module.css'
 | 
			
		||||
import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst'
 | 
			
		||||
import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor'
 | 
			
		||||
import { useSelector } from '@xstate/react'
 | 
			
		||||
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
 | 
			
		||||
 | 
			
		||||
const machineContextSelector = (snapshot?: {
 | 
			
		||||
  context: Record<string, unknown>
 | 
			
		||||
@ -37,7 +37,7 @@ function CommandBarKclInput({
 | 
			
		||||
  stepBack: () => void
 | 
			
		||||
  onSubmit: (event: unknown) => void
 | 
			
		||||
}) {
 | 
			
		||||
  const { commandBarSend, commandBarState } = useCommandsContext()
 | 
			
		||||
  const commandBarState = useCommandBarState()
 | 
			
		||||
  const previouslySetValue = commandBarState.context.argumentsToSubmit[
 | 
			
		||||
    arg.name
 | 
			
		||||
  ] as KclCommandValue | undefined
 | 
			
		||||
@ -82,7 +82,7 @@ function CommandBarKclInput({
 | 
			
		||||
      false
 | 
			
		||||
  )
 | 
			
		||||
  const [canSubmit, setCanSubmit] = useState(true)
 | 
			
		||||
  useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' }))
 | 
			
		||||
  useHotkeys('mod + k, mod + /', () => commandBarActor.send({ type: 'Close' }))
 | 
			
		||||
  const editorRef = useRef<HTMLDivElement>(null)
 | 
			
		||||
 | 
			
		||||
  const {
 | 
			
		||||
 | 
			
		||||
@ -1,43 +0,0 @@
 | 
			
		||||
import { createActorContext } from '@xstate/react'
 | 
			
		||||
import { editorManager } from 'lib/singletons'
 | 
			
		||||
import { commandBarMachine } from 'machines/commandBarMachine'
 | 
			
		||||
import { useEffect } from 'react'
 | 
			
		||||
 | 
			
		||||
export const CommandsContext = createActorContext(
 | 
			
		||||
  commandBarMachine.provide({
 | 
			
		||||
    guards: {
 | 
			
		||||
      'Command has no arguments': ({ context }) => {
 | 
			
		||||
        return (
 | 
			
		||||
          !context.selectedCommand?.args ||
 | 
			
		||||
          Object.keys(context.selectedCommand?.args).length === 0
 | 
			
		||||
        )
 | 
			
		||||
      },
 | 
			
		||||
      'All arguments are skippable': ({ context }) => {
 | 
			
		||||
        return Object.values(context.selectedCommand!.args!).every(
 | 
			
		||||
          (argConfig) => argConfig.skip
 | 
			
		||||
        )
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
export const CommandBarProvider = ({
 | 
			
		||||
  children,
 | 
			
		||||
}: {
 | 
			
		||||
  children: React.ReactNode
 | 
			
		||||
}) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <CommandsContext.Provider>
 | 
			
		||||
      <CommandBarProviderInner>{children}</CommandBarProviderInner>
 | 
			
		||||
    </CommandsContext.Provider>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
function CommandBarProviderInner({ children }: { children: React.ReactNode }) {
 | 
			
		||||
  const commandBarActor = CommandsContext.useActorRef()
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    editorManager.setCommandBarSend(commandBarActor.send)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  return children
 | 
			
		||||
}
 | 
			
		||||
@ -1,9 +1,9 @@
 | 
			
		||||
import { useCommandsContext } from 'hooks/useCommandsContext'
 | 
			
		||||
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
 | 
			
		||||
import CommandBarHeader from './CommandBarHeader'
 | 
			
		||||
import { useHotkeys } from 'react-hotkeys-hook'
 | 
			
		||||
 | 
			
		||||
function CommandBarReview({ stepBack }: { stepBack: () => void }) {
 | 
			
		||||
  const { commandBarState, commandBarSend } = useCommandsContext()
 | 
			
		||||
  const commandBarState = useCommandBarState()
 | 
			
		||||
  const {
 | 
			
		||||
    context: { argumentsToSubmit, selectedCommand },
 | 
			
		||||
  } = commandBarState
 | 
			
		||||
@ -33,7 +33,7 @@ function CommandBarReview({ stepBack }: { stepBack: () => void }) {
 | 
			
		||||
          parseInt(b.keys[0], 10) - 1
 | 
			
		||||
        ]
 | 
			
		||||
        const arg = selectedCommand?.args[argName]
 | 
			
		||||
        commandBarSend({
 | 
			
		||||
        commandBarActor.send({
 | 
			
		||||
          type: 'Edit argument',
 | 
			
		||||
          data: { arg: { ...arg, name: argName } },
 | 
			
		||||
        })
 | 
			
		||||
@ -50,7 +50,7 @@ function CommandBarReview({ stepBack }: { stepBack: () => void }) {
 | 
			
		||||
 | 
			
		||||
  function submitCommand(e: React.FormEvent<HTMLFormElement>) {
 | 
			
		||||
    e.preventDefault()
 | 
			
		||||
    commandBarSend({
 | 
			
		||||
    commandBarActor.send({
 | 
			
		||||
      type: 'Submit command',
 | 
			
		||||
      output: argumentsToSubmit,
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,4 @@
 | 
			
		||||
import { useSelector } from '@xstate/react'
 | 
			
		||||
import { useCommandsContext } from 'hooks/useCommandsContext'
 | 
			
		||||
import { Artifact } from 'lang/std/artifactGraph'
 | 
			
		||||
import { CommandArgument } from 'lib/commandTypes'
 | 
			
		||||
import {
 | 
			
		||||
@ -10,6 +9,7 @@ import {
 | 
			
		||||
import { kclManager } from 'lib/singletons'
 | 
			
		||||
import { reportRejection } from 'lib/trap'
 | 
			
		||||
import { toSync } from 'lib/utils'
 | 
			
		||||
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
 | 
			
		||||
import { modelingMachine } from 'machines/modelingMachine'
 | 
			
		||||
import { useEffect, useMemo, useRef, useState } from 'react'
 | 
			
		||||
import { StateFrom } from 'xstate'
 | 
			
		||||
@ -49,7 +49,7 @@ function CommandBarSelectionInput({
 | 
			
		||||
  onSubmit: (data: unknown) => void
 | 
			
		||||
}) {
 | 
			
		||||
  const inputRef = useRef<HTMLInputElement>(null)
 | 
			
		||||
  const { commandBarState, commandBarSend } = useCommandsContext()
 | 
			
		||||
  const commandBarState = useCommandBarState()
 | 
			
		||||
  const [hasSubmitted, setHasSubmitted] = useState(false)
 | 
			
		||||
  const selection = useSelector(arg.machineActor, selectionSelector)
 | 
			
		||||
  const selectionsByType = useMemo(() => {
 | 
			
		||||
@ -145,7 +145,7 @@ function CommandBarSelectionInput({
 | 
			
		||||
            if (event.key === 'Backspace') {
 | 
			
		||||
              stepBack()
 | 
			
		||||
            } else if (event.key === 'Escape') {
 | 
			
		||||
              commandBarSend({ type: 'Close' })
 | 
			
		||||
              commandBarActor.send({ type: 'Close' })
 | 
			
		||||
            }
 | 
			
		||||
          }}
 | 
			
		||||
          onChange={handleChange}
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import { useCommandsContext } from 'hooks/useCommandsContext'
 | 
			
		||||
import { CommandArgument } from 'lib/commandTypes'
 | 
			
		||||
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
 | 
			
		||||
import { RefObject, useEffect, useRef } from 'react'
 | 
			
		||||
import { useHotkeys } from 'react-hotkeys-hook'
 | 
			
		||||
 | 
			
		||||
@ -15,8 +15,8 @@ function CommandBarTextareaInput({
 | 
			
		||||
  stepBack: () => void
 | 
			
		||||
  onSubmit: (event: unknown) => void
 | 
			
		||||
}) {
 | 
			
		||||
  const { commandBarSend, commandBarState } = useCommandsContext()
 | 
			
		||||
  useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' }))
 | 
			
		||||
  const commandBarState = useCommandBarState()
 | 
			
		||||
  useHotkeys('mod + k, mod + /', () => commandBarActor.send({ type: 'Close' }))
 | 
			
		||||
  const formRef = useRef<HTMLFormElement>(null)
 | 
			
		||||
  const inputRef = useRef<HTMLTextAreaElement>(null)
 | 
			
		||||
  useTextareaAutoGrow(inputRef)
 | 
			
		||||
 | 
			
		||||
@ -1,16 +1,15 @@
 | 
			
		||||
import { useCommandsContext } from 'hooks/useCommandsContext'
 | 
			
		||||
import usePlatform from 'hooks/usePlatform'
 | 
			
		||||
import { hotkeyDisplay } from 'lib/hotkeyWrapper'
 | 
			
		||||
import { COMMAND_PALETTE_HOTKEY } from './CommandBar/CommandBar'
 | 
			
		||||
import { commandBarActor } from 'machines/commandBarMachine'
 | 
			
		||||
 | 
			
		||||
export function CommandBarOpenButton() {
 | 
			
		||||
  const { commandBarSend } = useCommandsContext()
 | 
			
		||||
  const platform = usePlatform()
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <button
 | 
			
		||||
      className="group rounded-full flex items-center justify-center gap-2 px-2 py-1 bg-primary/10 dark:bg-chalkboard-90 dark:backdrop-blur-sm border-primary hover:border-primary dark:border-chalkboard-50 dark:hover:border-inherit text-primary dark:text-inherit"
 | 
			
		||||
      onClick={() => commandBarSend({ type: 'Open' })}
 | 
			
		||||
      onClick={() => commandBarActor.send({ type: 'Open' })}
 | 
			
		||||
      data-testid="command-bar-open-button"
 | 
			
		||||
    >
 | 
			
		||||
      <span>Commands</span>
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,11 @@
 | 
			
		||||
import { Combobox } from '@headlessui/react'
 | 
			
		||||
import Fuse from 'fuse.js'
 | 
			
		||||
import { useCommandsContext } from 'hooks/useCommandsContext'
 | 
			
		||||
import { Command } from 'lib/commandTypes'
 | 
			
		||||
import { useEffect, useState } from 'react'
 | 
			
		||||
import { CustomIcon } from './CustomIcon'
 | 
			
		||||
import { getActorNextEvents } from 'lib/utils'
 | 
			
		||||
import { sortCommands } from 'lib/commandUtils'
 | 
			
		||||
import { commandBarActor } from 'machines/commandBarMachine'
 | 
			
		||||
 | 
			
		||||
function CommandComboBox({
 | 
			
		||||
  options,
 | 
			
		||||
@ -14,7 +14,6 @@ function CommandComboBox({
 | 
			
		||||
  options: Command[]
 | 
			
		||||
  placeholder?: string
 | 
			
		||||
}) {
 | 
			
		||||
  const { commandBarSend } = useCommandsContext()
 | 
			
		||||
  const [query, setQuery] = useState('')
 | 
			
		||||
  const [filteredOptions, setFilteredOptions] = useState<typeof options>()
 | 
			
		||||
 | 
			
		||||
@ -41,7 +40,7 @@ function CommandComboBox({
 | 
			
		||||
  }, [query])
 | 
			
		||||
 | 
			
		||||
  function handleSelection(command: Command) {
 | 
			
		||||
    commandBarSend({ type: 'Select command', data: { command } })
 | 
			
		||||
    commandBarActor.send({ type: 'Select command', data: { command } })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
@ -61,7 +60,7 @@ function CommandComboBox({
 | 
			
		||||
              (event.key === 'Backspace' && !event.currentTarget.value)
 | 
			
		||||
            ) {
 | 
			
		||||
              event.preventDefault()
 | 
			
		||||
              commandBarSend({ type: 'Close' })
 | 
			
		||||
              commandBarActor.send({ type: 'Close' })
 | 
			
		||||
            }
 | 
			
		||||
          }}
 | 
			
		||||
          placeholder={
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,6 @@ import {
 | 
			
		||||
  StateFrom,
 | 
			
		||||
  fromPromise,
 | 
			
		||||
} from 'xstate'
 | 
			
		||||
import { useCommandsContext } from 'hooks/useCommandsContext'
 | 
			
		||||
import { fileMachine } from 'machines/fileMachine'
 | 
			
		||||
import { isDesktop } from 'lib/isDesktop'
 | 
			
		||||
import {
 | 
			
		||||
@ -30,6 +29,7 @@ import {
 | 
			
		||||
} from 'lib/getKclSamplesManifest'
 | 
			
		||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
 | 
			
		||||
import { markOnce } from 'lib/performance'
 | 
			
		||||
import { commandBarActor } from 'machines/commandBarMachine'
 | 
			
		||||
 | 
			
		||||
type MachineContext<T extends AnyStateMachine> = {
 | 
			
		||||
  state: StateFrom<T>
 | 
			
		||||
@ -47,7 +47,6 @@ export const FileMachineProvider = ({
 | 
			
		||||
  children: React.ReactNode
 | 
			
		||||
}) => {
 | 
			
		||||
  const navigate = useNavigate()
 | 
			
		||||
  const { commandBarSend } = useCommandsContext()
 | 
			
		||||
  const { settings } = useSettingsAuthContext()
 | 
			
		||||
  const { project, file } = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
 | 
			
		||||
  const [kclSamples, setKclSamples] = React.useState<KclSamplesManifestItem[]>(
 | 
			
		||||
@ -90,7 +89,7 @@ export const FileMachineProvider = ({
 | 
			
		||||
        navigateToFile: ({ context, event }) => {
 | 
			
		||||
          if (event.type !== 'xstate.done.actor.create-and-open-file') return
 | 
			
		||||
          if (event.output && 'name' in event.output) {
 | 
			
		||||
            commandBarSend({ type: 'Close' })
 | 
			
		||||
            commandBarActor.send({ type: 'Close' })
 | 
			
		||||
            navigate(
 | 
			
		||||
              `..${PATHS.FILE}/${encodeURIComponent(
 | 
			
		||||
                context.selectedDirectory +
 | 
			
		||||
@ -336,15 +335,18 @@ export const FileMachineProvider = ({
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    commandBarSend({ type: 'Add commands', data: { commands: kclCommandMemo } })
 | 
			
		||||
    commandBarActor.send({
 | 
			
		||||
      type: 'Add commands',
 | 
			
		||||
      data: { commands: kclCommandMemo },
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    return () => {
 | 
			
		||||
      commandBarSend({
 | 
			
		||||
      commandBarActor.send({
 | 
			
		||||
        type: 'Remove commands',
 | 
			
		||||
        data: { commands: kclCommandMemo },
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }, [commandBarSend, kclCommandMemo])
 | 
			
		||||
  }, [commandBarActor.send, kclCommandMemo])
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <FileContext.Provider
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,11 @@
 | 
			
		||||
import { createContext, useEffect, useState } from 'react'
 | 
			
		||||
 | 
			
		||||
import { engineCommandManager } from 'lib/singletons'
 | 
			
		||||
import { CommandsContext } from 'components/CommandBar/CommandBarProvider'
 | 
			
		||||
import { isDesktop } from 'lib/isDesktop'
 | 
			
		||||
import { components } from 'lib/machine-api'
 | 
			
		||||
import { reportRejection } from 'lib/trap'
 | 
			
		||||
import { toSync } from 'lib/utils'
 | 
			
		||||
import { commandBarActor } from 'machines/commandBarMachine'
 | 
			
		||||
 | 
			
		||||
export type MachinesListing = Array<
 | 
			
		||||
  components['schemas']['MachineInfoResponse']
 | 
			
		||||
@ -42,8 +42,6 @@ export const MachineManagerProvider = ({
 | 
			
		||||
    components['schemas']['MachineInfoResponse'] | null
 | 
			
		||||
  >(null)
 | 
			
		||||
 | 
			
		||||
  const commandBarActor = CommandsContext.useActorRef()
 | 
			
		||||
 | 
			
		||||
  // Get the reason message for why there are no machines.
 | 
			
		||||
  const noMachinesReason = (): string | undefined => {
 | 
			
		||||
    if (machines.length > 0) {
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { useMachine } from '@xstate/react'
 | 
			
		||||
import { useMachine, useSelector } from '@xstate/react'
 | 
			
		||||
import React, {
 | 
			
		||||
  createContext,
 | 
			
		||||
  useEffect,
 | 
			
		||||
@ -11,6 +11,7 @@ import {
 | 
			
		||||
  AnyStateMachine,
 | 
			
		||||
  ContextFrom,
 | 
			
		||||
  Prop,
 | 
			
		||||
  SnapshotFrom,
 | 
			
		||||
  StateFrom,
 | 
			
		||||
  assign,
 | 
			
		||||
  fromPromise,
 | 
			
		||||
@ -78,7 +79,6 @@ import toast from 'react-hot-toast'
 | 
			
		||||
import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
 | 
			
		||||
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
 | 
			
		||||
import { err, reportRejection, trap } from 'lib/trap'
 | 
			
		||||
import { useCommandsContext } from 'hooks/useCommandsContext'
 | 
			
		||||
import {
 | 
			
		||||
  ExportIntent,
 | 
			
		||||
  EngineConnectionStateType,
 | 
			
		||||
@ -91,6 +91,7 @@ import { IndexLoaderData } from 'lib/types'
 | 
			
		||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
 | 
			
		||||
import { promptToEditFlow } from 'lib/promptToEdit'
 | 
			
		||||
import { kclEditorActor } from 'machines/kclEditorMachine'
 | 
			
		||||
import { commandBarActor } from 'machines/commandBarMachine'
 | 
			
		||||
 | 
			
		||||
type MachineContext<T extends AnyStateMachine> = {
 | 
			
		||||
  state: StateFrom<T>
 | 
			
		||||
@ -102,6 +103,10 @@ export const ModelingMachineContext = createContext(
 | 
			
		||||
  {} as MachineContext<typeof modelingMachine>
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const commandBarIsClosedSelector = (
 | 
			
		||||
  state: SnapshotFrom<typeof commandBarActor>
 | 
			
		||||
) => state.matches('Closed')
 | 
			
		||||
 | 
			
		||||
export const ModelingMachineProvider = ({
 | 
			
		||||
  children,
 | 
			
		||||
}: {
 | 
			
		||||
@ -132,8 +137,10 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
  let [searchParams] = useSearchParams()
 | 
			
		||||
  const pool = searchParams.get('pool')
 | 
			
		||||
 | 
			
		||||
  const { commandBarState, commandBarSend } = useCommandsContext()
 | 
			
		||||
 | 
			
		||||
  const isCommandBarClosed = useSelector(
 | 
			
		||||
    commandBarActor,
 | 
			
		||||
    commandBarIsClosedSelector
 | 
			
		||||
  )
 | 
			
		||||
  // Settings machine setup
 | 
			
		||||
  // const retrievedSettings = useRef(
 | 
			
		||||
  // localStorage?.getItem(MODELING_PERSIST_KEY) || '{}'
 | 
			
		||||
@ -538,7 +545,6 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
            trimmedPrompt,
 | 
			
		||||
            fileMachineSend,
 | 
			
		||||
            navigate,
 | 
			
		||||
            commandBarSend,
 | 
			
		||||
            context,
 | 
			
		||||
            token,
 | 
			
		||||
            settings: {
 | 
			
		||||
@ -552,7 +558,7 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
        'has valid selection for deletion': ({
 | 
			
		||||
          context: { selectionRanges },
 | 
			
		||||
        }) => {
 | 
			
		||||
          if (!commandBarState.matches('Closed')) return false
 | 
			
		||||
          if (!isCommandBarClosed) return false
 | 
			
		||||
          if (selectionRanges.graphSelections.length <= 0) return false
 | 
			
		||||
          return true
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@ -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 (
 | 
			
		||||
    <Menu>
 | 
			
		||||
@ -85,7 +84,7 @@ export const KclEditorMenu = ({ children }: PropsWithChildren) => {
 | 
			
		||||
          <Menu.Item>
 | 
			
		||||
            <button
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
                commandBarSend({
 | 
			
		||||
                commandBarActor.send({
 | 
			
		||||
                  type: 'Find and select command',
 | 
			
		||||
                  data: {
 | 
			
		||||
                    groupId: 'code',
 | 
			
		||||
 | 
			
		||||
@ -15,12 +15,12 @@ import { ModelingPane } from './ModelingPane'
 | 
			
		||||
import { isDesktop } from 'lib/isDesktop'
 | 
			
		||||
import { useModelingContext } from 'hooks/useModelingContext'
 | 
			
		||||
import { CustomIconName } from 'components/CustomIcon'
 | 
			
		||||
import { useCommandsContext } from 'hooks/useCommandsContext'
 | 
			
		||||
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
 | 
			
		||||
import { useKclContext } from 'lang/KclProvider'
 | 
			
		||||
import { MachineManagerContext } from 'components/MachineManagerProvider'
 | 
			
		||||
import { onboardingPaths } from 'routes/Onboarding/paths'
 | 
			
		||||
import { SIDEBAR_BUTTON_SUFFIX } from 'lib/constants'
 | 
			
		||||
import { commandBarActor } from 'machines/commandBarMachine'
 | 
			
		||||
 | 
			
		||||
interface ModelingSidebarProps {
 | 
			
		||||
  paneOpacity: '' | 'opacity-20' | 'opacity-40'
 | 
			
		||||
@ -37,7 +37,6 @@ function getPlatformString(): 'web' | 'desktop' {
 | 
			
		||||
 | 
			
		||||
export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
 | 
			
		||||
  const machineManager = useContext(MachineManagerContext)
 | 
			
		||||
  const { commandBarSend } = useCommandsContext()
 | 
			
		||||
  const kclContext = useKclContext()
 | 
			
		||||
  const { settings } = useSettingsAuthContext()
 | 
			
		||||
  const onboardingStatus = settings.context.app.onboardingStatus
 | 
			
		||||
@ -66,7 +65,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
 | 
			
		||||
      icon: 'floppyDiskArrow',
 | 
			
		||||
      keybinding: 'Ctrl + Shift + E',
 | 
			
		||||
      action: () =>
 | 
			
		||||
        commandBarSend({
 | 
			
		||||
        commandBarActor.send({
 | 
			
		||||
          type: 'Find and select command',
 | 
			
		||||
          data: { name: 'Export', groupId: 'modeling' },
 | 
			
		||||
        }),
 | 
			
		||||
@ -79,7 +78,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
 | 
			
		||||
      keybinding: 'Ctrl + Shift + M',
 | 
			
		||||
      // eslint-disable-next-line @typescript-eslint/no-misused-promises
 | 
			
		||||
      action: async () => {
 | 
			
		||||
        commandBarSend({
 | 
			
		||||
        commandBarActor.send({
 | 
			
		||||
          type: 'Find and select command',
 | 
			
		||||
          data: { name: 'Make', groupId: 'modeling' },
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,6 @@
 | 
			
		||||
import { fireEvent, render, screen } from '@testing-library/react'
 | 
			
		||||
import { BrowserRouter } from 'react-router-dom'
 | 
			
		||||
import { SettingsAuthProviderJest } from './SettingsAuthProvider'
 | 
			
		||||
import { CommandBarProvider } from './CommandBar/CommandBarProvider'
 | 
			
		||||
import {
 | 
			
		||||
  NETWORK_HEALTH_TEXT,
 | 
			
		||||
  NetworkHealthIndicator,
 | 
			
		||||
@ -12,9 +11,7 @@ function TestWrap({ children }: { children: React.ReactNode }) {
 | 
			
		||||
  // wrap in router and xState context
 | 
			
		||||
  return (
 | 
			
		||||
    <BrowserRouter>
 | 
			
		||||
      <CommandBarProvider>
 | 
			
		||||
        <SettingsAuthProviderJest>{children}</SettingsAuthProviderJest>
 | 
			
		||||
      </CommandBarProvider>
 | 
			
		||||
      <SettingsAuthProviderJest>{children}</SettingsAuthProviderJest>
 | 
			
		||||
    </BrowserRouter>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,6 @@ import { render, screen } from '@testing-library/react'
 | 
			
		||||
import { BrowserRouter } from 'react-router-dom'
 | 
			
		||||
import ProjectSidebarMenu from './ProjectSidebarMenu'
 | 
			
		||||
import { SettingsAuthProviderJest } from './SettingsAuthProvider'
 | 
			
		||||
import { CommandBarProvider } from './CommandBar/CommandBarProvider'
 | 
			
		||||
import { Project } from 'lib/project'
 | 
			
		||||
 | 
			
		||||
const now = new Date()
 | 
			
		||||
@ -33,11 +32,9 @@ describe('ProjectSidebarMenu tests', () => {
 | 
			
		||||
  test('Disables popover menu by default', () => {
 | 
			
		||||
    render(
 | 
			
		||||
      <BrowserRouter>
 | 
			
		||||
        <CommandBarProvider>
 | 
			
		||||
          <SettingsAuthProviderJest>
 | 
			
		||||
            <ProjectSidebarMenu project={projectWellFormed} />
 | 
			
		||||
          </SettingsAuthProviderJest>
 | 
			
		||||
        </CommandBarProvider>
 | 
			
		||||
        <SettingsAuthProviderJest>
 | 
			
		||||
          <ProjectSidebarMenu project={projectWellFormed} />
 | 
			
		||||
        </SettingsAuthProviderJest>
 | 
			
		||||
      </BrowserRouter>
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,6 @@ import { Link, useLocation, useNavigate } from 'react-router-dom'
 | 
			
		||||
import { Fragment, useMemo, useContext } from 'react'
 | 
			
		||||
import { Logo } from './Logo'
 | 
			
		||||
import { APP_NAME } from 'lib/constants'
 | 
			
		||||
import { useCommandsContext } from 'hooks/useCommandsContext'
 | 
			
		||||
import { CustomIcon } from './CustomIcon'
 | 
			
		||||
import { useLspContext } from './LspProvider'
 | 
			
		||||
import { engineCommandManager, kclManager } from 'lib/singletons'
 | 
			
		||||
@ -15,6 +14,9 @@ import { MachineManagerContext } from 'components/MachineManagerProvider'
 | 
			
		||||
import usePlatform from 'hooks/usePlatform'
 | 
			
		||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
 | 
			
		||||
import Tooltip from './Tooltip'
 | 
			
		||||
import { SnapshotFrom } from 'xstate'
 | 
			
		||||
import { commandBarActor } from 'machines/commandBarMachine'
 | 
			
		||||
import { useSelector } from '@xstate/react'
 | 
			
		||||
 | 
			
		||||
const ProjectSidebarMenu = ({
 | 
			
		||||
  project,
 | 
			
		||||
@ -84,6 +86,9 @@ function AppLogoLink({
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const commandsSelector = (state: SnapshotFrom<typeof commandBarActor>) =>
 | 
			
		||||
  state.context.commands
 | 
			
		||||
 | 
			
		||||
function ProjectMenuPopover({
 | 
			
		||||
  project,
 | 
			
		||||
  file,
 | 
			
		||||
@ -96,16 +101,14 @@ function ProjectMenuPopover({
 | 
			
		||||
  const navigate = useNavigate()
 | 
			
		||||
  const filePath = useAbsoluteFilePath()
 | 
			
		||||
  const machineManager = useContext(MachineManagerContext)
 | 
			
		||||
  const commands = useSelector(commandBarActor, commandsSelector)
 | 
			
		||||
 | 
			
		||||
  const { commandBarState, commandBarSend } = useCommandsContext()
 | 
			
		||||
  const { onProjectClose } = useLspContext()
 | 
			
		||||
  const exportCommandInfo = { name: 'Export', groupId: 'modeling' }
 | 
			
		||||
  const makeCommandInfo = { name: 'Make', groupId: 'modeling' }
 | 
			
		||||
  const findCommand = (obj: { name: string; groupId: string }) =>
 | 
			
		||||
    Boolean(
 | 
			
		||||
      commandBarState.context.commands.find(
 | 
			
		||||
        (c) => c.name === obj.name && c.groupId === obj.groupId
 | 
			
		||||
      )
 | 
			
		||||
      commands.find((c) => c.name === obj.name && c.groupId === obj.groupId)
 | 
			
		||||
    )
 | 
			
		||||
  const machineCount = machineManager.machines.length
 | 
			
		||||
 | 
			
		||||
@ -150,7 +153,7 @@ function ProjectMenuPopover({
 | 
			
		||||
          ),
 | 
			
		||||
          disabled: !findCommand(exportCommandInfo),
 | 
			
		||||
          onClick: () =>
 | 
			
		||||
            commandBarSend({
 | 
			
		||||
            commandBarActor.send({
 | 
			
		||||
              type: 'Find and select command',
 | 
			
		||||
              data: exportCommandInfo,
 | 
			
		||||
            }),
 | 
			
		||||
@ -175,7 +178,7 @@ function ProjectMenuPopover({
 | 
			
		||||
          ),
 | 
			
		||||
          disabled: !findCommand(makeCommandInfo) || machineCount === 0,
 | 
			
		||||
          onClick: () => {
 | 
			
		||||
            commandBarSend({
 | 
			
		||||
            commandBarActor.send({
 | 
			
		||||
              type: 'Find and select command',
 | 
			
		||||
              data: makeCommandInfo,
 | 
			
		||||
            })
 | 
			
		||||
@ -200,7 +203,7 @@ function ProjectMenuPopover({
 | 
			
		||||
    [
 | 
			
		||||
      platform,
 | 
			
		||||
      findCommand,
 | 
			
		||||
      commandBarSend,
 | 
			
		||||
      commandBarActor.send,
 | 
			
		||||
      engineCommandManager,
 | 
			
		||||
      onProjectClose,
 | 
			
		||||
      isDesktop,
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,4 @@
 | 
			
		||||
import { useMachine } from '@xstate/react'
 | 
			
		||||
import { useCommandsContext } from 'hooks/useCommandsContext'
 | 
			
		||||
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
 | 
			
		||||
import { useProjectsLoader } from 'hooks/useProjectsLoader'
 | 
			
		||||
import { projectsMachine } from 'machines/projectsMachine'
 | 
			
		||||
@ -24,6 +23,7 @@ import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
 | 
			
		||||
import useStateMachineCommands from 'hooks/useStateMachineCommands'
 | 
			
		||||
import { projectsCommandBarConfig } from 'lib/commandBarConfigs/projectsCommandConfig'
 | 
			
		||||
import { isDesktop } from 'lib/isDesktop'
 | 
			
		||||
import { commandBarActor } from 'machines/commandBarMachine'
 | 
			
		||||
 | 
			
		||||
type MachineContext<T extends AnyStateMachine> = {
 | 
			
		||||
  state?: StateFrom<T>
 | 
			
		||||
@ -73,7 +73,6 @@ const ProjectsContextDesktop = ({
 | 
			
		||||
}) => {
 | 
			
		||||
  const navigate = useNavigate()
 | 
			
		||||
  const location = useLocation()
 | 
			
		||||
  const { commandBarSend } = useCommandsContext()
 | 
			
		||||
  const { onProjectOpen } = useLspContext()
 | 
			
		||||
  const {
 | 
			
		||||
    settings: { context: settings },
 | 
			
		||||
@ -126,7 +125,7 @@ const ProjectsContextDesktop = ({
 | 
			
		||||
              },
 | 
			
		||||
              null
 | 
			
		||||
            )
 | 
			
		||||
            commandBarSend({ type: 'Close' })
 | 
			
		||||
            commandBarActor.send({ type: 'Close' })
 | 
			
		||||
            const newPathName = `${PATHS.FILE}/${encodeURIComponent(
 | 
			
		||||
              projectPath
 | 
			
		||||
            )}`
 | 
			
		||||
 | 
			
		||||
@ -29,7 +29,6 @@ import {
 | 
			
		||||
  createSettingsCommand,
 | 
			
		||||
  settingsWithCommandConfigs,
 | 
			
		||||
} from 'lib/commandBarConfigs/settingsCommandConfig'
 | 
			
		||||
import { useCommandsContext } from 'hooks/useCommandsContext'
 | 
			
		||||
import { Command } from 'lib/commandTypes'
 | 
			
		||||
import { BaseUnit } from 'lib/settings/settingsTypes'
 | 
			
		||||
import {
 | 
			
		||||
@ -42,6 +41,7 @@ import { isDesktop } from 'lib/isDesktop'
 | 
			
		||||
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
 | 
			
		||||
import { codeManager } from 'lib/singletons'
 | 
			
		||||
import { createRouteCommands } from 'lib/commandBarConfigs/routeCommandConfig'
 | 
			
		||||
import { commandBarActor } from 'machines/commandBarMachine'
 | 
			
		||||
 | 
			
		||||
type MachineContext<T extends AnyStateMachine> = {
 | 
			
		||||
  state: StateFrom<T>
 | 
			
		||||
@ -109,7 +109,6 @@ export const SettingsAuthProviderBase = ({
 | 
			
		||||
}) => {
 | 
			
		||||
  const location = useLocation()
 | 
			
		||||
  const navigate = useNavigate()
 | 
			
		||||
  const { commandBarSend } = useCommandsContext()
 | 
			
		||||
  const [settingsPath, setSettingsPath] = useState<string | undefined>(
 | 
			
		||||
    undefined
 | 
			
		||||
  )
 | 
			
		||||
@ -278,10 +277,10 @@ export const SettingsAuthProviderBase = ({
 | 
			
		||||
      )
 | 
			
		||||
      .filter((c) => c !== null) as Command[]
 | 
			
		||||
 | 
			
		||||
    commandBarSend({ type: 'Add commands', data: { commands: commands } })
 | 
			
		||||
    commandBarActor.send({ type: 'Add commands', data: { commands: commands } })
 | 
			
		||||
 | 
			
		||||
    return () => {
 | 
			
		||||
      commandBarSend({
 | 
			
		||||
      commandBarActor.send({
 | 
			
		||||
        type: 'Remove commands',
 | 
			
		||||
        data: { commands },
 | 
			
		||||
      })
 | 
			
		||||
@ -290,7 +289,7 @@ export const SettingsAuthProviderBase = ({
 | 
			
		||||
    settingsState,
 | 
			
		||||
    settingsSend,
 | 
			
		||||
    settingsActor,
 | 
			
		||||
    commandBarSend,
 | 
			
		||||
    commandBarActor.send,
 | 
			
		||||
    settingsWithCommandConfigs,
 | 
			
		||||
  ])
 | 
			
		||||
 | 
			
		||||
@ -303,7 +302,7 @@ export const SettingsAuthProviderBase = ({
 | 
			
		||||
      encodeURIComponent(loadedProject?.file?.path || BROWSER_PATH)
 | 
			
		||||
    const { RouteTelemetryCommand, RouteHomeCommand, RouteSettingsCommand } =
 | 
			
		||||
      createRouteCommands(navigate, location, filePath)
 | 
			
		||||
    commandBarSend({
 | 
			
		||||
    commandBarActor.send({
 | 
			
		||||
      type: 'Remove commands',
 | 
			
		||||
      data: {
 | 
			
		||||
        commands: [
 | 
			
		||||
@ -314,12 +313,12 @@ export const SettingsAuthProviderBase = ({
 | 
			
		||||
      },
 | 
			
		||||
    })
 | 
			
		||||
    if (location.pathname === PATHS.HOME) {
 | 
			
		||||
      commandBarSend({
 | 
			
		||||
      commandBarActor.send({
 | 
			
		||||
        type: 'Add commands',
 | 
			
		||||
        data: { commands: [RouteTelemetryCommand, RouteSettingsCommand] },
 | 
			
		||||
      })
 | 
			
		||||
    } else if (location.pathname.includes(PATHS.FILE)) {
 | 
			
		||||
      commandBarSend({
 | 
			
		||||
      commandBarActor.send({
 | 
			
		||||
        type: 'Add commands',
 | 
			
		||||
        data: {
 | 
			
		||||
          commands: [
 | 
			
		||||
 | 
			
		||||
@ -17,10 +17,11 @@ import {
 | 
			
		||||
import { useRouteLoaderData } from 'react-router-dom'
 | 
			
		||||
import { PATHS } from 'lib/paths'
 | 
			
		||||
import { IndexLoaderData } from 'lib/types'
 | 
			
		||||
import { useCommandsContext } from 'hooks/useCommandsContext'
 | 
			
		||||
import { err, reportRejection } from 'lib/trap'
 | 
			
		||||
import { getArtifactOfTypes } from 'lang/std/artifactGraph'
 | 
			
		||||
import { ViewControlContextMenu } from './ViewControlMenu'
 | 
			
		||||
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
 | 
			
		||||
import { useSelector } from '@xstate/react'
 | 
			
		||||
 | 
			
		||||
enum StreamState {
 | 
			
		||||
  Playing = 'playing',
 | 
			
		||||
@ -35,7 +36,7 @@ export const Stream = () => {
 | 
			
		||||
  const videoRef = useRef<HTMLVideoElement>(null)
 | 
			
		||||
  const { settings } = useSettingsAuthContext()
 | 
			
		||||
  const { state, send } = useModelingContext()
 | 
			
		||||
  const { commandBarState } = useCommandsContext()
 | 
			
		||||
  const commandBarState = useCommandBarState()
 | 
			
		||||
  const { mediaStream } = useAppStream()
 | 
			
		||||
  const { overallState, immediateState } = useNetworkContext()
 | 
			
		||||
  const [streamState, setStreamState] = useState(StreamState.Unset)
 | 
			
		||||
 | 
			
		||||
@ -28,7 +28,7 @@ import { base64Decode } from 'lang/wasm'
 | 
			
		||||
import { sendTelemetry } from 'lib/textToCad'
 | 
			
		||||
import { Themes } from 'lib/theme'
 | 
			
		||||
import { ActionButton } from './ActionButton'
 | 
			
		||||
import { commandBarMachine } from 'machines/commandBarMachine'
 | 
			
		||||
import { commandBarActor, commandBarMachine } from 'machines/commandBarMachine'
 | 
			
		||||
import { EventFrom } from 'xstate'
 | 
			
		||||
import { fileMachine } from 'machines/fileMachine'
 | 
			
		||||
import { reportRejection } from 'lib/trap'
 | 
			
		||||
@ -43,15 +43,10 @@ export function ToastTextToCadError({
 | 
			
		||||
  toastId,
 | 
			
		||||
  message,
 | 
			
		||||
  prompt,
 | 
			
		||||
  commandBarSend,
 | 
			
		||||
}: {
 | 
			
		||||
  toastId: string
 | 
			
		||||
  message: string
 | 
			
		||||
  prompt: string
 | 
			
		||||
  commandBarSend: (
 | 
			
		||||
    event: EventFrom<typeof commandBarMachine>,
 | 
			
		||||
    data?: unknown
 | 
			
		||||
  ) => void
 | 
			
		||||
}) {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col justify-between gap-6">
 | 
			
		||||
@ -81,7 +76,7 @@ export function ToastTextToCadError({
 | 
			
		||||
          }}
 | 
			
		||||
          name="Edit prompt"
 | 
			
		||||
          onClick={() => {
 | 
			
		||||
            commandBarSend({
 | 
			
		||||
            commandBarActor.send({
 | 
			
		||||
              type: 'Find and select command',
 | 
			
		||||
              data: {
 | 
			
		||||
                groupId: 'modeling',
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,6 @@ import {
 | 
			
		||||
} from 'react-router-dom'
 | 
			
		||||
import { Models } from '@kittycad/lib'
 | 
			
		||||
import { SettingsAuthProviderJest } from './SettingsAuthProvider'
 | 
			
		||||
import { CommandBarProvider } from './CommandBar/CommandBarProvider'
 | 
			
		||||
 | 
			
		||||
type User = Models['User_type']
 | 
			
		||||
 | 
			
		||||
@ -124,9 +123,7 @@ function TestWrap({ children }: { children: React.ReactNode }) {
 | 
			
		||||
      <Route
 | 
			
		||||
        path="/file/:id"
 | 
			
		||||
        element={
 | 
			
		||||
          <CommandBarProvider>
 | 
			
		||||
            <SettingsAuthProviderJest>{children}</SettingsAuthProviderJest>
 | 
			
		||||
          </CommandBarProvider>
 | 
			
		||||
          <SettingsAuthProviderJest>{children}</SettingsAuthProviderJest>
 | 
			
		||||
        }
 | 
			
		||||
      />
 | 
			
		||||
    ),
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,6 @@ import { engineCommandManager, kclManager } from 'lib/singletons'
 | 
			
		||||
import { modelingMachine, ModelingMachineEvent } from 'machines/modelingMachine'
 | 
			
		||||
import { Selections, Selection, processCodeMirrorRanges } from 'lib/selections'
 | 
			
		||||
import { undo, redo } from '@codemirror/commands'
 | 
			
		||||
import { CommandBarMachineEvent } from 'machines/commandBarMachine'
 | 
			
		||||
import { addLineHighlight, addLineHighlightEvent } from './highlightextension'
 | 
			
		||||
import {
 | 
			
		||||
  Diagnostic,
 | 
			
		||||
@ -52,9 +51,6 @@ export default class EditorManager {
 | 
			
		||||
  private _modelingSend: (eventInfo: ModelingMachineEvent) => void = () => {}
 | 
			
		||||
  private _modelingState: StateFrom<typeof modelingMachine> | null = null
 | 
			
		||||
 | 
			
		||||
  private _commandBarSend: (eventInfo: CommandBarMachineEvent) => void =
 | 
			
		||||
    () => {}
 | 
			
		||||
 | 
			
		||||
  private _convertToVariableEnabled: boolean = false
 | 
			
		||||
  private _convertToVariableCallback: () => void = () => {}
 | 
			
		||||
 | 
			
		||||
@ -161,14 +157,6 @@ export default class EditorManager {
 | 
			
		||||
    this._modelingState = state
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setCommandBarSend(send: (eventInfo: CommandBarMachineEvent) => void) {
 | 
			
		||||
    this._commandBarSend = send
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  commandBarSend(eventInfo: CommandBarMachineEvent): void {
 | 
			
		||||
    return this._commandBarSend(eventInfo)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get highlightRange(): Array<[number, number]> {
 | 
			
		||||
    return this._highlightRange
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -1,10 +0,0 @@
 | 
			
		||||
import { CommandsContext } from 'components/CommandBar/CommandBarProvider'
 | 
			
		||||
 | 
			
		||||
export const useCommandsContext = () => {
 | 
			
		||||
  const commandBarActor = CommandsContext.useActorRef()
 | 
			
		||||
  const commandBarState = CommandsContext.useSelector((state) => state)
 | 
			
		||||
  return {
 | 
			
		||||
    commandBarSend: commandBarActor.send,
 | 
			
		||||
    commandBarState,
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,7 +1,6 @@
 | 
			
		||||
import { useEffect } from 'react'
 | 
			
		||||
import { AnyStateMachine, Actor, StateFrom, EventFrom } from 'xstate'
 | 
			
		||||
import { createMachineCommand } from '../lib/createMachineCommand'
 | 
			
		||||
import { useCommandsContext } from './useCommandsContext'
 | 
			
		||||
import { modelingMachine } from 'machines/modelingMachine'
 | 
			
		||||
import { authMachine } from 'machines/authMachine'
 | 
			
		||||
import { settingsMachine } from 'machines/settingsMachine'
 | 
			
		||||
@ -15,6 +14,7 @@ import { useKclContext } from 'lang/KclProvider'
 | 
			
		||||
import { useNetworkContext } from 'hooks/useNetworkContext'
 | 
			
		||||
import { NetworkHealthState } from 'hooks/useNetworkStatus'
 | 
			
		||||
import { useAppState } from 'AppState'
 | 
			
		||||
import { commandBarActor } from 'machines/commandBarMachine'
 | 
			
		||||
 | 
			
		||||
// This might not be necessary, AnyStateMachine from xstate is working
 | 
			
		||||
export type AllMachines =
 | 
			
		||||
@ -48,7 +48,6 @@ export default function useStateMachineCommands<
 | 
			
		||||
  allCommandsRequireNetwork = false,
 | 
			
		||||
  onCancel,
 | 
			
		||||
}: UseStateMachineCommandsArgs<T, S>) {
 | 
			
		||||
  const { commandBarSend } = useCommandsContext()
 | 
			
		||||
  const { overallState } = useNetworkContext()
 | 
			
		||||
  const { isExecuting } = useKclContext()
 | 
			
		||||
  const { isStreamReady } = useAppState()
 | 
			
		||||
@ -76,10 +75,13 @@ export default function useStateMachineCommands<
 | 
			
		||||
      })
 | 
			
		||||
      .filter((c) => c !== null) as Command[] // TS isn't smart enough to know this filter removes nulls
 | 
			
		||||
 | 
			
		||||
    commandBarSend({ type: 'Add commands', data: { commands: newCommands } })
 | 
			
		||||
    commandBarActor.send({
 | 
			
		||||
      type: 'Add commands',
 | 
			
		||||
      data: { commands: newCommands },
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    return () => {
 | 
			
		||||
      commandBarSend({
 | 
			
		||||
      commandBarActor.send({
 | 
			
		||||
        type: 'Remove commands',
 | 
			
		||||
        data: { commands: newCommands },
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
@ -68,10 +68,6 @@ interface TextToKclProps {
 | 
			
		||||
    data?: unknown
 | 
			
		||||
  ) => unknown
 | 
			
		||||
  navigate: NavigateFunction
 | 
			
		||||
  commandBarSend: (
 | 
			
		||||
    type: EventFrom<typeof commandBarMachine>,
 | 
			
		||||
    data?: unknown
 | 
			
		||||
  ) => unknown
 | 
			
		||||
  context: ContextFrom<typeof fileMachine>
 | 
			
		||||
  token?: string
 | 
			
		||||
  settings: {
 | 
			
		||||
@ -84,7 +80,6 @@ export async function submitAndAwaitTextToKcl({
 | 
			
		||||
  trimmedPrompt,
 | 
			
		||||
  fileMachineSend,
 | 
			
		||||
  navigate,
 | 
			
		||||
  commandBarSend,
 | 
			
		||||
  context,
 | 
			
		||||
  token,
 | 
			
		||||
  settings,
 | 
			
		||||
@ -96,7 +91,6 @@ export async function submitAndAwaitTextToKcl({
 | 
			
		||||
        ToastTextToCadError({
 | 
			
		||||
          toastId,
 | 
			
		||||
          message,
 | 
			
		||||
          commandBarSend,
 | 
			
		||||
          prompt: trimmedPrompt,
 | 
			
		||||
        }),
 | 
			
		||||
      {
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { CustomIconName } from 'components/CustomIcon'
 | 
			
		||||
import { DEV } from 'env'
 | 
			
		||||
import { commandBarMachine } from 'machines/commandBarMachine'
 | 
			
		||||
import { commandBarActor, commandBarMachine } from 'machines/commandBarMachine'
 | 
			
		||||
import {
 | 
			
		||||
  canRectangleOrCircleTool,
 | 
			
		||||
  isClosedSketch,
 | 
			
		||||
@ -21,7 +21,6 @@ type ToolbarMode = {
 | 
			
		||||
export interface ToolbarItemCallbackProps {
 | 
			
		||||
  modelingState: StateFrom<typeof modelingMachine>
 | 
			
		||||
  modelingSend: (event: EventFrom<typeof modelingMachine>) => void
 | 
			
		||||
  commandBarSend: (event: EventFrom<typeof commandBarMachine>) => void
 | 
			
		||||
  sketchPathId: string | false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -84,8 +83,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
 | 
			
		||||
      'break',
 | 
			
		||||
      {
 | 
			
		||||
        id: 'extrude',
 | 
			
		||||
        onClick: ({ commandBarSend }) =>
 | 
			
		||||
          commandBarSend({
 | 
			
		||||
        onClick: () =>
 | 
			
		||||
          commandBarActor.send({
 | 
			
		||||
            type: 'Find and select command',
 | 
			
		||||
            data: { name: 'Extrude', groupId: 'modeling' },
 | 
			
		||||
          }),
 | 
			
		||||
@ -98,8 +97,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        id: 'revolve',
 | 
			
		||||
        onClick: ({ commandBarSend }) =>
 | 
			
		||||
          commandBarSend({
 | 
			
		||||
        onClick: () =>
 | 
			
		||||
          commandBarActor.send({
 | 
			
		||||
            type: 'Find and select command',
 | 
			
		||||
            data: { name: 'Revolve', groupId: 'modeling' },
 | 
			
		||||
          }),
 | 
			
		||||
@ -119,8 +118,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        id: 'sweep',
 | 
			
		||||
        onClick: ({ commandBarSend }) =>
 | 
			
		||||
          commandBarSend({
 | 
			
		||||
        onClick: () =>
 | 
			
		||||
          commandBarActor.send({
 | 
			
		||||
            type: 'Find and select command',
 | 
			
		||||
            data: { name: 'Sweep', groupId: 'modeling' },
 | 
			
		||||
          }),
 | 
			
		||||
@ -139,8 +138,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        id: 'loft',
 | 
			
		||||
        onClick: ({ commandBarSend }) =>
 | 
			
		||||
          commandBarSend({
 | 
			
		||||
        onClick: () =>
 | 
			
		||||
          commandBarActor.send({
 | 
			
		||||
            type: 'Find and select command',
 | 
			
		||||
            data: { name: 'Loft', groupId: 'modeling' },
 | 
			
		||||
          }),
 | 
			
		||||
@ -160,8 +159,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
 | 
			
		||||
      'break',
 | 
			
		||||
      {
 | 
			
		||||
        id: 'fillet3d',
 | 
			
		||||
        onClick: ({ commandBarSend }) =>
 | 
			
		||||
          commandBarSend({
 | 
			
		||||
        onClick: () =>
 | 
			
		||||
          commandBarActor.send({
 | 
			
		||||
            type: 'Find and select command',
 | 
			
		||||
            data: { name: 'Fillet', groupId: 'modeling' },
 | 
			
		||||
          }),
 | 
			
		||||
@ -174,8 +173,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        id: 'chamfer3d',
 | 
			
		||||
        onClick: ({ commandBarSend }) =>
 | 
			
		||||
          commandBarSend({
 | 
			
		||||
        onClick: () =>
 | 
			
		||||
          commandBarActor.send({
 | 
			
		||||
            type: 'Find and select command',
 | 
			
		||||
            data: { name: 'Chamfer', groupId: 'modeling' },
 | 
			
		||||
          }),
 | 
			
		||||
@ -188,8 +187,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        id: 'shell',
 | 
			
		||||
        onClick: ({ commandBarSend }) => {
 | 
			
		||||
          commandBarSend({
 | 
			
		||||
        onClick: () => {
 | 
			
		||||
          commandBarActor.send({
 | 
			
		||||
            type: 'Find and select command',
 | 
			
		||||
            data: { name: 'Shell', groupId: 'modeling' },
 | 
			
		||||
          })
 | 
			
		||||
@ -269,8 +268,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
 | 
			
		||||
      [
 | 
			
		||||
        {
 | 
			
		||||
          id: 'plane-offset',
 | 
			
		||||
          onClick: ({ commandBarSend }) => {
 | 
			
		||||
            commandBarSend({
 | 
			
		||||
          onClick: () => {
 | 
			
		||||
            commandBarActor.send({
 | 
			
		||||
              type: 'Find and select command',
 | 
			
		||||
              data: { name: 'Offset plane', groupId: 'modeling' },
 | 
			
		||||
            })
 | 
			
		||||
@ -301,8 +300,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
 | 
			
		||||
      [
 | 
			
		||||
        {
 | 
			
		||||
          id: 'text-to-cad',
 | 
			
		||||
          onClick: ({ commandBarSend }) =>
 | 
			
		||||
            commandBarSend({
 | 
			
		||||
          onClick: () =>
 | 
			
		||||
            commandBarActor.send({
 | 
			
		||||
              type: 'Find and select command',
 | 
			
		||||
              data: { name: 'Text-to-CAD', groupId: 'modeling' },
 | 
			
		||||
            }),
 | 
			
		||||
@ -319,8 +318,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          id: 'prompt-to-edit',
 | 
			
		||||
          onClick: ({ commandBarSend }) =>
 | 
			
		||||
            commandBarSend({
 | 
			
		||||
          onClick: () =>
 | 
			
		||||
            commandBarActor.send({
 | 
			
		||||
              type: 'Find and select command',
 | 
			
		||||
              data: { name: 'Prompt-to-edit', groupId: 'modeling' },
 | 
			
		||||
            }),
 | 
			
		||||
@ -593,8 +592,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
 | 
			
		||||
        {
 | 
			
		||||
          id: 'constraint-length',
 | 
			
		||||
          disabled: (state) => !state.matches({ Sketch: 'SketchIdle' }),
 | 
			
		||||
          onClick: ({ commandBarSend }) =>
 | 
			
		||||
            commandBarSend({
 | 
			
		||||
          onClick: () =>
 | 
			
		||||
            commandBarActor.send({
 | 
			
		||||
              type: 'Find and select command',
 | 
			
		||||
              data: {
 | 
			
		||||
                name: 'Constrain length',
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { assign, fromPromise, setup } from 'xstate'
 | 
			
		||||
import { assign, createActor, fromPromise, setup, SnapshotFrom } from 'xstate'
 | 
			
		||||
import {
 | 
			
		||||
  Command,
 | 
			
		||||
  CommandArgument,
 | 
			
		||||
@ -9,6 +9,7 @@ import { Selections__old } from 'lib/selections'
 | 
			
		||||
import { getCommandArgumentKclValuesOnly } from 'lib/commandUtils'
 | 
			
		||||
import { MachineManager } from 'components/MachineManagerProvider'
 | 
			
		||||
import toast from 'react-hot-toast'
 | 
			
		||||
import { useSelector } from '@xstate/react'
 | 
			
		||||
 | 
			
		||||
export type CommandBarContext = {
 | 
			
		||||
  commands: Command[]
 | 
			
		||||
@ -247,8 +248,17 @@ export const commandBarMachine = setup({
 | 
			
		||||
  guards: {
 | 
			
		||||
    'Command needs review': ({ context }) =>
 | 
			
		||||
      context.selectedCommand?.needsReview || false,
 | 
			
		||||
    'Command has no arguments': () => false,
 | 
			
		||||
    'All arguments are skippable': () => false,
 | 
			
		||||
    'Command has no arguments': ({ context }) => {
 | 
			
		||||
      return (
 | 
			
		||||
        !context.selectedCommand?.args ||
 | 
			
		||||
        Object.keys(context.selectedCommand?.args).length === 0
 | 
			
		||||
      )
 | 
			
		||||
    },
 | 
			
		||||
    'All arguments are skippable': ({ context }) => {
 | 
			
		||||
      return Object.values(context.selectedCommand!.args!).every(
 | 
			
		||||
        (argConfig) => argConfig.skip
 | 
			
		||||
      )
 | 
			
		||||
    },
 | 
			
		||||
    'Has selected command': ({ context }) => !!context.selectedCommand,
 | 
			
		||||
  },
 | 
			
		||||
  actors: {
 | 
			
		||||
@ -620,3 +630,12 @@ function sortCommands(a: Command, b: Command) {
 | 
			
		||||
  if (a.groupId === 'settings' && !(b.groupId === 'settings')) return 1
 | 
			
		||||
  return a.name.localeCompare(b.name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const commandBarActor = createActor(commandBarMachine).start()
 | 
			
		||||
 | 
			
		||||
/** Basic state snapshot selector */
 | 
			
		||||
const cmdBarStateSelector = (state: SnapshotFrom<typeof commandBarActor>) =>
 | 
			
		||||
  state
 | 
			
		||||
export const useCommandBarState = () => {
 | 
			
		||||
  return useSelector(commandBarActor, cmdBarStateSelector)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -24,13 +24,12 @@ import { markOnce } from 'lib/performance'
 | 
			
		||||
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
 | 
			
		||||
import { useProjectsLoader } from 'hooks/useProjectsLoader'
 | 
			
		||||
import { useProjectsContext } from 'hooks/useProjectsContext'
 | 
			
		||||
import { useCommandsContext } from 'hooks/useCommandsContext'
 | 
			
		||||
import { commandBarActor } from 'machines/commandBarMachine'
 | 
			
		||||
 | 
			
		||||
// This route only opens in the desktop context for now,
 | 
			
		||||
// as defined in Router.tsx, so we can use the desktop APIs and types.
 | 
			
		||||
const Home = () => {
 | 
			
		||||
  const { state, send } = useProjectsContext()
 | 
			
		||||
  const { commandBarSend } = useCommandsContext()
 | 
			
		||||
  const [projectsLoaderTrigger, setProjectsLoaderTrigger] = useState(0)
 | 
			
		||||
  const { projectsDir } = useProjectsLoader([projectsLoaderTrigger])
 | 
			
		||||
 | 
			
		||||
@ -128,7 +127,7 @@ const Home = () => {
 | 
			
		||||
              <ActionButton
 | 
			
		||||
                Element="button"
 | 
			
		||||
                onClick={() =>
 | 
			
		||||
                  commandBarSend({
 | 
			
		||||
                  commandBarActor.send({
 | 
			
		||||
                    type: 'Find and select command',
 | 
			
		||||
                    data: {
 | 
			
		||||
                      groupId: 'projects',
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user