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:
Frank Noirot
2025-01-23 10:25:21 -05:00
committed by GitHub
parent 3adb42b5f2
commit 8ef31a0be1
36 changed files with 174 additions and 242 deletions

View File

@ -31,7 +31,6 @@ import {
settingsLoader, settingsLoader,
telemetryLoader, telemetryLoader,
} from 'lib/routeLoaders' } from 'lib/routeLoaders'
import { CommandBarProvider } from 'components/CommandBar/CommandBarProvider'
import SettingsAuthProvider from 'components/SettingsAuthProvider' import SettingsAuthProvider from 'components/SettingsAuthProvider'
import LspProvider from 'components/LspProvider' import LspProvider from 'components/LspProvider'
import { KclContextProvider } from 'lang/KclProvider' import { KclContextProvider } from 'lang/KclProvider'
@ -58,23 +57,21 @@ const router = createRouter([
/* Make sure auth is the outermost provider or else we will have /* Make sure auth is the outermost provider or else we will have
* inefficient re-renders, use the react profiler to see. */ * inefficient re-renders, use the react profiler to see. */
element: ( element: (
<CommandBarProvider> <RouteProvider>
<RouteProvider> <SettingsAuthProvider>
<SettingsAuthProvider> <LspProvider>
<LspProvider> <ProjectsContextProvider>
<ProjectsContextProvider> <KclContextProvider>
<KclContextProvider> <AppStateProvider>
<AppStateProvider> <MachineManagerProvider>
<MachineManagerProvider> <Outlet />
<Outlet /> </MachineManagerProvider>
</MachineManagerProvider> </AppStateProvider>
</AppStateProvider> </KclContextProvider>
</KclContextProvider> </ProjectsContextProvider>
</ProjectsContextProvider> </LspProvider>
</LspProvider> </SettingsAuthProvider>
</SettingsAuthProvider> </RouteProvider>
</RouteProvider>
</CommandBarProvider>
), ),
errorElement: <ErrorPage />, errorElement: <ErrorPage />,
children: [ children: [

View File

@ -2,7 +2,6 @@ import { useRef, useMemo, memo, useCallback, useState } from 'react'
import { isCursorInSketchCommandRange } from 'lang/util' import { isCursorInSketchCommandRange } from 'lang/util'
import { engineCommandManager, kclManager } from 'lib/singletons' import { engineCommandManager, kclManager } from 'lib/singletons'
import { useModelingContext } from 'hooks/useModelingContext' import { useModelingContext } from 'hooks/useModelingContext'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { useNetworkContext } from 'hooks/useNetworkContext' import { useNetworkContext } from 'hooks/useNetworkContext'
import { NetworkHealthState } from 'hooks/useNetworkStatus' import { NetworkHealthState } from 'hooks/useNetworkStatus'
import { ActionButton } from 'components/ActionButton' import { ActionButton } from 'components/ActionButton'
@ -22,13 +21,13 @@ import {
} from 'lib/toolbar' } from 'lib/toolbar'
import { isDesktop } from 'lib/isDesktop' import { isDesktop } from 'lib/isDesktop'
import { openExternalBrowserIfDesktop } from 'lib/openWindow' import { openExternalBrowserIfDesktop } from 'lib/openWindow'
import { commandBarActor } from 'machines/commandBarMachine'
export function Toolbar({ export function Toolbar({
className = '', className = '',
...props ...props
}: React.HTMLAttributes<HTMLElement>) { }: React.HTMLAttributes<HTMLElement>) {
const { state, send, context } = useModelingContext() const { state, send, context } = useModelingContext()
const { commandBarSend } = useCommandsContext()
const iconClassName = const iconClassName =
'group-disabled:text-chalkboard-50 !text-inherit dark:group-enabled:group-hover:!text-inherit' 'group-disabled:text-chalkboard-50 !text-inherit dark:group-enabled:group-hover:!text-inherit'
const bgClassName = '!bg-transparent' const bgClassName = '!bg-transparent'
@ -71,10 +70,9 @@ export function Toolbar({
() => ({ () => ({
modelingState: state, modelingState: state,
modelingSend: send, modelingSend: send,
commandBarSend,
sketchPathId, sketchPathId,
}), }),
[state, send, commandBarSend, sketchPathId] [state, send, commandBarActor.send, sketchPathId]
) )
const tooltipContentClassName = !showRichContent const tooltipContentClassName = !showRichContent

View File

@ -46,8 +46,8 @@ import {
} from 'lang/modifyAst' } from 'lang/modifyAst'
import { ActionButton } from 'components/ActionButton' import { ActionButton } from 'components/ActionButton'
import { err, reportRejection, trap } from 'lib/trap' import { err, reportRejection, trap } from 'lib/trap'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { Node } from 'wasm-lib/kcl/bindings/Node' import { Node } from 'wasm-lib/kcl/bindings/Node'
import { commandBarActor } from 'machines/commandBarMachine'
function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } { function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } {
const [isCamMoving, setIsCamMoving] = useState(false) const [isCamMoving, setIsCamMoving] = useState(false)
@ -510,7 +510,6 @@ const ConstraintSymbol = ({
constrainInfo: ConstrainInfo constrainInfo: ConstrainInfo
verticalPosition: 'top' | 'bottom' verticalPosition: 'top' | 'bottom'
}) => { }) => {
const { commandBarSend } = useCommandsContext()
const { context } = useModelingContext() const { context } = useModelingContext()
const varNameMap: { const varNameMap: {
[key in ConstrainInfo['type']]: { [key in ConstrainInfo['type']]: {
@ -630,7 +629,7 @@ const ConstraintSymbol = ({
// disabled={implicitDesc} TODO why does this change styles that are hard to override? // disabled={implicitDesc} TODO why does this change styles that are hard to override?
onClick={toSync(async () => { onClick={toSync(async () => {
if (!isConstrained) { if (!isConstrained) {
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { data: {
name: 'Constrain with named value', name: 'Constrain with named value',
@ -756,7 +755,6 @@ export const CamDebugSettings = () => {
sceneInfra.camControls.reactCameraProperties sceneInfra.camControls.reactCameraProperties
) )
const [fov, setFov] = useState(12) const [fov, setFov] = useState(12)
const { commandBarSend } = useCommandsContext()
useEffect(() => { useEffect(() => {
sceneInfra.camControls.setReactCameraPropertiesCallback(setCamSettings) sceneInfra.camControls.setReactCameraPropertiesCallback(setCamSettings)
@ -775,7 +773,7 @@ export const CamDebugSettings = () => {
type="checkbox" type="checkbox"
checked={camSettings.type === 'perspective'} checked={camSettings.type === 'perspective'}
onChange={() => onChange={() =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { data: {
groupId: 'settings', groupId: 'settings',

View File

@ -61,6 +61,7 @@ import { SegmentInputs } from 'lang/std/stdTypes'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { editorManager, sceneInfra } from 'lib/singletons' import { editorManager, sceneInfra } from 'lib/singletons'
import { Selections } from 'lib/selections' import { Selections } from 'lib/selections'
import { commandBarActor } from 'machines/commandBarMachine'
interface CreateSegmentArgs { interface CreateSegmentArgs {
input: SegmentInputs input: SegmentInputs
@ -847,7 +848,7 @@ function createLengthIndicator({
}) })
// Command Bar // Command Bar
editorManager.commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { data: {
name: 'Constrain length', name: 'Constrain length',

View File

@ -1,8 +1,8 @@
import { Combobox } from '@headlessui/react' import { Combobox } from '@headlessui/react'
import { useSelector } from '@xstate/react' import { useSelector } from '@xstate/react'
import Fuse from 'fuse.js' import Fuse from 'fuse.js'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { CommandArgument, CommandArgumentOption } from 'lib/commandTypes' import { CommandArgument, CommandArgumentOption } from 'lib/commandTypes'
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
import { useEffect, useMemo, useRef, useState } from 'react' import { useEffect, useMemo, useRef, useState } from 'react'
import { AnyStateMachine, StateFrom } from 'xstate' import { AnyStateMachine, StateFrom } from 'xstate'
@ -23,7 +23,7 @@ function CommandArgOptionInput({
placeholder?: string placeholder?: string
}) { }) {
const actorContext = useSelector(arg.machineActor, contextSelector) const actorContext = useSelector(arg.machineActor, contextSelector)
const { commandBarSend, commandBarState } = useCommandsContext() const commandBarState = useCommandBarState()
const resolvedOptions = useMemo( const resolvedOptions = useMemo(
() => () =>
typeof arg.options === 'function' 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" 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) => { onKeyDown={(event) => {
if (event.metaKey && event.key === 'k') if (event.metaKey && event.key === 'k')
commandBarSend({ type: 'Close' }) commandBarActor.send({ type: 'Close' })
if (event.key === 'Backspace' && !event.currentTarget.value) { if (event.key === 'Backspace' && !event.currentTarget.value) {
stepBack() stepBack()
} }

View File

@ -1,6 +1,5 @@
import { Dialog, Popover, Transition } from '@headlessui/react' import { Dialog, Popover, Transition } from '@headlessui/react'
import { Fragment, useEffect } from 'react' import { Fragment, useEffect } from 'react'
import { useCommandsContext } from 'hooks/useCommandsContext'
import CommandBarArgument from './CommandBarArgument' import CommandBarArgument from './CommandBarArgument'
import CommandComboBox from '../CommandComboBox' import CommandComboBox from '../CommandComboBox'
import CommandBarReview from './CommandBarReview' import CommandBarReview from './CommandBarReview'
@ -8,12 +7,13 @@ import { useLocation } from 'react-router-dom'
import useHotkeyWrapper from 'lib/hotkeyWrapper' import useHotkeyWrapper from 'lib/hotkeyWrapper'
import { CustomIcon } from 'components/CustomIcon' import { CustomIcon } from 'components/CustomIcon'
import Tooltip from 'components/Tooltip' import Tooltip from 'components/Tooltip'
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
export const COMMAND_PALETTE_HOTKEY = 'mod+k' export const COMMAND_PALETTE_HOTKEY = 'mod+k'
export const CommandBar = () => { export const CommandBar = () => {
const { pathname } = useLocation() const { pathname } = useLocation()
const { commandBarState, commandBarSend } = useCommandsContext() const commandBarState = useCommandBarState()
const { const {
context: { selectedCommand, currentArgument, commands }, context: { selectedCommand, currentArgument, commands },
} = commandBarState } = commandBarState
@ -23,16 +23,16 @@ export const CommandBar = () => {
// Close the command bar when navigating // Close the command bar when navigating
useEffect(() => { useEffect(() => {
if (commandBarState.matches('Closed')) return if (commandBarState.matches('Closed')) return
commandBarSend({ type: 'Close' }) commandBarActor.send({ type: 'Close' })
}, [pathname]) }, [pathname])
// Hook up keyboard shortcuts // Hook up keyboard shortcuts
useHotkeyWrapper([COMMAND_PALETTE_HOTKEY], () => { useHotkeyWrapper([COMMAND_PALETTE_HOTKEY], () => {
if (commandBarState.context.commands.length === 0) return if (commandBarState.context.commands.length === 0) return
if (commandBarState.matches('Closed')) { if (commandBarState.matches('Closed')) {
commandBarSend({ type: 'Open' }) commandBarActor.send({ type: 'Open' })
} else { } else {
commandBarSend({ type: 'Close' }) commandBarActor.send({ type: 'Close' })
} }
}) })
@ -52,14 +52,14 @@ export const CommandBar = () => {
...entries[entries.length - 1][1], ...entries[entries.length - 1][1],
} }
commandBarSend({ commandBarActor.send({
type: 'Edit argument', type: 'Edit argument',
data: { data: {
arg: currentArg, arg: currentArg,
}, },
}) })
} else { } else {
commandBarSend({ type: 'Deselect command' }) commandBarActor.send({ type: 'Deselect command' })
} }
} else { } else {
const entries = Object.entries(selectedCommand?.args || {}) const entries = Object.entries(selectedCommand?.args || {})
@ -68,9 +68,9 @@ export const CommandBar = () => {
) )
if (index === 0) { if (index === 0) {
commandBarSend({ type: 'Deselect command' }) commandBarActor.send({ type: 'Deselect command' })
} else { } else {
commandBarSend({ commandBarActor.send({
type: 'Change current argument', type: 'Change current argument',
data: { data: {
arg: { name: entries[index - 1][0], ...entries[index - 1][1] }, arg: { name: entries[index - 1][0], ...entries[index - 1][1] },
@ -85,14 +85,14 @@ export const CommandBar = () => {
show={!commandBarState.matches('Closed') || false} show={!commandBarState.matches('Closed') || false}
afterLeave={() => { afterLeave={() => {
if (selectedCommand?.onCancel) selectedCommand.onCancel() if (selectedCommand?.onCancel) selectedCommand.onCancel()
commandBarSend({ type: 'Clear' }) commandBarActor.send({ type: 'Clear' })
}} }}
as={Fragment} as={Fragment}
> >
<WrapperComponent <WrapperComponent
open={!commandBarState.matches('Closed') || isSelectionArgument} open={!commandBarState.matches('Closed') || isSelectionArgument}
onClose={() => { onClose={() => {
commandBarSend({ type: 'Close' }) commandBarActor.send({ type: 'Close' })
}} }}
className={ className={
'fixed inset-0 z-50 overflow-y-auto pb-4 pt-1 ' + 'fixed inset-0 z-50 overflow-y-auto pb-4 pt-1 ' +
@ -122,7 +122,7 @@ export const CommandBar = () => {
) )
)} )}
<button <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" className="group block !absolute left-auto right-full top-[-3px] m-2.5 p-0 border-none bg-transparent hover:bg-transparent"
> >
<CustomIcon <CustomIcon

View File

@ -2,13 +2,13 @@ import CommandArgOptionInput from './CommandArgOptionInput'
import CommandBarBasicInput from './CommandBarBasicInput' import CommandBarBasicInput from './CommandBarBasicInput'
import CommandBarSelectionInput from './CommandBarSelectionInput' import CommandBarSelectionInput from './CommandBarSelectionInput'
import { CommandArgument } from 'lib/commandTypes' import { CommandArgument } from 'lib/commandTypes'
import { useCommandsContext } from 'hooks/useCommandsContext'
import CommandBarHeader from './CommandBarHeader' import CommandBarHeader from './CommandBarHeader'
import CommandBarKclInput from './CommandBarKclInput' import CommandBarKclInput from './CommandBarKclInput'
import CommandBarTextareaInput from './CommandBarTextareaInput' import CommandBarTextareaInput from './CommandBarTextareaInput'
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
function CommandBarArgument({ stepBack }: { stepBack: () => void }) { function CommandBarArgument({ stepBack }: { stepBack: () => void }) {
const { commandBarState, commandBarSend } = useCommandsContext() const commandBarState = useCommandBarState()
const { const {
context: { currentArgument }, context: { currentArgument },
} = commandBarState } = commandBarState
@ -16,7 +16,7 @@ function CommandBarArgument({ stepBack }: { stepBack: () => void }) {
function onSubmit(data: unknown) { function onSubmit(data: unknown) {
if (!currentArgument) return if (!currentArgument) return
commandBarSend({ commandBarActor.send({
type: 'Submit argument', type: 'Submit argument',
data: { data: {
[currentArgument.name]: data, [currentArgument.name]: data,

View File

@ -1,5 +1,5 @@
import { useCommandsContext } from 'hooks/useCommandsContext'
import { CommandArgument } from 'lib/commandTypes' import { CommandArgument } from 'lib/commandTypes'
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
import { useEffect, useRef } from 'react' import { useEffect, useRef } from 'react'
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
@ -15,8 +15,8 @@ function CommandBarBasicInput({
stepBack: () => void stepBack: () => void
onSubmit: (event: unknown) => void onSubmit: (event: unknown) => void
}) { }) {
const { commandBarSend, commandBarState } = useCommandsContext() const commandBarState = useCommandBarState()
useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' })) useHotkeys('mod + k, mod + /', () => commandBarActor.send({ type: 'Close' }))
const inputRef = useRef<HTMLInputElement>(null) const inputRef = useRef<HTMLInputElement>(null)
useEffect(() => { useEffect(() => {

View File

@ -1,4 +1,3 @@
import { useCommandsContext } from 'hooks/useCommandsContext'
import { CustomIcon } from '../CustomIcon' import { CustomIcon } from '../CustomIcon'
import React, { useState } from 'react' import React, { useState } from 'react'
import { ActionButton } from '../ActionButton' import { ActionButton } from '../ActionButton'
@ -7,9 +6,10 @@ import { useHotkeys } from 'react-hotkeys-hook'
import { KclCommandValue, KclExpressionWithVariable } from 'lib/commandTypes' import { KclCommandValue, KclExpressionWithVariable } from 'lib/commandTypes'
import Tooltip from 'components/Tooltip' import Tooltip from 'components/Tooltip'
import { roundOff } from 'lib/utils' import { roundOff } from 'lib/utils'
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
function CommandBarHeader({ children }: React.PropsWithChildren<{}>) { function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
const { commandBarState, commandBarSend } = useCommandsContext() const commandBarState = useCommandBarState()
const { const {
context: { selectedCommand, currentArgument, argumentsToSubmit }, context: { selectedCommand, currentArgument, argumentsToSubmit },
} = commandBarState } = commandBarState
@ -49,7 +49,7 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
] ]
const arg = selectedCommand?.args[argName] const arg = selectedCommand?.args[argName]
if (!argName || !arg) return if (!argName || !arg) return
commandBarSend({ commandBarActor.send({
type: 'Change current argument', type: 'Change current argument',
data: { arg: { ...arg, name: argName } }, data: { arg: { ...arg, name: argName } },
}) })
@ -100,7 +100,7 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
} }
disabled={!isReviewing && currentArgument?.name === argName} disabled={!isReviewing && currentArgument?.name === argName}
onClick={() => { onClick={() => {
commandBarSend({ commandBarActor.send({
type: isReviewing type: isReviewing
? 'Edit argument' ? 'Edit argument'
: 'Change current argument', : 'Change current argument',

View File

@ -7,7 +7,6 @@ import {
} from '@codemirror/autocomplete' } from '@codemirror/autocomplete'
import { EditorView, keymap, ViewUpdate } from '@codemirror/view' import { EditorView, keymap, ViewUpdate } from '@codemirror/view'
import { CustomIcon } from 'components/CustomIcon' import { CustomIcon } from 'components/CustomIcon'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { CommandArgument, KclCommandValue } from 'lib/commandTypes' import { CommandArgument, KclCommandValue } from 'lib/commandTypes'
import { getSystemTheme } from 'lib/theme' import { getSystemTheme } from 'lib/theme'
@ -20,6 +19,7 @@ import styles from './CommandBarKclInput.module.css'
import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst' import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst'
import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor' import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor'
import { useSelector } from '@xstate/react' import { useSelector } from '@xstate/react'
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
const machineContextSelector = (snapshot?: { const machineContextSelector = (snapshot?: {
context: Record<string, unknown> context: Record<string, unknown>
@ -37,7 +37,7 @@ function CommandBarKclInput({
stepBack: () => void stepBack: () => void
onSubmit: (event: unknown) => void onSubmit: (event: unknown) => void
}) { }) {
const { commandBarSend, commandBarState } = useCommandsContext() const commandBarState = useCommandBarState()
const previouslySetValue = commandBarState.context.argumentsToSubmit[ const previouslySetValue = commandBarState.context.argumentsToSubmit[
arg.name arg.name
] as KclCommandValue | undefined ] as KclCommandValue | undefined
@ -82,7 +82,7 @@ function CommandBarKclInput({
false false
) )
const [canSubmit, setCanSubmit] = useState(true) 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 editorRef = useRef<HTMLDivElement>(null)
const { const {

View File

@ -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
}

View File

@ -1,9 +1,9 @@
import { useCommandsContext } from 'hooks/useCommandsContext' import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
import CommandBarHeader from './CommandBarHeader' import CommandBarHeader from './CommandBarHeader'
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
function CommandBarReview({ stepBack }: { stepBack: () => void }) { function CommandBarReview({ stepBack }: { stepBack: () => void }) {
const { commandBarState, commandBarSend } = useCommandsContext() const commandBarState = useCommandBarState()
const { const {
context: { argumentsToSubmit, selectedCommand }, context: { argumentsToSubmit, selectedCommand },
} = commandBarState } = commandBarState
@ -33,7 +33,7 @@ function CommandBarReview({ stepBack }: { stepBack: () => void }) {
parseInt(b.keys[0], 10) - 1 parseInt(b.keys[0], 10) - 1
] ]
const arg = selectedCommand?.args[argName] const arg = selectedCommand?.args[argName]
commandBarSend({ commandBarActor.send({
type: 'Edit argument', type: 'Edit argument',
data: { arg: { ...arg, name: argName } }, data: { arg: { ...arg, name: argName } },
}) })
@ -50,7 +50,7 @@ function CommandBarReview({ stepBack }: { stepBack: () => void }) {
function submitCommand(e: React.FormEvent<HTMLFormElement>) { function submitCommand(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault() e.preventDefault()
commandBarSend({ commandBarActor.send({
type: 'Submit command', type: 'Submit command',
output: argumentsToSubmit, output: argumentsToSubmit,
}) })

View File

@ -1,5 +1,4 @@
import { useSelector } from '@xstate/react' import { useSelector } from '@xstate/react'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { Artifact } from 'lang/std/artifactGraph' import { Artifact } from 'lang/std/artifactGraph'
import { CommandArgument } from 'lib/commandTypes' import { CommandArgument } from 'lib/commandTypes'
import { import {
@ -10,6 +9,7 @@ import {
import { kclManager } from 'lib/singletons' import { kclManager } from 'lib/singletons'
import { reportRejection } from 'lib/trap' import { reportRejection } from 'lib/trap'
import { toSync } from 'lib/utils' import { toSync } from 'lib/utils'
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
import { modelingMachine } from 'machines/modelingMachine' import { modelingMachine } from 'machines/modelingMachine'
import { useEffect, useMemo, useRef, useState } from 'react' import { useEffect, useMemo, useRef, useState } from 'react'
import { StateFrom } from 'xstate' import { StateFrom } from 'xstate'
@ -49,7 +49,7 @@ function CommandBarSelectionInput({
onSubmit: (data: unknown) => void onSubmit: (data: unknown) => void
}) { }) {
const inputRef = useRef<HTMLInputElement>(null) const inputRef = useRef<HTMLInputElement>(null)
const { commandBarState, commandBarSend } = useCommandsContext() const commandBarState = useCommandBarState()
const [hasSubmitted, setHasSubmitted] = useState(false) const [hasSubmitted, setHasSubmitted] = useState(false)
const selection = useSelector(arg.machineActor, selectionSelector) const selection = useSelector(arg.machineActor, selectionSelector)
const selectionsByType = useMemo(() => { const selectionsByType = useMemo(() => {
@ -145,7 +145,7 @@ function CommandBarSelectionInput({
if (event.key === 'Backspace') { if (event.key === 'Backspace') {
stepBack() stepBack()
} else if (event.key === 'Escape') { } else if (event.key === 'Escape') {
commandBarSend({ type: 'Close' }) commandBarActor.send({ type: 'Close' })
} }
}} }}
onChange={handleChange} onChange={handleChange}

View File

@ -1,5 +1,5 @@
import { useCommandsContext } from 'hooks/useCommandsContext'
import { CommandArgument } from 'lib/commandTypes' import { CommandArgument } from 'lib/commandTypes'
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
import { RefObject, useEffect, useRef } from 'react' import { RefObject, useEffect, useRef } from 'react'
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
@ -15,8 +15,8 @@ function CommandBarTextareaInput({
stepBack: () => void stepBack: () => void
onSubmit: (event: unknown) => void onSubmit: (event: unknown) => void
}) { }) {
const { commandBarSend, commandBarState } = useCommandsContext() const commandBarState = useCommandBarState()
useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' })) useHotkeys('mod + k, mod + /', () => commandBarActor.send({ type: 'Close' }))
const formRef = useRef<HTMLFormElement>(null) const formRef = useRef<HTMLFormElement>(null)
const inputRef = useRef<HTMLTextAreaElement>(null) const inputRef = useRef<HTMLTextAreaElement>(null)
useTextareaAutoGrow(inputRef) useTextareaAutoGrow(inputRef)

View File

@ -1,16 +1,15 @@
import { useCommandsContext } from 'hooks/useCommandsContext'
import usePlatform from 'hooks/usePlatform' import usePlatform from 'hooks/usePlatform'
import { hotkeyDisplay } from 'lib/hotkeyWrapper' import { hotkeyDisplay } from 'lib/hotkeyWrapper'
import { COMMAND_PALETTE_HOTKEY } from './CommandBar/CommandBar' import { COMMAND_PALETTE_HOTKEY } from './CommandBar/CommandBar'
import { commandBarActor } from 'machines/commandBarMachine'
export function CommandBarOpenButton() { export function CommandBarOpenButton() {
const { commandBarSend } = useCommandsContext()
const platform = usePlatform() const platform = usePlatform()
return ( return (
<button <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" 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" data-testid="command-bar-open-button"
> >
<span>Commands</span> <span>Commands</span>

View File

@ -1,11 +1,11 @@
import { Combobox } from '@headlessui/react' import { Combobox } from '@headlessui/react'
import Fuse from 'fuse.js' import Fuse from 'fuse.js'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { Command } from 'lib/commandTypes' import { Command } from 'lib/commandTypes'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { CustomIcon } from './CustomIcon' import { CustomIcon } from './CustomIcon'
import { getActorNextEvents } from 'lib/utils' import { getActorNextEvents } from 'lib/utils'
import { sortCommands } from 'lib/commandUtils' import { sortCommands } from 'lib/commandUtils'
import { commandBarActor } from 'machines/commandBarMachine'
function CommandComboBox({ function CommandComboBox({
options, options,
@ -14,7 +14,6 @@ function CommandComboBox({
options: Command[] options: Command[]
placeholder?: string placeholder?: string
}) { }) {
const { commandBarSend } = useCommandsContext()
const [query, setQuery] = useState('') const [query, setQuery] = useState('')
const [filteredOptions, setFilteredOptions] = useState<typeof options>() const [filteredOptions, setFilteredOptions] = useState<typeof options>()
@ -41,7 +40,7 @@ function CommandComboBox({
}, [query]) }, [query])
function handleSelection(command: Command) { function handleSelection(command: Command) {
commandBarSend({ type: 'Select command', data: { command } }) commandBarActor.send({ type: 'Select command', data: { command } })
} }
return ( return (
@ -61,7 +60,7 @@ function CommandComboBox({
(event.key === 'Backspace' && !event.currentTarget.value) (event.key === 'Backspace' && !event.currentTarget.value)
) { ) {
event.preventDefault() event.preventDefault()
commandBarSend({ type: 'Close' }) commandBarActor.send({ type: 'Close' })
} }
}} }}
placeholder={ placeholder={

View File

@ -12,7 +12,6 @@ import {
StateFrom, StateFrom,
fromPromise, fromPromise,
} from 'xstate' } from 'xstate'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { fileMachine } from 'machines/fileMachine' import { fileMachine } from 'machines/fileMachine'
import { isDesktop } from 'lib/isDesktop' import { isDesktop } from 'lib/isDesktop'
import { import {
@ -30,6 +29,7 @@ import {
} from 'lib/getKclSamplesManifest' } from 'lib/getKclSamplesManifest'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { markOnce } from 'lib/performance' import { markOnce } from 'lib/performance'
import { commandBarActor } from 'machines/commandBarMachine'
type MachineContext<T extends AnyStateMachine> = { type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T> state: StateFrom<T>
@ -47,7 +47,6 @@ export const FileMachineProvider = ({
children: React.ReactNode children: React.ReactNode
}) => { }) => {
const navigate = useNavigate() const navigate = useNavigate()
const { commandBarSend } = useCommandsContext()
const { settings } = useSettingsAuthContext() const { settings } = useSettingsAuthContext()
const { project, file } = useRouteLoaderData(PATHS.FILE) as IndexLoaderData const { project, file } = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
const [kclSamples, setKclSamples] = React.useState<KclSamplesManifestItem[]>( const [kclSamples, setKclSamples] = React.useState<KclSamplesManifestItem[]>(
@ -90,7 +89,7 @@ export const FileMachineProvider = ({
navigateToFile: ({ context, event }) => { navigateToFile: ({ context, event }) => {
if (event.type !== 'xstate.done.actor.create-and-open-file') return if (event.type !== 'xstate.done.actor.create-and-open-file') return
if (event.output && 'name' in event.output) { if (event.output && 'name' in event.output) {
commandBarSend({ type: 'Close' }) commandBarActor.send({ type: 'Close' })
navigate( navigate(
`..${PATHS.FILE}/${encodeURIComponent( `..${PATHS.FILE}/${encodeURIComponent(
context.selectedDirectory + context.selectedDirectory +
@ -336,15 +335,18 @@ export const FileMachineProvider = ({
) )
useEffect(() => { useEffect(() => {
commandBarSend({ type: 'Add commands', data: { commands: kclCommandMemo } }) commandBarActor.send({
type: 'Add commands',
data: { commands: kclCommandMemo },
})
return () => { return () => {
commandBarSend({ commandBarActor.send({
type: 'Remove commands', type: 'Remove commands',
data: { commands: kclCommandMemo }, data: { commands: kclCommandMemo },
}) })
} }
}, [commandBarSend, kclCommandMemo]) }, [commandBarActor.send, kclCommandMemo])
return ( return (
<FileContext.Provider <FileContext.Provider

View File

@ -1,11 +1,11 @@
import { createContext, useEffect, useState } from 'react' import { createContext, useEffect, useState } from 'react'
import { engineCommandManager } from 'lib/singletons' import { engineCommandManager } from 'lib/singletons'
import { CommandsContext } from 'components/CommandBar/CommandBarProvider'
import { isDesktop } from 'lib/isDesktop' import { isDesktop } from 'lib/isDesktop'
import { components } from 'lib/machine-api' import { components } from 'lib/machine-api'
import { reportRejection } from 'lib/trap' import { reportRejection } from 'lib/trap'
import { toSync } from 'lib/utils' import { toSync } from 'lib/utils'
import { commandBarActor } from 'machines/commandBarMachine'
export type MachinesListing = Array< export type MachinesListing = Array<
components['schemas']['MachineInfoResponse'] components['schemas']['MachineInfoResponse']
@ -42,8 +42,6 @@ export const MachineManagerProvider = ({
components['schemas']['MachineInfoResponse'] | null components['schemas']['MachineInfoResponse'] | null
>(null) >(null)
const commandBarActor = CommandsContext.useActorRef()
// Get the reason message for why there are no machines. // Get the reason message for why there are no machines.
const noMachinesReason = (): string | undefined => { const noMachinesReason = (): string | undefined => {
if (machines.length > 0) { if (machines.length > 0) {

View File

@ -1,4 +1,4 @@
import { useMachine } from '@xstate/react' import { useMachine, useSelector } from '@xstate/react'
import React, { import React, {
createContext, createContext,
useEffect, useEffect,
@ -11,6 +11,7 @@ import {
AnyStateMachine, AnyStateMachine,
ContextFrom, ContextFrom,
Prop, Prop,
SnapshotFrom,
StateFrom, StateFrom,
assign, assign,
fromPromise, fromPromise,
@ -78,7 +79,6 @@ import toast from 'react-hot-toast'
import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom' import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls' import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
import { err, reportRejection, trap } from 'lib/trap' import { err, reportRejection, trap } from 'lib/trap'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { import {
ExportIntent, ExportIntent,
EngineConnectionStateType, EngineConnectionStateType,
@ -91,6 +91,7 @@ import { IndexLoaderData } from 'lib/types'
import { Node } from 'wasm-lib/kcl/bindings/Node' import { Node } from 'wasm-lib/kcl/bindings/Node'
import { promptToEditFlow } from 'lib/promptToEdit' import { promptToEditFlow } from 'lib/promptToEdit'
import { kclEditorActor } from 'machines/kclEditorMachine' import { kclEditorActor } from 'machines/kclEditorMachine'
import { commandBarActor } from 'machines/commandBarMachine'
type MachineContext<T extends AnyStateMachine> = { type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T> state: StateFrom<T>
@ -102,6 +103,10 @@ export const ModelingMachineContext = createContext(
{} as MachineContext<typeof modelingMachine> {} as MachineContext<typeof modelingMachine>
) )
const commandBarIsClosedSelector = (
state: SnapshotFrom<typeof commandBarActor>
) => state.matches('Closed')
export const ModelingMachineProvider = ({ export const ModelingMachineProvider = ({
children, children,
}: { }: {
@ -132,8 +137,10 @@ export const ModelingMachineProvider = ({
let [searchParams] = useSearchParams() let [searchParams] = useSearchParams()
const pool = searchParams.get('pool') const pool = searchParams.get('pool')
const { commandBarState, commandBarSend } = useCommandsContext() const isCommandBarClosed = useSelector(
commandBarActor,
commandBarIsClosedSelector
)
// Settings machine setup // Settings machine setup
// const retrievedSettings = useRef( // const retrievedSettings = useRef(
// localStorage?.getItem(MODELING_PERSIST_KEY) || '{}' // localStorage?.getItem(MODELING_PERSIST_KEY) || '{}'
@ -538,7 +545,6 @@ export const ModelingMachineProvider = ({
trimmedPrompt, trimmedPrompt,
fileMachineSend, fileMachineSend,
navigate, navigate,
commandBarSend,
context, context,
token, token,
settings: { settings: {
@ -552,7 +558,7 @@ export const ModelingMachineProvider = ({
'has valid selection for deletion': ({ 'has valid selection for deletion': ({
context: { selectionRanges }, context: { selectionRanges },
}) => { }) => {
if (!commandBarState.matches('Closed')) return false if (!isCommandBarClosed) return false
if (selectionRanges.graphSelections.length <= 0) return false if (selectionRanges.graphSelections.length <= 0) return false
return true return true
}, },

View File

@ -9,12 +9,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { kclManager } from 'lib/singletons' import { kclManager } from 'lib/singletons'
import { openExternalBrowserIfDesktop } from 'lib/openWindow' import { openExternalBrowserIfDesktop } from 'lib/openWindow'
import { reportRejection } from 'lib/trap' import { reportRejection } from 'lib/trap'
import { useCommandsContext } from 'hooks/useCommandsContext' import { commandBarActor } from 'machines/commandBarMachine'
export const KclEditorMenu = ({ children }: PropsWithChildren) => { export const KclEditorMenu = ({ children }: PropsWithChildren) => {
const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } = const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } =
useConvertToVariable() useConvertToVariable()
const { commandBarSend } = useCommandsContext()
return ( return (
<Menu> <Menu>
@ -85,7 +84,7 @@ export const KclEditorMenu = ({ children }: PropsWithChildren) => {
<Menu.Item> <Menu.Item>
<button <button
onClick={() => { onClick={() => {
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { data: {
groupId: 'code', groupId: 'code',

View File

@ -15,12 +15,12 @@ import { ModelingPane } from './ModelingPane'
import { isDesktop } from 'lib/isDesktop' import { isDesktop } from 'lib/isDesktop'
import { useModelingContext } from 'hooks/useModelingContext' import { useModelingContext } from 'hooks/useModelingContext'
import { CustomIconName } from 'components/CustomIcon' import { CustomIconName } from 'components/CustomIcon'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { IconDefinition } from '@fortawesome/free-solid-svg-icons' import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
import { useKclContext } from 'lang/KclProvider' import { useKclContext } from 'lang/KclProvider'
import { MachineManagerContext } from 'components/MachineManagerProvider' import { MachineManagerContext } from 'components/MachineManagerProvider'
import { onboardingPaths } from 'routes/Onboarding/paths' import { onboardingPaths } from 'routes/Onboarding/paths'
import { SIDEBAR_BUTTON_SUFFIX } from 'lib/constants' import { SIDEBAR_BUTTON_SUFFIX } from 'lib/constants'
import { commandBarActor } from 'machines/commandBarMachine'
interface ModelingSidebarProps { interface ModelingSidebarProps {
paneOpacity: '' | 'opacity-20' | 'opacity-40' paneOpacity: '' | 'opacity-20' | 'opacity-40'
@ -37,7 +37,6 @@ function getPlatformString(): 'web' | 'desktop' {
export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) { export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
const machineManager = useContext(MachineManagerContext) const machineManager = useContext(MachineManagerContext)
const { commandBarSend } = useCommandsContext()
const kclContext = useKclContext() const kclContext = useKclContext()
const { settings } = useSettingsAuthContext() const { settings } = useSettingsAuthContext()
const onboardingStatus = settings.context.app.onboardingStatus const onboardingStatus = settings.context.app.onboardingStatus
@ -66,7 +65,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
icon: 'floppyDiskArrow', icon: 'floppyDiskArrow',
keybinding: 'Ctrl + Shift + E', keybinding: 'Ctrl + Shift + E',
action: () => action: () =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Export', groupId: 'modeling' }, data: { name: 'Export', groupId: 'modeling' },
}), }),
@ -79,7 +78,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
keybinding: 'Ctrl + Shift + M', keybinding: 'Ctrl + Shift + M',
// eslint-disable-next-line @typescript-eslint/no-misused-promises // eslint-disable-next-line @typescript-eslint/no-misused-promises
action: async () => { action: async () => {
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Make', groupId: 'modeling' }, data: { name: 'Make', groupId: 'modeling' },
}) })

View File

@ -1,7 +1,6 @@
import { fireEvent, render, screen } from '@testing-library/react' import { fireEvent, render, screen } from '@testing-library/react'
import { BrowserRouter } from 'react-router-dom' import { BrowserRouter } from 'react-router-dom'
import { SettingsAuthProviderJest } from './SettingsAuthProvider' import { SettingsAuthProviderJest } from './SettingsAuthProvider'
import { CommandBarProvider } from './CommandBar/CommandBarProvider'
import { import {
NETWORK_HEALTH_TEXT, NETWORK_HEALTH_TEXT,
NetworkHealthIndicator, NetworkHealthIndicator,
@ -12,9 +11,7 @@ function TestWrap({ children }: { children: React.ReactNode }) {
// wrap in router and xState context // wrap in router and xState context
return ( return (
<BrowserRouter> <BrowserRouter>
<CommandBarProvider> <SettingsAuthProviderJest>{children}</SettingsAuthProviderJest>
<SettingsAuthProviderJest>{children}</SettingsAuthProviderJest>
</CommandBarProvider>
</BrowserRouter> </BrowserRouter>
) )
} }

View File

@ -2,7 +2,6 @@ import { render, screen } from '@testing-library/react'
import { BrowserRouter } from 'react-router-dom' import { BrowserRouter } from 'react-router-dom'
import ProjectSidebarMenu from './ProjectSidebarMenu' import ProjectSidebarMenu from './ProjectSidebarMenu'
import { SettingsAuthProviderJest } from './SettingsAuthProvider' import { SettingsAuthProviderJest } from './SettingsAuthProvider'
import { CommandBarProvider } from './CommandBar/CommandBarProvider'
import { Project } from 'lib/project' import { Project } from 'lib/project'
const now = new Date() const now = new Date()
@ -33,11 +32,9 @@ describe('ProjectSidebarMenu tests', () => {
test('Disables popover menu by default', () => { test('Disables popover menu by default', () => {
render( render(
<BrowserRouter> <BrowserRouter>
<CommandBarProvider> <SettingsAuthProviderJest>
<SettingsAuthProviderJest> <ProjectSidebarMenu project={projectWellFormed} />
<ProjectSidebarMenu project={projectWellFormed} /> </SettingsAuthProviderJest>
</SettingsAuthProviderJest>
</CommandBarProvider>
</BrowserRouter> </BrowserRouter>
) )

View File

@ -7,7 +7,6 @@ import { Link, useLocation, useNavigate } from 'react-router-dom'
import { Fragment, useMemo, useContext } from 'react' import { Fragment, useMemo, useContext } from 'react'
import { Logo } from './Logo' import { Logo } from './Logo'
import { APP_NAME } from 'lib/constants' import { APP_NAME } from 'lib/constants'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { CustomIcon } from './CustomIcon' import { CustomIcon } from './CustomIcon'
import { useLspContext } from './LspProvider' import { useLspContext } from './LspProvider'
import { engineCommandManager, kclManager } from 'lib/singletons' import { engineCommandManager, kclManager } from 'lib/singletons'
@ -15,6 +14,9 @@ import { MachineManagerContext } from 'components/MachineManagerProvider'
import usePlatform from 'hooks/usePlatform' import usePlatform from 'hooks/usePlatform'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath' import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
import Tooltip from './Tooltip' import Tooltip from './Tooltip'
import { SnapshotFrom } from 'xstate'
import { commandBarActor } from 'machines/commandBarMachine'
import { useSelector } from '@xstate/react'
const ProjectSidebarMenu = ({ const ProjectSidebarMenu = ({
project, project,
@ -84,6 +86,9 @@ function AppLogoLink({
) )
} }
const commandsSelector = (state: SnapshotFrom<typeof commandBarActor>) =>
state.context.commands
function ProjectMenuPopover({ function ProjectMenuPopover({
project, project,
file, file,
@ -96,16 +101,14 @@ function ProjectMenuPopover({
const navigate = useNavigate() const navigate = useNavigate()
const filePath = useAbsoluteFilePath() const filePath = useAbsoluteFilePath()
const machineManager = useContext(MachineManagerContext) const machineManager = useContext(MachineManagerContext)
const commands = useSelector(commandBarActor, commandsSelector)
const { commandBarState, commandBarSend } = useCommandsContext()
const { onProjectClose } = useLspContext() const { onProjectClose } = useLspContext()
const exportCommandInfo = { name: 'Export', groupId: 'modeling' } const exportCommandInfo = { name: 'Export', groupId: 'modeling' }
const makeCommandInfo = { name: 'Make', groupId: 'modeling' } const makeCommandInfo = { name: 'Make', groupId: 'modeling' }
const findCommand = (obj: { name: string; groupId: string }) => const findCommand = (obj: { name: string; groupId: string }) =>
Boolean( Boolean(
commandBarState.context.commands.find( commands.find((c) => c.name === obj.name && c.groupId === obj.groupId)
(c) => c.name === obj.name && c.groupId === obj.groupId
)
) )
const machineCount = machineManager.machines.length const machineCount = machineManager.machines.length
@ -150,7 +153,7 @@ function ProjectMenuPopover({
), ),
disabled: !findCommand(exportCommandInfo), disabled: !findCommand(exportCommandInfo),
onClick: () => onClick: () =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: exportCommandInfo, data: exportCommandInfo,
}), }),
@ -175,7 +178,7 @@ function ProjectMenuPopover({
), ),
disabled: !findCommand(makeCommandInfo) || machineCount === 0, disabled: !findCommand(makeCommandInfo) || machineCount === 0,
onClick: () => { onClick: () => {
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: makeCommandInfo, data: makeCommandInfo,
}) })
@ -200,7 +203,7 @@ function ProjectMenuPopover({
[ [
platform, platform,
findCommand, findCommand,
commandBarSend, commandBarActor.send,
engineCommandManager, engineCommandManager,
onProjectClose, onProjectClose,
isDesktop, isDesktop,

View File

@ -1,5 +1,4 @@
import { useMachine } from '@xstate/react' import { useMachine } from '@xstate/react'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher' import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
import { useProjectsLoader } from 'hooks/useProjectsLoader' import { useProjectsLoader } from 'hooks/useProjectsLoader'
import { projectsMachine } from 'machines/projectsMachine' import { projectsMachine } from 'machines/projectsMachine'
@ -24,6 +23,7 @@ import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import useStateMachineCommands from 'hooks/useStateMachineCommands' import useStateMachineCommands from 'hooks/useStateMachineCommands'
import { projectsCommandBarConfig } from 'lib/commandBarConfigs/projectsCommandConfig' import { projectsCommandBarConfig } from 'lib/commandBarConfigs/projectsCommandConfig'
import { isDesktop } from 'lib/isDesktop' import { isDesktop } from 'lib/isDesktop'
import { commandBarActor } from 'machines/commandBarMachine'
type MachineContext<T extends AnyStateMachine> = { type MachineContext<T extends AnyStateMachine> = {
state?: StateFrom<T> state?: StateFrom<T>
@ -73,7 +73,6 @@ const ProjectsContextDesktop = ({
}) => { }) => {
const navigate = useNavigate() const navigate = useNavigate()
const location = useLocation() const location = useLocation()
const { commandBarSend } = useCommandsContext()
const { onProjectOpen } = useLspContext() const { onProjectOpen } = useLspContext()
const { const {
settings: { context: settings }, settings: { context: settings },
@ -126,7 +125,7 @@ const ProjectsContextDesktop = ({
}, },
null null
) )
commandBarSend({ type: 'Close' }) commandBarActor.send({ type: 'Close' })
const newPathName = `${PATHS.FILE}/${encodeURIComponent( const newPathName = `${PATHS.FILE}/${encodeURIComponent(
projectPath projectPath
)}` )}`

View File

@ -29,7 +29,6 @@ import {
createSettingsCommand, createSettingsCommand,
settingsWithCommandConfigs, settingsWithCommandConfigs,
} from 'lib/commandBarConfigs/settingsCommandConfig' } from 'lib/commandBarConfigs/settingsCommandConfig'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { Command } from 'lib/commandTypes' import { Command } from 'lib/commandTypes'
import { BaseUnit } from 'lib/settings/settingsTypes' import { BaseUnit } from 'lib/settings/settingsTypes'
import { import {
@ -42,6 +41,7 @@ import { isDesktop } from 'lib/isDesktop'
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher' import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
import { codeManager } from 'lib/singletons' import { codeManager } from 'lib/singletons'
import { createRouteCommands } from 'lib/commandBarConfigs/routeCommandConfig' import { createRouteCommands } from 'lib/commandBarConfigs/routeCommandConfig'
import { commandBarActor } from 'machines/commandBarMachine'
type MachineContext<T extends AnyStateMachine> = { type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T> state: StateFrom<T>
@ -109,7 +109,6 @@ export const SettingsAuthProviderBase = ({
}) => { }) => {
const location = useLocation() const location = useLocation()
const navigate = useNavigate() const navigate = useNavigate()
const { commandBarSend } = useCommandsContext()
const [settingsPath, setSettingsPath] = useState<string | undefined>( const [settingsPath, setSettingsPath] = useState<string | undefined>(
undefined undefined
) )
@ -278,10 +277,10 @@ export const SettingsAuthProviderBase = ({
) )
.filter((c) => c !== null) as Command[] .filter((c) => c !== null) as Command[]
commandBarSend({ type: 'Add commands', data: { commands: commands } }) commandBarActor.send({ type: 'Add commands', data: { commands: commands } })
return () => { return () => {
commandBarSend({ commandBarActor.send({
type: 'Remove commands', type: 'Remove commands',
data: { commands }, data: { commands },
}) })
@ -290,7 +289,7 @@ export const SettingsAuthProviderBase = ({
settingsState, settingsState,
settingsSend, settingsSend,
settingsActor, settingsActor,
commandBarSend, commandBarActor.send,
settingsWithCommandConfigs, settingsWithCommandConfigs,
]) ])
@ -303,7 +302,7 @@ export const SettingsAuthProviderBase = ({
encodeURIComponent(loadedProject?.file?.path || BROWSER_PATH) encodeURIComponent(loadedProject?.file?.path || BROWSER_PATH)
const { RouteTelemetryCommand, RouteHomeCommand, RouteSettingsCommand } = const { RouteTelemetryCommand, RouteHomeCommand, RouteSettingsCommand } =
createRouteCommands(navigate, location, filePath) createRouteCommands(navigate, location, filePath)
commandBarSend({ commandBarActor.send({
type: 'Remove commands', type: 'Remove commands',
data: { data: {
commands: [ commands: [
@ -314,12 +313,12 @@ export const SettingsAuthProviderBase = ({
}, },
}) })
if (location.pathname === PATHS.HOME) { if (location.pathname === PATHS.HOME) {
commandBarSend({ commandBarActor.send({
type: 'Add commands', type: 'Add commands',
data: { commands: [RouteTelemetryCommand, RouteSettingsCommand] }, data: { commands: [RouteTelemetryCommand, RouteSettingsCommand] },
}) })
} else if (location.pathname.includes(PATHS.FILE)) { } else if (location.pathname.includes(PATHS.FILE)) {
commandBarSend({ commandBarActor.send({
type: 'Add commands', type: 'Add commands',
data: { data: {
commands: [ commands: [

View File

@ -17,10 +17,11 @@ import {
import { useRouteLoaderData } from 'react-router-dom' import { useRouteLoaderData } from 'react-router-dom'
import { PATHS } from 'lib/paths' import { PATHS } from 'lib/paths'
import { IndexLoaderData } from 'lib/types' import { IndexLoaderData } from 'lib/types'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { err, reportRejection } from 'lib/trap' import { err, reportRejection } from 'lib/trap'
import { getArtifactOfTypes } from 'lang/std/artifactGraph' import { getArtifactOfTypes } from 'lang/std/artifactGraph'
import { ViewControlContextMenu } from './ViewControlMenu' import { ViewControlContextMenu } from './ViewControlMenu'
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
import { useSelector } from '@xstate/react'
enum StreamState { enum StreamState {
Playing = 'playing', Playing = 'playing',
@ -35,7 +36,7 @@ export const Stream = () => {
const videoRef = useRef<HTMLVideoElement>(null) const videoRef = useRef<HTMLVideoElement>(null)
const { settings } = useSettingsAuthContext() const { settings } = useSettingsAuthContext()
const { state, send } = useModelingContext() const { state, send } = useModelingContext()
const { commandBarState } = useCommandsContext() const commandBarState = useCommandBarState()
const { mediaStream } = useAppStream() const { mediaStream } = useAppStream()
const { overallState, immediateState } = useNetworkContext() const { overallState, immediateState } = useNetworkContext()
const [streamState, setStreamState] = useState(StreamState.Unset) const [streamState, setStreamState] = useState(StreamState.Unset)

View File

@ -28,7 +28,7 @@ import { base64Decode } from 'lang/wasm'
import { sendTelemetry } from 'lib/textToCad' import { sendTelemetry } from 'lib/textToCad'
import { Themes } from 'lib/theme' import { Themes } from 'lib/theme'
import { ActionButton } from './ActionButton' import { ActionButton } from './ActionButton'
import { commandBarMachine } from 'machines/commandBarMachine' import { commandBarActor, commandBarMachine } from 'machines/commandBarMachine'
import { EventFrom } from 'xstate' import { EventFrom } from 'xstate'
import { fileMachine } from 'machines/fileMachine' import { fileMachine } from 'machines/fileMachine'
import { reportRejection } from 'lib/trap' import { reportRejection } from 'lib/trap'
@ -43,15 +43,10 @@ export function ToastTextToCadError({
toastId, toastId,
message, message,
prompt, prompt,
commandBarSend,
}: { }: {
toastId: string toastId: string
message: string message: string
prompt: string prompt: string
commandBarSend: (
event: EventFrom<typeof commandBarMachine>,
data?: unknown
) => void
}) { }) {
return ( return (
<div className="flex flex-col justify-between gap-6"> <div className="flex flex-col justify-between gap-6">
@ -81,7 +76,7 @@ export function ToastTextToCadError({
}} }}
name="Edit prompt" name="Edit prompt"
onClick={() => { onClick={() => {
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { data: {
groupId: 'modeling', groupId: 'modeling',

View File

@ -8,7 +8,6 @@ import {
} from 'react-router-dom' } from 'react-router-dom'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import { SettingsAuthProviderJest } from './SettingsAuthProvider' import { SettingsAuthProviderJest } from './SettingsAuthProvider'
import { CommandBarProvider } from './CommandBar/CommandBarProvider'
type User = Models['User_type'] type User = Models['User_type']
@ -124,9 +123,7 @@ function TestWrap({ children }: { children: React.ReactNode }) {
<Route <Route
path="/file/:id" path="/file/:id"
element={ element={
<CommandBarProvider> <SettingsAuthProviderJest>{children}</SettingsAuthProviderJest>
<SettingsAuthProviderJest>{children}</SettingsAuthProviderJest>
</CommandBarProvider>
} }
/> />
), ),

View File

@ -5,7 +5,6 @@ import { engineCommandManager, kclManager } from 'lib/singletons'
import { modelingMachine, ModelingMachineEvent } from 'machines/modelingMachine' import { modelingMachine, ModelingMachineEvent } from 'machines/modelingMachine'
import { Selections, Selection, processCodeMirrorRanges } from 'lib/selections' import { Selections, Selection, processCodeMirrorRanges } from 'lib/selections'
import { undo, redo } from '@codemirror/commands' import { undo, redo } from '@codemirror/commands'
import { CommandBarMachineEvent } from 'machines/commandBarMachine'
import { addLineHighlight, addLineHighlightEvent } from './highlightextension' import { addLineHighlight, addLineHighlightEvent } from './highlightextension'
import { import {
Diagnostic, Diagnostic,
@ -52,9 +51,6 @@ export default class EditorManager {
private _modelingSend: (eventInfo: ModelingMachineEvent) => void = () => {} private _modelingSend: (eventInfo: ModelingMachineEvent) => void = () => {}
private _modelingState: StateFrom<typeof modelingMachine> | null = null private _modelingState: StateFrom<typeof modelingMachine> | null = null
private _commandBarSend: (eventInfo: CommandBarMachineEvent) => void =
() => {}
private _convertToVariableEnabled: boolean = false private _convertToVariableEnabled: boolean = false
private _convertToVariableCallback: () => void = () => {} private _convertToVariableCallback: () => void = () => {}
@ -161,14 +157,6 @@ export default class EditorManager {
this._modelingState = state this._modelingState = state
} }
setCommandBarSend(send: (eventInfo: CommandBarMachineEvent) => void) {
this._commandBarSend = send
}
commandBarSend(eventInfo: CommandBarMachineEvent): void {
return this._commandBarSend(eventInfo)
}
get highlightRange(): Array<[number, number]> { get highlightRange(): Array<[number, number]> {
return this._highlightRange return this._highlightRange
} }

View File

@ -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,
}
}

View File

@ -1,7 +1,6 @@
import { useEffect } from 'react' import { useEffect } from 'react'
import { AnyStateMachine, Actor, StateFrom, EventFrom } from 'xstate' import { AnyStateMachine, Actor, StateFrom, EventFrom } from 'xstate'
import { createMachineCommand } from '../lib/createMachineCommand' import { createMachineCommand } from '../lib/createMachineCommand'
import { useCommandsContext } from './useCommandsContext'
import { modelingMachine } from 'machines/modelingMachine' import { modelingMachine } from 'machines/modelingMachine'
import { authMachine } from 'machines/authMachine' import { authMachine } from 'machines/authMachine'
import { settingsMachine } from 'machines/settingsMachine' import { settingsMachine } from 'machines/settingsMachine'
@ -15,6 +14,7 @@ import { useKclContext } from 'lang/KclProvider'
import { useNetworkContext } from 'hooks/useNetworkContext' import { useNetworkContext } from 'hooks/useNetworkContext'
import { NetworkHealthState } from 'hooks/useNetworkStatus' import { NetworkHealthState } from 'hooks/useNetworkStatus'
import { useAppState } from 'AppState' import { useAppState } from 'AppState'
import { commandBarActor } from 'machines/commandBarMachine'
// This might not be necessary, AnyStateMachine from xstate is working // This might not be necessary, AnyStateMachine from xstate is working
export type AllMachines = export type AllMachines =
@ -48,7 +48,6 @@ export default function useStateMachineCommands<
allCommandsRequireNetwork = false, allCommandsRequireNetwork = false,
onCancel, onCancel,
}: UseStateMachineCommandsArgs<T, S>) { }: UseStateMachineCommandsArgs<T, S>) {
const { commandBarSend } = useCommandsContext()
const { overallState } = useNetworkContext() const { overallState } = useNetworkContext()
const { isExecuting } = useKclContext() const { isExecuting } = useKclContext()
const { isStreamReady } = useAppState() 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 .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 () => { return () => {
commandBarSend({ commandBarActor.send({
type: 'Remove commands', type: 'Remove commands',
data: { commands: newCommands }, data: { commands: newCommands },
}) })

View File

@ -68,10 +68,6 @@ interface TextToKclProps {
data?: unknown data?: unknown
) => unknown ) => unknown
navigate: NavigateFunction navigate: NavigateFunction
commandBarSend: (
type: EventFrom<typeof commandBarMachine>,
data?: unknown
) => unknown
context: ContextFrom<typeof fileMachine> context: ContextFrom<typeof fileMachine>
token?: string token?: string
settings: { settings: {
@ -84,7 +80,6 @@ export async function submitAndAwaitTextToKcl({
trimmedPrompt, trimmedPrompt,
fileMachineSend, fileMachineSend,
navigate, navigate,
commandBarSend,
context, context,
token, token,
settings, settings,
@ -96,7 +91,6 @@ export async function submitAndAwaitTextToKcl({
ToastTextToCadError({ ToastTextToCadError({
toastId, toastId,
message, message,
commandBarSend,
prompt: trimmedPrompt, prompt: trimmedPrompt,
}), }),
{ {

View File

@ -1,6 +1,6 @@
import { CustomIconName } from 'components/CustomIcon' import { CustomIconName } from 'components/CustomIcon'
import { DEV } from 'env' import { DEV } from 'env'
import { commandBarMachine } from 'machines/commandBarMachine' import { commandBarActor, commandBarMachine } from 'machines/commandBarMachine'
import { import {
canRectangleOrCircleTool, canRectangleOrCircleTool,
isClosedSketch, isClosedSketch,
@ -21,7 +21,6 @@ type ToolbarMode = {
export interface ToolbarItemCallbackProps { export interface ToolbarItemCallbackProps {
modelingState: StateFrom<typeof modelingMachine> modelingState: StateFrom<typeof modelingMachine>
modelingSend: (event: EventFrom<typeof modelingMachine>) => void modelingSend: (event: EventFrom<typeof modelingMachine>) => void
commandBarSend: (event: EventFrom<typeof commandBarMachine>) => void
sketchPathId: string | false sketchPathId: string | false
} }
@ -84,8 +83,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
'break', 'break',
{ {
id: 'extrude', id: 'extrude',
onClick: ({ commandBarSend }) => onClick: () =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Extrude', groupId: 'modeling' }, data: { name: 'Extrude', groupId: 'modeling' },
}), }),
@ -98,8 +97,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
}, },
{ {
id: 'revolve', id: 'revolve',
onClick: ({ commandBarSend }) => onClick: () =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Revolve', groupId: 'modeling' }, data: { name: 'Revolve', groupId: 'modeling' },
}), }),
@ -119,8 +118,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
}, },
{ {
id: 'sweep', id: 'sweep',
onClick: ({ commandBarSend }) => onClick: () =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Sweep', groupId: 'modeling' }, data: { name: 'Sweep', groupId: 'modeling' },
}), }),
@ -139,8 +138,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
}, },
{ {
id: 'loft', id: 'loft',
onClick: ({ commandBarSend }) => onClick: () =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Loft', groupId: 'modeling' }, data: { name: 'Loft', groupId: 'modeling' },
}), }),
@ -160,8 +159,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
'break', 'break',
{ {
id: 'fillet3d', id: 'fillet3d',
onClick: ({ commandBarSend }) => onClick: () =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Fillet', groupId: 'modeling' }, data: { name: 'Fillet', groupId: 'modeling' },
}), }),
@ -174,8 +173,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
}, },
{ {
id: 'chamfer3d', id: 'chamfer3d',
onClick: ({ commandBarSend }) => onClick: () =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Chamfer', groupId: 'modeling' }, data: { name: 'Chamfer', groupId: 'modeling' },
}), }),
@ -188,8 +187,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
}, },
{ {
id: 'shell', id: 'shell',
onClick: ({ commandBarSend }) => { onClick: () => {
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Shell', groupId: 'modeling' }, data: { name: 'Shell', groupId: 'modeling' },
}) })
@ -269,8 +268,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
[ [
{ {
id: 'plane-offset', id: 'plane-offset',
onClick: ({ commandBarSend }) => { onClick: () => {
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Offset plane', groupId: 'modeling' }, data: { name: 'Offset plane', groupId: 'modeling' },
}) })
@ -301,8 +300,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
[ [
{ {
id: 'text-to-cad', id: 'text-to-cad',
onClick: ({ commandBarSend }) => onClick: () =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Text-to-CAD', groupId: 'modeling' }, data: { name: 'Text-to-CAD', groupId: 'modeling' },
}), }),
@ -319,8 +318,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
}, },
{ {
id: 'prompt-to-edit', id: 'prompt-to-edit',
onClick: ({ commandBarSend }) => onClick: () =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Prompt-to-edit', groupId: 'modeling' }, data: { name: 'Prompt-to-edit', groupId: 'modeling' },
}), }),
@ -593,8 +592,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
{ {
id: 'constraint-length', id: 'constraint-length',
disabled: (state) => !state.matches({ Sketch: 'SketchIdle' }), disabled: (state) => !state.matches({ Sketch: 'SketchIdle' }),
onClick: ({ commandBarSend }) => onClick: () =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { data: {
name: 'Constrain length', name: 'Constrain length',

View File

@ -1,4 +1,4 @@
import { assign, fromPromise, setup } from 'xstate' import { assign, createActor, fromPromise, setup, SnapshotFrom } from 'xstate'
import { import {
Command, Command,
CommandArgument, CommandArgument,
@ -9,6 +9,7 @@ import { Selections__old } from 'lib/selections'
import { getCommandArgumentKclValuesOnly } from 'lib/commandUtils' import { getCommandArgumentKclValuesOnly } from 'lib/commandUtils'
import { MachineManager } from 'components/MachineManagerProvider' import { MachineManager } from 'components/MachineManagerProvider'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { useSelector } from '@xstate/react'
export type CommandBarContext = { export type CommandBarContext = {
commands: Command[] commands: Command[]
@ -247,8 +248,17 @@ export const commandBarMachine = setup({
guards: { guards: {
'Command needs review': ({ context }) => 'Command needs review': ({ context }) =>
context.selectedCommand?.needsReview || false, context.selectedCommand?.needsReview || false,
'Command has no arguments': () => false, 'Command has no arguments': ({ context }) => {
'All arguments are skippable': () => false, 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, 'Has selected command': ({ context }) => !!context.selectedCommand,
}, },
actors: { actors: {
@ -620,3 +630,12 @@ function sortCommands(a: Command, b: Command) {
if (a.groupId === 'settings' && !(b.groupId === 'settings')) return 1 if (a.groupId === 'settings' && !(b.groupId === 'settings')) return 1
return a.name.localeCompare(b.name) 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)
}

View File

@ -24,13 +24,12 @@ import { markOnce } from 'lib/performance'
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher' import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
import { useProjectsLoader } from 'hooks/useProjectsLoader' import { useProjectsLoader } from 'hooks/useProjectsLoader'
import { useProjectsContext } from 'hooks/useProjectsContext' 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, // This route only opens in the desktop context for now,
// as defined in Router.tsx, so we can use the desktop APIs and types. // as defined in Router.tsx, so we can use the desktop APIs and types.
const Home = () => { const Home = () => {
const { state, send } = useProjectsContext() const { state, send } = useProjectsContext()
const { commandBarSend } = useCommandsContext()
const [projectsLoaderTrigger, setProjectsLoaderTrigger] = useState(0) const [projectsLoaderTrigger, setProjectsLoaderTrigger] = useState(0)
const { projectsDir } = useProjectsLoader([projectsLoaderTrigger]) const { projectsDir } = useProjectsLoader([projectsLoaderTrigger])
@ -128,7 +127,7 @@ const Home = () => {
<ActionButton <ActionButton
Element="button" Element="button"
onClick={() => onClick={() =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { data: {
groupId: 'projects', groupId: 'projects',