Compare commits
6 Commits
v0.24.1
...
achalmers/
Author | SHA1 | Date | |
---|---|---|---|
0604da9c37 | |||
29bf77bb82 | |||
e81b614523 | |||
5a5fe3bb95 | |||
0710f6e5f2 | |||
c9d5633647 |
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 249 KiB |
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 249 KiB |
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 249 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 249 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
@ -3691,6 +3691,46 @@ const extrude001 = extrude(distance001, sketch001)`.replace(
|
||||
) // remove newlines
|
||||
)
|
||||
})
|
||||
|
||||
test('Can switch between sketch tools via command bar', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const sketchButton = page.getByRole('button', { name: 'Start Sketch' })
|
||||
const cmdBarButton = page.getByRole('button', { name: 'Commands' })
|
||||
const rectangleToolCommand = page.getByRole('option', {
|
||||
name: 'Rectangle',
|
||||
})
|
||||
const rectangleToolButton = page.getByRole('button', { name: 'Rectangle' })
|
||||
const lineToolCommand = page.getByRole('option', { name: 'Line' })
|
||||
const lineToolButton = page.getByRole('button', { name: 'Line' })
|
||||
const arcToolCommand = page.getByRole('option', { name: 'Tangential Arc' })
|
||||
const arcToolButton = page.getByRole('button', { name: 'Tangential Arc' })
|
||||
|
||||
// Start a sketch
|
||||
await sketchButton.click()
|
||||
await page.mouse.click(700, 200)
|
||||
|
||||
// Switch between sketch tools via the command bar
|
||||
await expect(lineToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||
await cmdBarButton.click()
|
||||
await rectangleToolCommand.click()
|
||||
await expect(rectangleToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||
await cmdBarButton.click()
|
||||
await lineToolCommand.click()
|
||||
await expect(lineToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||
|
||||
// Click in the scene a couple times to draw a line
|
||||
// so tangential arc is valid
|
||||
await page.mouse.click(700, 200)
|
||||
await page.mouse.click(700, 300)
|
||||
|
||||
// switch to tangential arc via command bar
|
||||
await cmdBarButton.click()
|
||||
await arcToolCommand.click()
|
||||
await expect(arcToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Regression tests', () => {
|
||||
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 44 KiB |
@ -60,7 +60,7 @@ export function Toolbar({
|
||||
? send('CancelSketch')
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'line',
|
||||
data: { tool: 'line' },
|
||||
}),
|
||||
{ enabled: !disableLineButton, scopes: ['sketch'] }
|
||||
)
|
||||
@ -75,7 +75,7 @@ export function Toolbar({
|
||||
? send('CancelSketch')
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'tangentialArc',
|
||||
data: { tool: 'tangentialArc' },
|
||||
}),
|
||||
{ enabled: !disableTangentialArc, scopes: ['sketch'] }
|
||||
)
|
||||
@ -89,7 +89,7 @@ export function Toolbar({
|
||||
? send('CancelSketch')
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'rectangle',
|
||||
data: { tool: 'rectangle' },
|
||||
}),
|
||||
{ enabled: !disableRectangle, scopes: ['sketch'] }
|
||||
)
|
||||
@ -263,7 +263,7 @@ export function Toolbar({
|
||||
? send('CancelSketch')
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'line',
|
||||
data: { tool: 'line' },
|
||||
})
|
||||
}
|
||||
aria-pressed={state?.matches('Sketch.Line tool')}
|
||||
@ -293,7 +293,7 @@ export function Toolbar({
|
||||
? send('CancelSketch')
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'tangentialArc',
|
||||
data: { tool: 'tangentialArc' },
|
||||
})
|
||||
}
|
||||
aria-pressed={state.matches('Sketch.Tangential arc to')}
|
||||
@ -323,7 +323,7 @@ export function Toolbar({
|
||||
? send('CancelSketch')
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'rectangle',
|
||||
data: { tool: 'rectangle' },
|
||||
})
|
||||
}
|
||||
aria-pressed={state.matches('Sketch.Rectangle tool')}
|
||||
|
@ -28,6 +28,11 @@ export const CommandBarProvider = ({
|
||||
Object.keys(context.selectedCommand?.args).length === 0
|
||||
)
|
||||
},
|
||||
'All arguments are skippable': (context, _event) => {
|
||||
return Object.values(context.selectedCommand!.args!).every(
|
||||
(argConfig) => argConfig.skip
|
||||
)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -70,19 +70,23 @@ function CommandComboBox({
|
||||
>
|
||||
{filteredOptions?.map((option) => (
|
||||
<Combobox.Option
|
||||
key={option.name}
|
||||
key={option.groupId + option.name + (option.displayName || '')}
|
||||
value={option}
|
||||
className="flex items-center gap-2 px-4 py-1 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90"
|
||||
className="flex items-center gap-4 px-4 py-1.5 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90"
|
||||
>
|
||||
{'icon' in option && option.icon && (
|
||||
<CustomIcon name={option.icon} className="w-5 h-5" />
|
||||
)}
|
||||
<p className="flex-grow">{option.displayName || option.name} </p>
|
||||
{option.description && (
|
||||
<p className="text-xs text-chalkboard-60 dark:text-chalkboard-40">
|
||||
{option.description}
|
||||
<div className="flex-grow flex flex-col">
|
||||
<p className="my-0 leading-tight">
|
||||
{option.displayName || option.name}{' '}
|
||||
</p>
|
||||
)}
|
||||
{option.description && (
|
||||
<p className="my-0 text-xs text-chalkboard-60 dark:text-chalkboard-50">
|
||||
{option.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
|
@ -131,6 +131,16 @@ const CustomIconMap = {
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
code: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10.7071 5L7.77734 14.7794L8.73527 15.0663L11.665 5.28698L10.7071 5ZM2.35356 9.64644L5.85362 6.14644L6.56072 6.85355L3.41423 10L6.56072 13.1464L5.85362 13.8536L2.35356 10.3536L2 10L2.35356 9.64644ZM17.0607 9.64644L13.5607 6.14644L12.8536 6.85355L16 10L12.8536 13.1464L13.5607 13.8535L17.0607 10.3536L17.4142 10L17.0607 9.64644Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
dimension: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
|
@ -42,7 +42,7 @@ import {
|
||||
import { applyConstraintIntersect } from './Toolbar/Intersect'
|
||||
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
|
||||
import useStateMachineCommands from 'hooks/useStateMachineCommands'
|
||||
import { modelingMachineConfig } from 'lib/commandBarConfigs/modelingCommandConfig'
|
||||
import { modelingMachineCommandConfig } from 'lib/commandBarConfigs/modelingCommandConfig'
|
||||
import {
|
||||
STRAIGHT_SEGMENT,
|
||||
TANGENTIAL_ARC_TO_SEGMENT,
|
||||
@ -920,7 +920,7 @@ export const ModelingMachineProvider = ({
|
||||
state: modelingState,
|
||||
send: modelingSend,
|
||||
actor: modelingActor,
|
||||
commandBarConfig: modelingMachineConfig,
|
||||
commandBarConfig: modelingMachineCommandConfig,
|
||||
allCommandsRequireNetwork: true,
|
||||
// TODO for when sketch tools are in the toolbar: This was added when we used one "Cancel" event,
|
||||
// but we need to support "SketchCancel" and basically
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { DEV } from 'env'
|
||||
import { MouseEventHandler, useEffect, useRef, useState } from 'react'
|
||||
import { getNormalisedCoordinates } from '../lib/utils'
|
||||
import Loading from './Loading'
|
||||
@ -22,6 +23,8 @@ export const Stream = () => {
|
||||
const { overallState } = useNetworkContext()
|
||||
const [isFreezeFrame, setIsFreezeFrame] = useState(false)
|
||||
|
||||
const IDLE = true
|
||||
|
||||
const isNetworkOkay =
|
||||
overallState === NetworkHealthState.Ok ||
|
||||
overallState === NetworkHealthState.Weak
|
||||
@ -53,7 +56,7 @@ export const Stream = () => {
|
||||
capture: true,
|
||||
})
|
||||
|
||||
const IDLE_TIME_MS = 1000 * 20
|
||||
const IDLE_TIME_MS = 1000 * 60 * 2
|
||||
let timeoutIdIdleA: ReturnType<typeof setTimeout> | undefined = undefined
|
||||
|
||||
const teardown = () => {
|
||||
@ -62,19 +65,21 @@ export const Stream = () => {
|
||||
sceneInfra.modelingSend({ type: 'Cancel' })
|
||||
// Give video time to pause
|
||||
window.requestAnimationFrame(() => {
|
||||
engineCommandManager.engineConnection?.tearDown({ freeze: true })
|
||||
engineCommandManager.tearDown()
|
||||
})
|
||||
}
|
||||
|
||||
// Teardown everything if we go hidden or reconnect
|
||||
if (globalThis?.window?.document) {
|
||||
globalThis.window.document.onvisibilitychange = () => {
|
||||
if (globalThis.window.document.visibilityState === 'hidden') {
|
||||
clearTimeout(timeoutIdIdleA)
|
||||
timeoutIdIdleA = setTimeout(teardown, IDLE_TIME_MS)
|
||||
} else if (!engineCommandManager.engineConnection?.isReady()) {
|
||||
clearTimeout(timeoutIdIdleA)
|
||||
engineCommandManager.engineConnection?.connect(true)
|
||||
if (IDLE && DEV) {
|
||||
if (globalThis?.window?.document) {
|
||||
globalThis.window.document.onvisibilitychange = () => {
|
||||
if (globalThis.window.document.visibilityState === 'hidden') {
|
||||
clearTimeout(timeoutIdIdleA)
|
||||
timeoutIdIdleA = setTimeout(teardown, IDLE_TIME_MS)
|
||||
} else if (!engineCommandManager.engineConnection?.isReady()) {
|
||||
clearTimeout(timeoutIdIdleA)
|
||||
engineCommandManager.engineConnection?.connect(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -82,35 +87,44 @@ export const Stream = () => {
|
||||
let timeoutIdIdleB: ReturnType<typeof setTimeout> | undefined = undefined
|
||||
|
||||
const onAnyInput = () => {
|
||||
if (!engineCommandManager.engineConnection?.isReady()) {
|
||||
engineCommandManager.engineConnection?.connect(true)
|
||||
}
|
||||
// Clear both timers
|
||||
clearTimeout(timeoutIdIdleA)
|
||||
clearTimeout(timeoutIdIdleB)
|
||||
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
|
||||
}
|
||||
|
||||
globalThis?.window?.document?.addEventListener('keydown', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('mousemove', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('mousedown', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('scroll', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('touchstart', onAnyInput)
|
||||
if (IDLE && DEV) {
|
||||
globalThis?.window?.document?.addEventListener('keydown', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('mousemove', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('mousedown', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('scroll', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('touchstart', onAnyInput)
|
||||
}
|
||||
|
||||
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
|
||||
if (IDLE && DEV) {
|
||||
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
|
||||
}
|
||||
|
||||
return () => {
|
||||
globalThis?.window?.document?.removeEventListener('paste', handlePaste, {
|
||||
capture: true,
|
||||
})
|
||||
globalThis?.window?.document?.removeEventListener('keydown', onAnyInput)
|
||||
globalThis?.window?.document?.removeEventListener('mousemove', onAnyInput)
|
||||
globalThis?.window?.document?.removeEventListener('mousedown', onAnyInput)
|
||||
globalThis?.window?.document?.removeEventListener('scroll', onAnyInput)
|
||||
globalThis?.window?.document?.removeEventListener(
|
||||
'touchstart',
|
||||
onAnyInput
|
||||
)
|
||||
if (IDLE && DEV) {
|
||||
globalThis?.window?.document?.removeEventListener('keydown', onAnyInput)
|
||||
globalThis?.window?.document?.removeEventListener(
|
||||
'mousemove',
|
||||
onAnyInput
|
||||
)
|
||||
globalThis?.window?.document?.removeEventListener(
|
||||
'mousedown',
|
||||
onAnyInput
|
||||
)
|
||||
globalThis?.window?.document?.removeEventListener('scroll', onAnyInput)
|
||||
globalThis?.window?.document?.removeEventListener(
|
||||
'touchstart',
|
||||
onAnyInput
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useLayoutEffect, useEffect, useRef } from 'react'
|
||||
import { useLayoutEffect, useEffect, useRef, useState } from 'react'
|
||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||
import { deferExecution } from 'lib/utils'
|
||||
import { Themes } from 'lib/theme'
|
||||
@ -30,9 +30,6 @@ export function useSetupEngineManager(
|
||||
const { setAppState } = useAppState()
|
||||
const { setMediaStream } = useAppStream()
|
||||
|
||||
const streamWidth = streamRef?.current?.offsetWidth
|
||||
const streamHeight = streamRef?.current?.offsetHeight
|
||||
|
||||
const hasSetNonZeroDimensions = useRef<boolean>(false)
|
||||
|
||||
if (settings.pool) {
|
||||
@ -41,55 +38,60 @@ export function useSetupEngineManager(
|
||||
engineCommandManager.pool = settings.pool
|
||||
}
|
||||
|
||||
const startEngineInstance = () => {
|
||||
const startEngineInstance = (restart: boolean = false) => {
|
||||
// Load the engine command manager once with the initial width and height,
|
||||
// then we do not want to reload it.
|
||||
const { width: quadWidth, height: quadHeight } = getDimensions(
|
||||
streamWidth,
|
||||
streamHeight
|
||||
streamRef?.current?.offsetWidth ?? 0,
|
||||
streamRef?.current?.offsetHeight ?? 0
|
||||
)
|
||||
if (
|
||||
!hasSetNonZeroDimensions.current &&
|
||||
quadHeight &&
|
||||
quadWidth &&
|
||||
settings.modelingSend
|
||||
) {
|
||||
engineCommandManager.start({
|
||||
setMediaStream: setMediaStream,
|
||||
setIsStreamReady: (isStreamReady) => setAppState({ isStreamReady }),
|
||||
width: quadWidth,
|
||||
height: quadHeight,
|
||||
executeCode: () => {
|
||||
// We only want to execute the code here that we already have set.
|
||||
// Nothing else.
|
||||
kclManager.isFirstRender = true
|
||||
return kclManager.executeCode(true, true).then(() => {
|
||||
kclManager.isFirstRender = false
|
||||
})
|
||||
},
|
||||
token,
|
||||
settings,
|
||||
makeDefaultPlanes: () => {
|
||||
return makeDefaultPlanes(kclManager.engineCommandManager)
|
||||
},
|
||||
modifyGrid: (hidden: boolean) => {
|
||||
return modifyGrid(kclManager.engineCommandManager, hidden)
|
||||
},
|
||||
})
|
||||
settings.modelingSend({
|
||||
type: 'Set context',
|
||||
data: {
|
||||
streamDimensions: {
|
||||
streamWidth: quadWidth,
|
||||
streamHeight: quadHeight,
|
||||
},
|
||||
},
|
||||
})
|
||||
hasSetNonZeroDimensions.current = true
|
||||
if (restart) {
|
||||
kclManager.isFirstRender = false
|
||||
}
|
||||
engineCommandManager.start({
|
||||
restart,
|
||||
setMediaStream: (mediaStream) => setMediaStream(mediaStream),
|
||||
setIsStreamReady: (isStreamReady) => setAppState({ isStreamReady }),
|
||||
width: quadWidth,
|
||||
height: quadHeight,
|
||||
executeCode: () => {
|
||||
// We only want to execute the code here that we already have set.
|
||||
// Nothing else.
|
||||
kclManager.isFirstRender = true
|
||||
return kclManager.executeCode(true, true).then(() => {
|
||||
kclManager.isFirstRender = false
|
||||
})
|
||||
},
|
||||
token,
|
||||
settings,
|
||||
makeDefaultPlanes: () => {
|
||||
return makeDefaultPlanes(kclManager.engineCommandManager)
|
||||
},
|
||||
modifyGrid: (hidden: boolean) => {
|
||||
return modifyGrid(kclManager.engineCommandManager, hidden)
|
||||
},
|
||||
})
|
||||
settings.modelingSend({
|
||||
type: 'Set context',
|
||||
data: {
|
||||
streamDimensions: {
|
||||
streamWidth: quadWidth,
|
||||
streamHeight: quadHeight,
|
||||
},
|
||||
},
|
||||
})
|
||||
hasSetNonZeroDimensions.current = true
|
||||
}
|
||||
|
||||
useLayoutEffect(startEngineInstance, [
|
||||
useLayoutEffect(() => {
|
||||
const { width: quadWidth, height: quadHeight } = getDimensions(
|
||||
streamRef?.current?.offsetWidth ?? 0,
|
||||
streamRef?.current?.offsetHeight ?? 0
|
||||
)
|
||||
if (!hasSetNonZeroDimensions.current && quadHeight && quadWidth) {
|
||||
startEngineInstance()
|
||||
}
|
||||
}, [
|
||||
streamRef?.current?.offsetWidth,
|
||||
streamRef?.current?.offsetHeight,
|
||||
settings.modelingSend,
|
||||
@ -98,8 +100,8 @@ export function useSetupEngineManager(
|
||||
useEffect(() => {
|
||||
const handleResize = deferExecution(() => {
|
||||
const { width, height } = getDimensions(
|
||||
streamRef?.current?.offsetWidth,
|
||||
streamRef?.current?.offsetHeight
|
||||
streamRef?.current?.offsetWidth ?? 0,
|
||||
streamRef?.current?.offsetHeight ?? 0
|
||||
)
|
||||
if (
|
||||
settings.modelingContext.store.streamDimensions.streamWidth !== width ||
|
||||
@ -122,10 +124,37 @@ export function useSetupEngineManager(
|
||||
}, 500)
|
||||
|
||||
const onOnline = () => {
|
||||
startEngineInstance()
|
||||
startEngineInstance(true)
|
||||
}
|
||||
|
||||
const onVisibilityChange = () => {
|
||||
if (window.document.visibilityState === 'visible') {
|
||||
if (
|
||||
!engineCommandManager.engineConnection?.isReady() &&
|
||||
!engineCommandManager.engineConnection?.isConnecting()
|
||||
) {
|
||||
startEngineInstance()
|
||||
}
|
||||
}
|
||||
}
|
||||
window.document.addEventListener('visibilitychange', onVisibilityChange)
|
||||
|
||||
const onAnyInput = () => {
|
||||
if (
|
||||
!engineCommandManager.engineConnection?.isReady() &&
|
||||
!engineCommandManager.engineConnection?.isConnecting()
|
||||
) {
|
||||
startEngineInstance()
|
||||
}
|
||||
}
|
||||
window.document.addEventListener('keydown', onAnyInput)
|
||||
window.document.addEventListener('mousemove', onAnyInput)
|
||||
window.document.addEventListener('mousedown', onAnyInput)
|
||||
window.document.addEventListener('scroll', onAnyInput)
|
||||
window.document.addEventListener('touchstart', onAnyInput)
|
||||
|
||||
const onOffline = () => {
|
||||
kclManager.isFirstRender = true
|
||||
engineCommandManager.tearDown()
|
||||
}
|
||||
|
||||
@ -133,11 +162,30 @@ export function useSetupEngineManager(
|
||||
window.addEventListener('offline', onOffline)
|
||||
window.addEventListener('resize', handleResize)
|
||||
return () => {
|
||||
window.document.removeEventListener(
|
||||
'visibilitychange',
|
||||
onVisibilityChange
|
||||
)
|
||||
window.document.removeEventListener('keydown', onAnyInput)
|
||||
window.document.removeEventListener('mousemove', onAnyInput)
|
||||
window.document.removeEventListener('mousedown', onAnyInput)
|
||||
window.document.removeEventListener('scroll', onAnyInput)
|
||||
window.document.removeEventListener('touchstart', onAnyInput)
|
||||
window.removeEventListener('online', onOnline)
|
||||
window.removeEventListener('offline', onOffline)
|
||||
window.removeEventListener('resize', handleResize)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Engine relies on many settings so we should rebind events when it changes
|
||||
// We have to list out the ones we care about because the settings object holds
|
||||
// non-settings too...
|
||||
}, [
|
||||
settings.enableSSAO,
|
||||
settings.highlightEdges,
|
||||
settings.showScaleGrid,
|
||||
settings.theme,
|
||||
settings.pool,
|
||||
])
|
||||
}
|
||||
|
||||
function getDimensions(streamWidth?: number, streamHeight?: number) {
|
||||
|
@ -6,7 +6,11 @@ import { modelingMachine } from 'machines/modelingMachine'
|
||||
import { authMachine } from 'machines/authMachine'
|
||||
import { settingsMachine } from 'machines/settingsMachine'
|
||||
import { homeMachine } from 'machines/homeMachine'
|
||||
import { Command, CommandSetConfig, CommandSetSchema } from 'lib/commandTypes'
|
||||
import {
|
||||
Command,
|
||||
StateMachineCommandSetConfig,
|
||||
StateMachineCommandSetSchema,
|
||||
} from 'lib/commandTypes'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { useNetworkContext } from 'hooks/useNetworkContext'
|
||||
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
||||
@ -21,20 +25,20 @@ export type AllMachines =
|
||||
|
||||
interface UseStateMachineCommandsArgs<
|
||||
T extends AllMachines,
|
||||
S extends CommandSetSchema<T>
|
||||
S extends StateMachineCommandSetSchema<T>
|
||||
> {
|
||||
machineId: T['id']
|
||||
state: StateFrom<T>
|
||||
send: Function
|
||||
actor: InterpreterFrom<T>
|
||||
commandBarConfig?: CommandSetConfig<T, S>
|
||||
commandBarConfig?: StateMachineCommandSetConfig<T, S>
|
||||
allCommandsRequireNetwork?: boolean
|
||||
onCancel?: () => void
|
||||
}
|
||||
|
||||
export default function useStateMachineCommands<
|
||||
T extends AnyStateMachine,
|
||||
S extends CommandSetSchema<T>
|
||||
S extends StateMachineCommandSetSchema<T>
|
||||
>({
|
||||
machineId,
|
||||
state,
|
||||
@ -58,7 +62,7 @@ export default function useStateMachineCommands<
|
||||
const newCommands = state.nextEvents
|
||||
.filter((_) => !allCommandsRequireNetwork || !disableAllButtons)
|
||||
.filter((e) => !['done.', 'error.'].some((n) => e.includes(n)))
|
||||
.map((type) =>
|
||||
.flatMap((type) =>
|
||||
createMachineCommand<T, S>({
|
||||
// The group is the owner machine's ID.
|
||||
groupId: machineId,
|
||||
|
@ -3,6 +3,8 @@ import { createContext, useContext, useEffect, useState } from 'react'
|
||||
import { type IndexLoaderData } from 'lib/types'
|
||||
import { useLoaderData } from 'react-router-dom'
|
||||
import { codeManager, kclManager } from 'lib/singletons'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { Command } from 'lib/commandTypes'
|
||||
|
||||
const KclContext = createContext({
|
||||
code: codeManager?.code || '',
|
||||
@ -35,6 +37,7 @@ export function KclContextProvider({
|
||||
const [errors, setErrors] = useState<KCLError[]>([])
|
||||
const [logs, setLogs] = useState<string[]>([])
|
||||
const [wasmInitFailed, setWasmInitFailed] = useState(false)
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
|
||||
useEffect(() => {
|
||||
codeManager.registerCallBacks({
|
||||
@ -50,6 +53,28 @@ export function KclContextProvider({
|
||||
})
|
||||
}, [])
|
||||
|
||||
// Add format code to command palette.
|
||||
useEffect(() => {
|
||||
const commands: Command[] = [
|
||||
{
|
||||
name: 'format-code',
|
||||
displayName: 'Format Code',
|
||||
description: 'Nicely formats the KCL code in the editor.',
|
||||
needsReview: false,
|
||||
groupId: 'code',
|
||||
icon: 'code',
|
||||
onSubmit: (data) => {
|
||||
kclManager.format()
|
||||
},
|
||||
},
|
||||
]
|
||||
commandBarSend({ type: 'Add commands', data: { commands } })
|
||||
|
||||
return () => {
|
||||
commandBarSend({ type: 'Remove commands', data: { commands } })
|
||||
}
|
||||
}, [kclManager, commandBarSend])
|
||||
|
||||
return (
|
||||
<KclContext.Provider
|
||||
value={{
|
||||
|
@ -302,6 +302,30 @@ class EngineConnection extends EventTarget {
|
||||
mediaStream?: MediaStream
|
||||
freezeFrame: boolean = false
|
||||
|
||||
onIceCandidate = function (
|
||||
this: RTCPeerConnection,
|
||||
event: RTCPeerConnectionIceEvent
|
||||
) {}
|
||||
onIceCandidateError = function (
|
||||
this: RTCPeerConnection,
|
||||
event: RTCPeerConnectionIceErrorEvent
|
||||
) {}
|
||||
onConnectionStateChange = function (this: RTCPeerConnection, event: Event) {}
|
||||
onDataChannelOpen = function (this: RTCDataChannel, event: Event) {}
|
||||
onDataChannelClose = function (this: RTCDataChannel, event: Event) {}
|
||||
onDataChannelError = function (this: RTCDataChannel, event: Event) {}
|
||||
onDataChannelMessage = function (this: RTCDataChannel, event: MessageEvent) {}
|
||||
onDataChannel = function (
|
||||
this: RTCPeerConnection,
|
||||
event: RTCDataChannelEvent
|
||||
) {}
|
||||
onTrack = function (this: RTCPeerConnection, event: RTCTrackEvent) {}
|
||||
onWebSocketOpen = function (event: Event) {}
|
||||
onWebSocketClose = function (event: Event) {}
|
||||
onWebSocketError = function (event: Event) {}
|
||||
onWebSocketMessage = function (event: MessageEvent) {}
|
||||
onNetworkStatusReady = () => {}
|
||||
|
||||
private _state: EngineConnectionState = {
|
||||
type: EngineConnectionStateType.Fresh,
|
||||
}
|
||||
@ -346,6 +370,7 @@ class EngineConnection extends EventTarget {
|
||||
private engineCommandManager: EngineCommandManager
|
||||
|
||||
private pingPongSpan: { ping?: Date; pong?: Date }
|
||||
private pingIntervalId: ReturnType<typeof setInterval>
|
||||
|
||||
constructor({
|
||||
engineCommandManager,
|
||||
@ -368,7 +393,7 @@ class EngineConnection extends EventTarget {
|
||||
// Without an interval ping, our connection will timeout.
|
||||
// If this.freezeFrame is true we skip this logic so only reconnect
|
||||
// happens on mouse move
|
||||
setInterval(() => {
|
||||
this.pingIntervalId = setInterval(() => {
|
||||
if (this.freezeFrame) return
|
||||
|
||||
switch (this.state.type as EngineConnectionStateType) {
|
||||
@ -434,6 +459,44 @@ class EngineConnection extends EventTarget {
|
||||
tearDown(opts?: { freeze: boolean }) {
|
||||
this.freezeFrame = opts?.freeze ?? false
|
||||
this.disconnectAll()
|
||||
clearInterval(this.pingIntervalId)
|
||||
|
||||
this.pc?.removeEventListener('icecandidate', this.onIceCandidate)
|
||||
this.pc?.removeEventListener('icecandidateerror', this.onIceCandidateError)
|
||||
this.pc?.removeEventListener(
|
||||
'connectionstatechange',
|
||||
this.onConnectionStateChange
|
||||
)
|
||||
this.pc?.removeEventListener('track', this.onTrack)
|
||||
|
||||
this.unreliableDataChannel?.removeEventListener(
|
||||
'open',
|
||||
this.onDataChannelOpen
|
||||
)
|
||||
this.unreliableDataChannel?.removeEventListener(
|
||||
'close',
|
||||
this.onDataChannelClose
|
||||
)
|
||||
this.unreliableDataChannel?.removeEventListener(
|
||||
'error',
|
||||
this.onDataChannelError
|
||||
)
|
||||
this.unreliableDataChannel?.removeEventListener(
|
||||
'message',
|
||||
this.onDataChannelMessage
|
||||
)
|
||||
this.pc?.removeEventListener('datachannel', this.onDataChannel)
|
||||
|
||||
this.websocket?.removeEventListener('open', this.onWebSocketOpen)
|
||||
this.websocket?.removeEventListener('close', this.onWebSocketClose)
|
||||
this.websocket?.removeEventListener('error', this.onWebSocketError)
|
||||
this.websocket?.removeEventListener('message', this.onWebSocketMessage)
|
||||
|
||||
window.removeEventListener(
|
||||
'use-network-status-ready',
|
||||
this.onNetworkStatusReady
|
||||
)
|
||||
|
||||
this.state = {
|
||||
type: EngineConnectionStateType.Disconnecting,
|
||||
value: { type: DisconnectingType.Quit },
|
||||
@ -477,7 +540,7 @@ class EngineConnection extends EventTarget {
|
||||
},
|
||||
}
|
||||
|
||||
this.pc.addEventListener('icecandidate', (event) => {
|
||||
this.onIceCandidate = (event: RTCPeerConnectionIceEvent) => {
|
||||
if (event.candidate === null) {
|
||||
return
|
||||
}
|
||||
@ -499,18 +562,20 @@ class EngineConnection extends EventTarget {
|
||||
usernameFragment: event.candidate.usernameFragment || undefined,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
this.pc.addEventListener('icecandidate', this.onIceCandidate)
|
||||
|
||||
this.pc.addEventListener('icecandidateerror', (_event: Event) => {
|
||||
this.onIceCandidateError = (_event: Event) => {
|
||||
const event = _event as RTCPeerConnectionIceErrorEvent
|
||||
console.warn(
|
||||
`ICE candidate returned an error: ${event.errorCode}: ${event.errorText} for ${event.url}`
|
||||
)
|
||||
})
|
||||
}
|
||||
this.pc.addEventListener('icecandidateerror', this.onIceCandidateError)
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/connectionstatechange_event
|
||||
// Event type: generic Event type...
|
||||
this.pc.addEventListener('connectionstatechange', (event: any) => {
|
||||
this.onConnectionStateChange = (event: any) => {
|
||||
console.log('connectionstatechange: ' + event.target?.connectionState)
|
||||
switch (event.target?.connectionState) {
|
||||
// From what I understand, only after have we done the ICE song and
|
||||
@ -539,9 +604,13 @@ class EngineConnection extends EventTarget {
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
this.pc.addEventListener(
|
||||
'connectionstatechange',
|
||||
this.onConnectionStateChange
|
||||
)
|
||||
|
||||
this.pc.addEventListener('track', (event) => {
|
||||
this.onTrack = (event) => {
|
||||
const mediaStream = event.streams[0]
|
||||
|
||||
this.state = {
|
||||
@ -625,9 +694,10 @@ class EngineConnection extends EventTarget {
|
||||
// to pass it to the rest of the application.
|
||||
|
||||
this.mediaStream = mediaStream
|
||||
})
|
||||
}
|
||||
this.pc.addEventListener('track', this.onTrack)
|
||||
|
||||
this.pc.addEventListener('datachannel', (event) => {
|
||||
this.onDataChannel = (event) => {
|
||||
this.unreliableDataChannel = event.channel
|
||||
|
||||
this.state = {
|
||||
@ -638,7 +708,7 @@ class EngineConnection extends EventTarget {
|
||||
},
|
||||
}
|
||||
|
||||
this.unreliableDataChannel.addEventListener('open', (event) => {
|
||||
this.onDataChannelOpen = (event) => {
|
||||
this.state = {
|
||||
type: EngineConnectionStateType.Connecting,
|
||||
value: {
|
||||
@ -654,14 +724,22 @@ class EngineConnection extends EventTarget {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent(EngineConnectionEvents.Opened, { detail: this })
|
||||
)
|
||||
})
|
||||
}
|
||||
this.unreliableDataChannel?.addEventListener(
|
||||
'open',
|
||||
this.onDataChannelOpen
|
||||
)
|
||||
|
||||
this.unreliableDataChannel.addEventListener('close', (event) => {
|
||||
this.onDataChannelClose = (event) => {
|
||||
this.disconnectAll()
|
||||
this.finalizeIfAllConnectionsClosed()
|
||||
})
|
||||
}
|
||||
this.unreliableDataChannel?.addEventListener(
|
||||
'close',
|
||||
this.onDataChannelClose
|
||||
)
|
||||
|
||||
this.unreliableDataChannel.addEventListener('error', (event) => {
|
||||
this.onDataChannelError = (event) => {
|
||||
this.disconnectAll()
|
||||
|
||||
this.state = {
|
||||
@ -674,8 +752,13 @@ class EngineConnection extends EventTarget {
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
this.unreliableDataChannel.addEventListener('message', (event) => {
|
||||
}
|
||||
this.unreliableDataChannel?.addEventListener(
|
||||
'error',
|
||||
this.onDataChannelError
|
||||
)
|
||||
|
||||
this.onDataChannelMessage = (event) => {
|
||||
const result: UnreliableResponses = JSON.parse(event.data)
|
||||
Object.values(
|
||||
this.engineCommandManager.unreliableSubscriptions[result.type] || {}
|
||||
@ -697,8 +780,13 @@ class EngineConnection extends EventTarget {
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
this.unreliableDataChannel.addEventListener(
|
||||
'message',
|
||||
this.onDataChannelMessage
|
||||
)
|
||||
}
|
||||
this.pc.addEventListener('datachannel', this.onDataChannel)
|
||||
}
|
||||
|
||||
const createWebSocketConnection = () => {
|
||||
@ -712,7 +800,7 @@ class EngineConnection extends EventTarget {
|
||||
this.websocket = new WebSocket(this.url, [])
|
||||
this.websocket.binaryType = 'arraybuffer'
|
||||
|
||||
this.websocket.addEventListener('open', (event) => {
|
||||
this.onWebSocketOpen = (event) => {
|
||||
this.state = {
|
||||
type: EngineConnectionStateType.Connecting,
|
||||
value: {
|
||||
@ -733,14 +821,16 @@ class EngineConnection extends EventTarget {
|
||||
// Send an initial ping
|
||||
this.send({ type: 'ping' })
|
||||
this.pingPongSpan.ping = new Date()
|
||||
})
|
||||
}
|
||||
this.websocket.addEventListener('open', this.onWebSocketOpen)
|
||||
|
||||
this.websocket.addEventListener('close', (event) => {
|
||||
this.onWebSocketClose = (event) => {
|
||||
this.disconnectAll()
|
||||
this.finalizeIfAllConnectionsClosed()
|
||||
})
|
||||
}
|
||||
this.websocket.addEventListener('close', this.onWebSocketClose)
|
||||
|
||||
this.websocket.addEventListener('error', (event) => {
|
||||
this.onWebSocketError = (event) => {
|
||||
this.disconnectAll()
|
||||
|
||||
this.state = {
|
||||
@ -753,9 +843,10 @@ class EngineConnection extends EventTarget {
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
this.websocket.addEventListener('error', this.onWebSocketError)
|
||||
|
||||
this.websocket.addEventListener('message', (event) => {
|
||||
this.onWebSocketMessage = (event) => {
|
||||
// In the EngineConnection, we're looking for messages to/from
|
||||
// the server that relate to the ICE handshake, or WebRTC
|
||||
// negotiation. There may be other messages (including ArrayBuffer
|
||||
@ -960,15 +1051,20 @@ class EngineConnection extends EventTarget {
|
||||
})
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
this.websocket.addEventListener('message', this.onWebSocketMessage)
|
||||
}
|
||||
|
||||
if (reconnecting) {
|
||||
createWebSocketConnection()
|
||||
} else {
|
||||
window.addEventListener('use-network-status-ready', () => {
|
||||
this.onNetworkStatusReady = () => {
|
||||
createWebSocketConnection()
|
||||
})
|
||||
}
|
||||
window.addEventListener(
|
||||
'use-network-status-ready',
|
||||
this.onNetworkStatusReady
|
||||
)
|
||||
}
|
||||
}
|
||||
// Do not change this back to an object or any, we should only be sending the
|
||||
@ -1154,7 +1250,15 @@ export class EngineCommandManager extends EventTarget {
|
||||
private makeDefaultPlanes: () => Promise<DefaultPlanes> | null = () => null
|
||||
private modifyGrid: (hidden: boolean) => Promise<void> | null = () => null
|
||||
|
||||
private onEngineConnectionOpened = () => {}
|
||||
private onEngineConnectionClosed = () => {}
|
||||
private onEngineConnectionStarted = ({ detail: engineConnection }: any) => {}
|
||||
private onEngineConnectionNewTrack = ({
|
||||
detail,
|
||||
}: CustomEvent<NewTrackArgs>) => {}
|
||||
|
||||
start({
|
||||
restart,
|
||||
setMediaStream,
|
||||
setIsStreamReady,
|
||||
width,
|
||||
@ -1170,6 +1274,7 @@ export class EngineCommandManager extends EventTarget {
|
||||
showScaleGrid: false,
|
||||
},
|
||||
}: {
|
||||
restart?: boolean
|
||||
setMediaStream: (stream: MediaStream) => void
|
||||
setIsStreamReady: (isStreamReady: boolean) => void
|
||||
width: number
|
||||
@ -1215,162 +1320,168 @@ export class EngineCommandManager extends EventTarget {
|
||||
})
|
||||
)
|
||||
|
||||
this.onEngineConnectionOpened = () => {
|
||||
// Set the stream background color
|
||||
// This takes RGBA values from 0-1
|
||||
// So we convert from the conventional 0-255 found in Figma
|
||||
|
||||
void this.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'set_background_color',
|
||||
color: getThemeColorForEngine(settings.theme),
|
||||
},
|
||||
})
|
||||
|
||||
// Sets the default line colors
|
||||
const opposingTheme = getOppositeTheme(settings.theme)
|
||||
this.sendSceneCommand({
|
||||
cmd_id: uuidv4(),
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'set_default_system_properties',
|
||||
color: getThemeColorForEngine(opposingTheme),
|
||||
},
|
||||
})
|
||||
|
||||
// Set the edge lines visibility
|
||||
this.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'edge_lines_visible' as any, // TODO: update kittycad.ts to use the correct type
|
||||
hidden: !settings.highlightEdges,
|
||||
},
|
||||
})
|
||||
|
||||
this._camControlsCameraChange()
|
||||
this.sendSceneCommand({
|
||||
// CameraControls subscribes to default_camera_get_settings response events
|
||||
// firing this at connection ensure the camera's are synced initially
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
// We want modify the grid first because we don't want it to flash.
|
||||
// Ideally these would already be default hidden in engine (TODO do
|
||||
// that) https://github.com/KittyCAD/engine/issues/2282
|
||||
this.modifyGrid(!settings.showScaleGrid)?.then(async () => {
|
||||
await this.initPlanes()
|
||||
this.resolveReady()
|
||||
setIsStreamReady(true)
|
||||
await executeCode()
|
||||
})
|
||||
}
|
||||
this.engineConnection.addEventListener(
|
||||
EngineConnectionEvents.Opened,
|
||||
() => {
|
||||
// Set the stream background color
|
||||
// This takes RGBA values from 0-1
|
||||
// So we convert from the conventional 0-255 found in Figma
|
||||
|
||||
void this.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'set_background_color',
|
||||
color: getThemeColorForEngine(settings.theme),
|
||||
},
|
||||
})
|
||||
|
||||
// Sets the default line colors
|
||||
const opposingTheme = getOppositeTheme(settings.theme)
|
||||
this.sendSceneCommand({
|
||||
cmd_id: uuidv4(),
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'set_default_system_properties',
|
||||
color: getThemeColorForEngine(opposingTheme),
|
||||
},
|
||||
})
|
||||
|
||||
// Set the edge lines visibility
|
||||
this.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'edge_lines_visible' as any, // TODO: update kittycad.ts to use the correct type
|
||||
hidden: !settings.highlightEdges,
|
||||
},
|
||||
})
|
||||
|
||||
this._camControlsCameraChange()
|
||||
this.sendSceneCommand({
|
||||
// CameraControls subscribes to default_camera_get_settings response events
|
||||
// firing this at connection ensure the camera's are synced initially
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
// We want modify the grid first because we don't want it to flash.
|
||||
// Ideally these would already be default hidden in engine (TODO do
|
||||
// that) https://github.com/KittyCAD/engine/issues/2282
|
||||
this.modifyGrid(!settings.showScaleGrid)?.then(async () => {
|
||||
await this.initPlanes()
|
||||
this.resolveReady()
|
||||
setIsStreamReady(true)
|
||||
await executeCode()
|
||||
})
|
||||
}
|
||||
this.onEngineConnectionOpened
|
||||
)
|
||||
|
||||
this.onEngineConnectionClosed = () => {
|
||||
setIsStreamReady(false)
|
||||
}
|
||||
this.engineConnection.addEventListener(
|
||||
EngineConnectionEvents.Closed,
|
||||
() => {
|
||||
setIsStreamReady(false)
|
||||
}
|
||||
this.onEngineConnectionClosed
|
||||
)
|
||||
|
||||
this.engineConnection.addEventListener(
|
||||
EngineConnectionEvents.ConnectionStarted,
|
||||
({ detail: engineConnection }: any) => {
|
||||
engineConnection?.pc?.addEventListener(
|
||||
'datachannel',
|
||||
(event: RTCDataChannelEvent) => {
|
||||
let unreliableDataChannel = event.channel
|
||||
this.onEngineConnectionStarted = ({ detail: engineConnection }: any) => {
|
||||
engineConnection?.pc?.addEventListener(
|
||||
'datachannel',
|
||||
(event: RTCDataChannelEvent) => {
|
||||
let unreliableDataChannel = event.channel
|
||||
|
||||
unreliableDataChannel.addEventListener(
|
||||
'message',
|
||||
(event: MessageEvent) => {
|
||||
const result: UnreliableResponses = JSON.parse(event.data)
|
||||
Object.values(
|
||||
this.unreliableSubscriptions[result.type] || {}
|
||||
).forEach(
|
||||
// TODO: There is only one response that uses the unreliable channel atm,
|
||||
// highlight_set_entity, if there are more it's likely they will all have the same
|
||||
// sequence logic, but I'm not sure if we use a single global sequence or a sequence
|
||||
// per unreliable subscription.
|
||||
(callback) => {
|
||||
let data = result?.data
|
||||
if (isHighlightSetEntity_type(data)) {
|
||||
if (
|
||||
data.sequence !== undefined &&
|
||||
data.sequence > this.inSequence
|
||||
) {
|
||||
this.inSequence = data.sequence
|
||||
callback(result)
|
||||
}
|
||||
unreliableDataChannel.addEventListener(
|
||||
'message',
|
||||
(event: MessageEvent) => {
|
||||
const result: UnreliableResponses = JSON.parse(event.data)
|
||||
Object.values(
|
||||
this.unreliableSubscriptions[result.type] || {}
|
||||
).forEach(
|
||||
// TODO: There is only one response that uses the unreliable channel atm,
|
||||
// highlight_set_entity, if there are more it's likely they will all have the same
|
||||
// sequence logic, but I'm not sure if we use a single global sequence or a sequence
|
||||
// per unreliable subscription.
|
||||
(callback) => {
|
||||
let data = result?.data
|
||||
if (isHighlightSetEntity_type(data)) {
|
||||
if (
|
||||
data.sequence !== undefined &&
|
||||
data.sequence > this.inSequence
|
||||
) {
|
||||
this.inSequence = data.sequence
|
||||
callback(result)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
// When the EngineConnection starts a connection, we want to register
|
||||
// callbacks into the WebSocket/PeerConnection.
|
||||
engineConnection.websocket?.addEventListener('message', ((
|
||||
event: MessageEvent
|
||||
) => {
|
||||
if (event.data instanceof ArrayBuffer) {
|
||||
// If the data is an ArrayBuffer, it's the result of an export command,
|
||||
// because in all other cases we send JSON strings. But in the case of
|
||||
// export we send a binary blob.
|
||||
// Pass this to our export function.
|
||||
exportSave(event.data).then(() => {
|
||||
this.pendingExport?.resolve()
|
||||
}, this.pendingExport?.reject)
|
||||
} else {
|
||||
const message: Models['WebSocketResponse_type'] = JSON.parse(
|
||||
event.data
|
||||
)
|
||||
if (
|
||||
message.success &&
|
||||
(message.resp.type === 'modeling' ||
|
||||
message.resp.type === 'modeling_batch') &&
|
||||
message.request_id
|
||||
) {
|
||||
this.handleModelingCommand(
|
||||
message.resp,
|
||||
message.request_id,
|
||||
message
|
||||
}
|
||||
)
|
||||
} else if (
|
||||
!message.success &&
|
||||
message.request_id &&
|
||||
this.artifactMap[message.request_id]
|
||||
) {
|
||||
this.handleFailedModelingCommand(message.request_id, message)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
// When the EngineConnection starts a connection, we want to register
|
||||
// callbacks into the WebSocket/PeerConnection.
|
||||
engineConnection.websocket?.addEventListener('message', ((
|
||||
event: MessageEvent
|
||||
) => {
|
||||
if (event.data instanceof ArrayBuffer) {
|
||||
// If the data is an ArrayBuffer, it's the result of an export command,
|
||||
// because in all other cases we send JSON strings. But in the case of
|
||||
// export we send a binary blob.
|
||||
// Pass this to our export function.
|
||||
exportSave(event.data).then(() => {
|
||||
this.pendingExport?.resolve()
|
||||
}, this.pendingExport?.reject)
|
||||
} else {
|
||||
const message: Models['WebSocketResponse_type'] = JSON.parse(
|
||||
event.data
|
||||
)
|
||||
if (
|
||||
message.success &&
|
||||
(message.resp.type === 'modeling' ||
|
||||
message.resp.type === 'modeling_batch') &&
|
||||
message.request_id
|
||||
) {
|
||||
this.handleModelingCommand(
|
||||
message.resp,
|
||||
message.request_id,
|
||||
message
|
||||
)
|
||||
} else if (
|
||||
!message.success &&
|
||||
message.request_id &&
|
||||
this.artifactMap[message.request_id]
|
||||
) {
|
||||
this.handleFailedModelingCommand(message.request_id, message)
|
||||
}
|
||||
}) as EventListener)
|
||||
}
|
||||
}) as EventListener)
|
||||
|
||||
this.engineConnection?.addEventListener(
|
||||
EngineConnectionEvents.NewTrack,
|
||||
(({ detail: { mediaStream } }: CustomEvent<NewTrackArgs>) => {
|
||||
mediaStream.getVideoTracks()[0].addEventListener('mute', () => {
|
||||
console.error(
|
||||
'video track mute: check webrtc internals -> inbound rtp'
|
||||
)
|
||||
})
|
||||
this.onEngineConnectionNewTrack = ({
|
||||
detail: { mediaStream },
|
||||
}: CustomEvent<NewTrackArgs>) => {
|
||||
mediaStream.getVideoTracks()[0].addEventListener('mute', () => {
|
||||
console.error(
|
||||
'video track mute: check webrtc internals -> inbound rtp'
|
||||
)
|
||||
})
|
||||
|
||||
setMediaStream(mediaStream)
|
||||
}) as EventListener
|
||||
)
|
||||
|
||||
this.engineConnection?.connect()
|
||||
setMediaStream(mediaStream)
|
||||
}
|
||||
this.engineConnection?.addEventListener(
|
||||
EngineConnectionEvents.NewTrack,
|
||||
this.onEngineConnectionNewTrack as EventListener
|
||||
)
|
||||
|
||||
this.engineConnection?.connect()
|
||||
}
|
||||
this.engineConnection.addEventListener(
|
||||
EngineConnectionEvents.ConnectionStarted,
|
||||
this.onEngineConnectionStarted
|
||||
)
|
||||
}
|
||||
|
||||
@ -1629,7 +1740,26 @@ export class EngineCommandManager extends EventTarget {
|
||||
}
|
||||
tearDown() {
|
||||
if (this.engineConnection) {
|
||||
this.engineConnection.removeEventListener(
|
||||
EngineConnectionEvents.Opened,
|
||||
this.onEngineConnectionOpened
|
||||
)
|
||||
this.engineConnection.removeEventListener(
|
||||
EngineConnectionEvents.Closed,
|
||||
this.onEngineConnectionClosed
|
||||
)
|
||||
this.engineConnection.removeEventListener(
|
||||
EngineConnectionEvents.ConnectionStarted,
|
||||
this.onEngineConnectionStarted
|
||||
)
|
||||
this.engineConnection.removeEventListener(
|
||||
EngineConnectionEvents.NewTrack,
|
||||
this.onEngineConnectionNewTrack as EventListener
|
||||
)
|
||||
|
||||
this.engineConnection?.tearDown()
|
||||
this.engineConnection = undefined
|
||||
|
||||
// Our window.tearDown assignment causes this case to happen which is
|
||||
// only really for tests.
|
||||
// @ts-ignore
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { CommandSetConfig } from 'lib/commandTypes'
|
||||
import { StateMachineCommandSetConfig } from 'lib/commandTypes'
|
||||
import { authMachine } from 'machines/authMachine'
|
||||
|
||||
type AuthCommandSchema = {}
|
||||
|
||||
export const authCommandBarConfig: CommandSetConfig<
|
||||
export const authCommandBarConfig: StateMachineCommandSetConfig<
|
||||
typeof authMachine,
|
||||
AuthCommandSchema
|
||||
> = {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { CommandSetConfig } from 'lib/commandTypes'
|
||||
import { StateMachineCommandSetConfig } from 'lib/commandTypes'
|
||||
import { homeMachine } from 'machines/homeMachine'
|
||||
|
||||
export type HomeCommandSchema = {
|
||||
@ -17,7 +17,7 @@ export type HomeCommandSchema = {
|
||||
}
|
||||
}
|
||||
|
||||
export const homeCommandBarConfig: CommandSetConfig<
|
||||
export const homeCommandBarConfig: StateMachineCommandSetConfig<
|
||||
typeof homeMachine,
|
||||
HomeCommandSchema
|
||||
> = {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { CommandSetConfig, KclCommandValue } from 'lib/commandTypes'
|
||||
import { StateMachineCommandSetConfig, KclCommandValue } from 'lib/commandTypes'
|
||||
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { modelingMachine } from 'machines/modelingMachine'
|
||||
import { modelingMachine, SketchTool } from 'machines/modelingMachine'
|
||||
|
||||
type OutputFormat = Models['OutputFormat_type']
|
||||
type OutputTypeKey = OutputFormat['type']
|
||||
@ -27,9 +27,12 @@ export type ModelingCommandSchema = {
|
||||
// result: (typeof EXTRUSION_RESULTS)[number]
|
||||
distance: KclCommandValue
|
||||
}
|
||||
'change tool': {
|
||||
tool: SketchTool
|
||||
}
|
||||
}
|
||||
|
||||
export const modelingMachineConfig: CommandSetConfig<
|
||||
export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
typeof modelingMachine,
|
||||
ModelingCommandSchema
|
||||
> = {
|
||||
@ -37,22 +40,47 @@ export const modelingMachineConfig: CommandSetConfig<
|
||||
description: 'Enter sketch mode.',
|
||||
icon: 'sketch',
|
||||
},
|
||||
// TODO the event is no 'change tool' with data: 'line', 'rectangle' etc
|
||||
// 'Equip Line tool': {
|
||||
// description: 'Start drawing straight lines.',
|
||||
// icon: 'line',
|
||||
// displayName: 'Line',
|
||||
// },
|
||||
// 'Equip tangential arc to': {
|
||||
// description: 'Start drawing an arc tangent to the current segment.',
|
||||
// icon: 'arc',
|
||||
// displayName: 'Tangential Arc',
|
||||
// },
|
||||
// 'Equip rectangle tool': {
|
||||
// description: 'Start drawing a rectangle.',
|
||||
// icon: 'rectangle',
|
||||
// displayName: 'Rectangle',
|
||||
// },
|
||||
'change tool': [
|
||||
{
|
||||
description: 'Start drawing straight lines.',
|
||||
icon: 'line',
|
||||
displayName: 'Line',
|
||||
args: {
|
||||
tool: {
|
||||
defaultValue: 'line',
|
||||
required: true,
|
||||
skip: true,
|
||||
inputType: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Start drawing an arc tangent to the current segment.',
|
||||
icon: 'arc',
|
||||
displayName: 'Tangential Arc',
|
||||
args: {
|
||||
tool: {
|
||||
defaultValue: 'tangentialArc',
|
||||
required: true,
|
||||
skip: true,
|
||||
inputType: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Start drawing a rectangle.',
|
||||
icon: 'rectangle',
|
||||
displayName: 'Rectangle',
|
||||
args: {
|
||||
tool: {
|
||||
defaultValue: 'rectangle',
|
||||
required: true,
|
||||
skip: true,
|
||||
inputType: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
Export: {
|
||||
description: 'Export the current model.',
|
||||
icon: 'exportFile',
|
||||
|
@ -124,6 +124,7 @@ export function createSettingsCommand({
|
||||
displayName: `Settings · ${decamelize(type.replaceAll('.', ' · '), {
|
||||
separator: ' ',
|
||||
})}`,
|
||||
description: settingConfig.description,
|
||||
groupId: 'settings',
|
||||
icon: 'settings',
|
||||
needsReview: false,
|
||||
|
@ -33,13 +33,13 @@ export interface KclExpressionWithVariable extends KclExpression {
|
||||
export type KclCommandValue = KclExpression | KclExpressionWithVariable
|
||||
export type CommandInputType = (typeof INPUT_TYPES)[number]
|
||||
|
||||
export type CommandSetSchema<T extends AnyStateMachine> = Partial<{
|
||||
export type StateMachineCommandSetSchema<T extends AnyStateMachine> = Partial<{
|
||||
[EventType in EventFrom<T>['type']]: Record<string, any>
|
||||
}>
|
||||
|
||||
export type CommandSet<
|
||||
export type StateMachineCommandSet<
|
||||
T extends AllMachines,
|
||||
Schema extends CommandSetSchema<T>
|
||||
Schema extends StateMachineCommandSetSchema<T>
|
||||
> = Partial<{
|
||||
[EventType in EventFrom<T>['type']]: Command<
|
||||
T,
|
||||
@ -48,21 +48,25 @@ export type CommandSet<
|
||||
>
|
||||
}>
|
||||
|
||||
export type CommandSetConfig<
|
||||
/**
|
||||
* A configuration object for a set of commands tied to a state machine.
|
||||
* Each event type can have one or more commands associated with it.
|
||||
* @param T The state machine type.
|
||||
* @param Schema The schema for the command set, defined by the developer.
|
||||
*/
|
||||
export type StateMachineCommandSetConfig<
|
||||
T extends AllMachines,
|
||||
Schema extends CommandSetSchema<T>
|
||||
Schema extends StateMachineCommandSetSchema<T>
|
||||
> = Partial<{
|
||||
[EventType in EventFrom<T>['type']]: CommandConfig<
|
||||
T,
|
||||
EventFrom<T>['type'],
|
||||
Schema[EventType]
|
||||
>
|
||||
[EventType in EventFrom<T>['type']]:
|
||||
| CommandConfig<T, EventFrom<T>['type'], Schema[EventType]>
|
||||
| CommandConfig<T, EventFrom<T>['type'], Schema[EventType]>[]
|
||||
}>
|
||||
|
||||
export type Command<
|
||||
T extends AnyStateMachine = AnyStateMachine,
|
||||
CommandName extends EventFrom<T>['type'] = EventFrom<T>['type'],
|
||||
CommandSchema extends CommandSetSchema<T>[CommandName] = CommandSetSchema<T>[CommandName]
|
||||
CommandSchema extends StateMachineCommandSetSchema<T>[CommandName] = StateMachineCommandSetSchema<T>[CommandName]
|
||||
> = {
|
||||
name: CommandName
|
||||
groupId: T['id']
|
||||
@ -81,7 +85,7 @@ export type Command<
|
||||
export type CommandConfig<
|
||||
T extends AnyStateMachine = AnyStateMachine,
|
||||
CommandName extends EventFrom<T>['type'] = EventFrom<T>['type'],
|
||||
CommandSchema extends CommandSetSchema<T>[CommandName] = CommandSetSchema<T>[CommandName]
|
||||
CommandSchema extends StateMachineCommandSetSchema<T>[CommandName] = StateMachineCommandSetSchema<T>[CommandName]
|
||||
> = Omit<
|
||||
Command<T, CommandName, CommandSchema>,
|
||||
'name' | 'groupId' | 'onSubmit' | 'onCancel' | 'args' | 'needsReview'
|
||||
|
@ -11,20 +11,20 @@ import {
|
||||
CommandArgument,
|
||||
CommandArgumentConfig,
|
||||
CommandConfig,
|
||||
CommandSetConfig,
|
||||
CommandSetSchema,
|
||||
StateMachineCommandSetConfig,
|
||||
StateMachineCommandSetSchema,
|
||||
} from './commandTypes'
|
||||
|
||||
interface CreateMachineCommandProps<
|
||||
T extends AnyStateMachine,
|
||||
S extends CommandSetSchema<T>
|
||||
S extends StateMachineCommandSetSchema<T>
|
||||
> {
|
||||
type: EventFrom<T>['type']
|
||||
groupId: T['id']
|
||||
state: StateFrom<T>
|
||||
send: Function
|
||||
actor: InterpreterFrom<T>
|
||||
commandBarConfig?: CommandSetConfig<T, S>
|
||||
commandBarConfig?: StateMachineCommandSetConfig<T, S>
|
||||
onCancel?: () => void
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ interface CreateMachineCommandProps<
|
||||
// from a more terse Command Bar Meta definition.
|
||||
export function createMachineCommand<
|
||||
T extends AnyStateMachine,
|
||||
S extends CommandSetSchema<T>
|
||||
S extends StateMachineCommandSetSchema<T>
|
||||
>({
|
||||
groupId,
|
||||
type,
|
||||
@ -41,13 +41,30 @@ export function createMachineCommand<
|
||||
actor,
|
||||
commandBarConfig,
|
||||
onCancel,
|
||||
}: CreateMachineCommandProps<T, S>): Command<
|
||||
T,
|
||||
typeof type,
|
||||
S[typeof type]
|
||||
> | null {
|
||||
}: CreateMachineCommandProps<T, S>):
|
||||
| Command<T, typeof type, S[typeof type]>
|
||||
| Command<T, typeof type, S[typeof type]>[]
|
||||
| null {
|
||||
const commandConfig = commandBarConfig && commandBarConfig[type]
|
||||
if (!commandConfig) return null
|
||||
// There may be no command config for this event type,
|
||||
// or there may be multiple commands to create.
|
||||
if (!commandConfig) {
|
||||
return null
|
||||
} else if (commandConfig instanceof Array) {
|
||||
return commandConfig
|
||||
.map((config) =>
|
||||
createMachineCommand({
|
||||
groupId,
|
||||
type,
|
||||
state,
|
||||
send,
|
||||
actor,
|
||||
commandBarConfig: { [type]: config },
|
||||
onCancel,
|
||||
})
|
||||
)
|
||||
.filter((c) => c !== null) as Command<T, typeof type, S[typeof type]>[]
|
||||
}
|
||||
|
||||
// Hide commands based on platform by returning `null`
|
||||
// so the consumer can filter them out
|
||||
@ -64,6 +81,7 @@ export function createMachineCommand<
|
||||
name: type,
|
||||
groupId,
|
||||
icon,
|
||||
description: commandConfig.description,
|
||||
needsReview: commandConfig.needsReview || false,
|
||||
onSubmit: (data?: S[typeof type]) => {
|
||||
if (data !== undefined && data !== null) {
|
||||
@ -84,6 +102,10 @@ export function createMachineCommand<
|
||||
command.onCancel = onCancel
|
||||
}
|
||||
|
||||
if ('displayName' in commandConfig) {
|
||||
command.displayName = commandConfig.displayName
|
||||
}
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
@ -92,7 +114,7 @@ export function createMachineCommand<
|
||||
// bundled together into the args for a Command.
|
||||
function buildCommandArguments<
|
||||
T extends AnyStateMachine,
|
||||
S extends CommandSetSchema<T>,
|
||||
S extends StateMachineCommandSetSchema<T>,
|
||||
CommandName extends EventFrom<T>['type'] = EventFrom<T>['type']
|
||||
>(
|
||||
state: StateFrom<T>,
|
||||
@ -112,7 +134,7 @@ function buildCommandArguments<
|
||||
|
||||
export function buildCommandArgument<
|
||||
T extends AnyStateMachine,
|
||||
O extends CommandSetSchema<T> = CommandSetSchema<T>
|
||||
O extends StateMachineCommandSetSchema<T> = StateMachineCommandSetSchema<T>
|
||||
>(
|
||||
arg: CommandArgumentConfig<O, T>,
|
||||
context: ContextFrom<T>,
|
||||
|
@ -66,7 +66,7 @@ export type CommandBarMachineEvent =
|
||||
|
||||
export const commandBarMachine = createMachine(
|
||||
{
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QGED2BbdBDAdhABAEJYBOAxMgDaqxgDaADALqKgAONAlgC6eo6sQAD0QBaAJwA6AGwAmAKwBmBoukAWafIAcDcSoA0IAJ6JZDaZIDs8hgzV6AjA61a1DWQF8PhtJlwFiciowUkYWJBAOWB4+AQiRBFF5CwdpcVkHS1lpVyU5QxNEh1lFGTUsrUUtOQd5SwZLLx8MbDwiUkkqGkgyAHk2MBwwwSiY-kEEswZJbPltM0U3eXLZAsRFeQcrerUHRbTFvfkmkF9WgI6u2ggyADFONv98WkowAGNufDeW-2GI0d443iiHESkktUUilkskqdiUWjWCAcDC0kjUqnElkc6lkoK0JzOT0CnWo1zIAEEIARvn48LA-uwuIC4qAEqIHJjJPJcYtxFoYdJFFjVsZEG5pqCquJxJoGvJxOUCT82sSrj0AEpgdCoABuYC+yog9OYIyZsQmYmhWzMmTqLnU0kyikRGVRbj5lg2SmUam5StpFxIkgAymBXh8HlADQGyKHw58aecGZEzUDWYgchY7CisWoSlpQQ5EVDLJJxKkdIpyypUop-ed2kHCW0Xu9uD1kwDzcCEJCtlUNgWhZZ1OlnaKEBpZJItHVzDZ5xlpPWiZdDc8w22Ow5wozosyLUiVNMSg5ytVKmfrIipzO564z2otPVpI1vKd18SAOJYbgACzAEhI3wUgoAAV3QQZuFgCg-1wGAvjAkgSCgkCSHAyCcG4TtUxZYRED2TQZGSOw80qaQ7ARCc9mmZYSksextGkNJBRXFUOh-f9AOA0CIKgmCABE4E3D5oyTE1-lww8clKBjZF2Bh5SFZwXUUyRVDzJwNE2LQzzYwNJE4gCgJwKNeMw6DJHJAB3LAYlM-AHjYMDuFjMCACN0B4NCMKgnD927dMkWSUs8yY8RISfWQshvcsrDlapshULJPHfZsDKM7iHPM-jJAANSwShOAgX9IzICB+DASQHh1VAAGsqp1Qrit-MByXQvisP8sY8ISZwbCsDllBLSwz1qRFBQsRZ5WRIVkui-TG0M39jJ4jqLNgfLmpK3hTLIQCSFQIM2EoX8ADMjvQSQmqKna2vWvyJL3HrD1SEoyzSdJUkUm9dnUtQn10aK6hkxbiU1HVODAay3M87zE1+J6UwCtN8IQUauR0OZ0ksFwUUqRFPWmUdouPXR3TBjoIahmHKQIHKuqRrtUYSaVShhLF+TkeTzBFQosVKDRM2RVInEdSmg2p6GyE1bU9R8zrsKZqSexFqQHWlHNXHzCbFhnX1+UydFkVxiXJClmGAFEIG8hmld3ZGXp7TR5C5RSCxdjlQV1tR9bqaVdhsSFxDN5AALeOrgPa3ysJgiqcCqmr6sa7bWujxXjQd5nesQZYthsblIQYJx6hUic9AsdEFT2HQzByVLmgDJaw-eSOHPTjbysq6qcFqhrrtT9sO-4ugd1NFGc4QXEPo5eVLGsFxlnKREXGnZI9HcT1mOikO0s-S5w7bqNh9j-bkKOyQTvOy6B9utOHtj7qD1V8pSyrHJZ3STY4QmjQ0R2Ww0oSjuF3u+HAqAIBwEEOlRs48nZBVENkKQNprC42qBoJ0LothaXMJ9aElRXDLj3k3VUpJIBwOfgg4oMx8y41SPUOY8p5DFk2GiKoxsoRVmhGoM2cY2zAQRngChgU0a7HZtFH0ilRp1D5usVh6JXCKD2CUdI8lQ7rlbB8chkkJ6HkxFsBeulbQZAUCwrYCiqzIhyE+fqZtMomTMg-aCwiWYEV2NOBUCghQ6EFGzYsvpwQUT5MxawFECx2JWllRxMdLI2TsrtKMTkXIuMnmeCiZYwrePMFWCKv1XZKVGroWwmxNARK4g4hWG0tp3wSSk16lQpC41ULjV8uxUjSBvFCNEDTdCOkWCY44xCGzgzAJDaGdTnaZHBADYcqg5DpCovzeRno5izA0BeUOh8o5OPgDo+BaNvqV0xNFaE7gdYTnnvkmUChdKEMyF4LwQA */
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QGED2BbdBDAdhABAEJYBOAxMgDaqxgDaADALqKgAONAlgC6eo6sQAD0QBaAJwA6AGwAmAKwBmBoukAWafIAcDcSoA0IAJ6JZDaZIDs8hgzV6AjA61a1DWQF8PhtJlwFiciowUkYWJBAOWB4+AQiRBFF5CwdpcVkHS1lpVyU5QxNEh1lFGTUsrUUtOQd5SwZLLx8MbDwiUkkqGkgyAHk2MBwwwSiY-kEEswZJbPltM0U3eXLZAsRFeQcrerUHRbTFvfkmkF9WgI6u2ggyADFONv98WkowAGNufDeW-2GI0d443iiHESkktUUilkskqdiUWjWCAcDC0kjUqnElkc6lkoK0JzOT0CnWo1zIAEEIARvn48LA-uwuIC4qAEqIHJjJPJcYtxFoYdJFFjVsZEG5pqCquJxJoGvJxOUCT82sSrj0AEpgdCoABuYC+yog9OYIyZsQmYmhWzMmTqLnU0kyikRGVRbj5lg2SmUam5StpFxIkgAymBXh8HlADQGyKHw58aecGZEzUDWYgchY7CisWoSlpQQ5EVDLJJxKkdIpyypUop-ed2kHCW0Xu9uD1kwDzcCEJCtlUNgWhZZ1OlnaKEBpZJItHVzDZ5xlpPWiZdDc8w22O7JwozosyLUj3KiZZY85YtMUNgx5Ii81JuS5xBtPVCCyuVR0AOJYbgACzAEhI3wUgoAAV3QQZuFgCg-1wGAvjAkgSCgkCSHAyCcG4TtUxZYRED2TQZGSOw80qaQ7ARCc9mmZYSjPPFpDSQUP0DSQf3-QDgNAiCoJggAROBNw+aMkxNf5cMPHJSjPWRdhvZ9LGcF0b0kVQ8ycDRNkvNRWMbdjfwAoCcCjHjMOgyRyQAdywGITPwB42DA7hYzAgAjdAeDQjCoJw-du3TJE6mnWwoT0NIUTUAs71sMtdGsRYCyUtI9OJDijO49DeKw2BJAANSwShOAgX9IzICB+DASQHh1VAAGsqp1Qrit-MBySy8y-LGPCElSeoy2lZJcwcNRfXHQpBWmWQsRsPYdDPZJUu-QyuPssy+Py5qSt4EyyEAkhUCDNhKF-AAzQ70EkJqiu2tqOt88S926w9UhhLlykWXFHXcTJixsd60hHGxNj2Jag01HVODAKzXI8rzE1+R6U38tN8IQJSuR0OZ0gvHQXHGxBPWmUdppUWwFV0MHJAhqGYcpAh1qwrqDx7ZFajUmVpulEbqlqREsfBTQ0jcPM5P5KmaehshNW1PVvOy7Cka7VHeoYDkyjSPQtAvPMqkRQU1BnX1+UydFkQvCWwEhqWAFEIC8xnFd3ZHntZuTDZG4ob1nGxPTUfXrBnS8nDmEpT2ObxTnXVUALeOrgPanycvKyrqpwWqGqurbWsThXjWd5WesQZYtmBkplCceplIncK0SrXYqnccxxcj5s2OQWP4-s3PzJgiqcCqmr6sa7P2x7vi6AcAvJJ7XEy0dUFMWsFxlnKfn+S5CL3E9Jiuapjv3i7qNx+T-bDskY6zourObpz+6cuZgK0Y5Ua0TqEvXDkJR-YnR0s1xjkIMnDln3uuVsHwOxT1NCjIuR5nCSBUExJi1huRqyooUTYpYnzeyUFkKsy4Tg4FQBAOAgg26Nmga7QKohshSBtNYXGDonQumtPYaQfsSjLBHDefepJICUJZtQ4oMx8wXj6jebGt4JwbC2OiVwig9hh2hLpVu0cOhxjbMBBGeABFPwSA3ERRMlhKWCn9WRVQzZQirMo0BAYNzxn4RJGBh5MRbGXsHawGQFBmLrpUasOQorOAjs0OxaUVrGVMvfaCuiVYEV2KWTYg1yxyA0i6ZYaIPSL3lOkS8wSo6hOWpxCJ8te6WRsnZKMjlnIxNgSNIiiTF6vVSdI9JM1taXlxLzTwqiClBnSqtSJScLIFVvjtKANSXquENqoJwtgyZzRFIUQ4MhqgNACdNTQCpLbWyshMnsjpSwjXRJYHecgcmImsLIz0odNAaGqPiHpDYY6HwTlE+ATiqHPwohYewWQshmGhHrCcJz5Bck5toZwGhMheC8EAA */
|
||||
predictableActionArguments: true,
|
||||
tsTypes: {} as import('./commandBarMachine.typegen').Typegen0,
|
||||
context: {
|
||||
@ -147,6 +147,10 @@ export const commandBarMachine = createMachine(
|
||||
cond: 'Command has no arguments',
|
||||
actions: ['Execute command'],
|
||||
},
|
||||
{
|
||||
target: 'Checking Arguments',
|
||||
cond: 'All arguments are skippable',
|
||||
},
|
||||
{
|
||||
target: 'Gathering arguments',
|
||||
actions: ['Set current argument to first non-skippable'],
|
||||
@ -510,7 +514,9 @@ export const commandBarMachine = createMachine(
|
||||
)
|
||||
|
||||
function sortCommands(a: Command, b: Command) {
|
||||
if (b.groupId === 'auth') return -1
|
||||
if (a.groupId === 'auth') return 1
|
||||
if (b.groupId === 'auth' && !(a.groupId === 'auth')) return -2
|
||||
if (a.groupId === 'auth' && !(b.groupId === 'auth')) return 2
|
||||
if (b.groupId === 'settings' && !(a.groupId === 'settings')) return -1
|
||||
if (a.groupId === 'settings' && !(b.groupId === 'settings')) return 1
|
||||
return a.name.localeCompare(b.name)
|
||||
}
|
||||
|
10
src/wasm-lib/Cargo.lock
generated
@ -1842,7 +1842,7 @@ dependencies = [
|
||||
"bincode",
|
||||
"either",
|
||||
"fnv",
|
||||
"itertools 0.10.5",
|
||||
"itertools 0.12.1",
|
||||
"lazy_static",
|
||||
"nom",
|
||||
"quick-xml",
|
||||
@ -2928,18 +2928,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.61"
|
||||
version = "1.0.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
|
||||
checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.61"
|
||||
version = "1.0.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
|
||||
checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -35,7 +35,7 @@ schemars = { version = "0.8.17", features = ["impl_json_schema", "url", "uuid1"]
|
||||
serde = { version = "1.0.204", features = ["derive"] }
|
||||
serde_json = "1.0.120"
|
||||
sha2 = "0.10.8"
|
||||
thiserror = "1.0.61"
|
||||
thiserror = "1.0.62"
|
||||
toml = "0.8.14"
|
||||
ts-rs = { version = "9.0.1", features = ["uuid-impl", "url-impl", "chrono-impl", "no-serde-warnings", "serde-json-impl"] }
|
||||
url = { version = "2.5.2", features = ["serde"] }
|
||||
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 134 KiB |
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 126 KiB |
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 132 KiB |
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 130 KiB |
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 123 KiB |
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 126 KiB |
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 127 KiB |
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 122 KiB |
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 146 KiB |
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 160 KiB |
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 122 KiB |
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 132 KiB |
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 127 KiB |
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 119 KiB |
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 139 KiB |
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 125 KiB |
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 139 KiB |
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 124 KiB |
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 214 KiB |
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 125 KiB |
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 131 KiB |
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 122 KiB |
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 114 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 136 KiB |
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 131 KiB |
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 111 KiB |
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 189 KiB |
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 112 KiB |
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 112 KiB |
Before Width: | Height: | Size: 333 KiB After Width: | Height: | Size: 334 KiB |
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 129 KiB |
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 195 KiB |
Before Width: | Height: | Size: 250 KiB After Width: | Height: | Size: 250 KiB |
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 150 KiB |
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 147 KiB |
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 167 KiB |
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 168 KiB |
Before Width: | Height: | Size: 220 KiB After Width: | Height: | Size: 220 KiB |
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 118 KiB |
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 132 KiB |
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 157 KiB |
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 124 KiB |
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 107 KiB |
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 97 KiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 147 KiB |
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |