Add sketch tools back to the command bar (#3008)
* Make machine command type names more explicit * Prepare "change tool" event for command bar * Make it so that state machine events can each map to multiple command configs * Make commands with all skippable args possible * Add back the tools to the command bar * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * Update to use new `groupId` property name * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * Oops didn't save this other instance of `ownerMachine` * Add a playwright test --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
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
|
) // 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', () => {
|
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('CancelSketch')
|
||||||
: send({
|
: send({
|
||||||
type: 'change tool',
|
type: 'change tool',
|
||||||
data: 'line',
|
data: { tool: 'line' },
|
||||||
}),
|
}),
|
||||||
{ enabled: !disableLineButton, scopes: ['sketch'] }
|
{ enabled: !disableLineButton, scopes: ['sketch'] }
|
||||||
)
|
)
|
||||||
@ -75,7 +75,7 @@ export function Toolbar({
|
|||||||
? send('CancelSketch')
|
? send('CancelSketch')
|
||||||
: send({
|
: send({
|
||||||
type: 'change tool',
|
type: 'change tool',
|
||||||
data: 'tangentialArc',
|
data: { tool: 'tangentialArc' },
|
||||||
}),
|
}),
|
||||||
{ enabled: !disableTangentialArc, scopes: ['sketch'] }
|
{ enabled: !disableTangentialArc, scopes: ['sketch'] }
|
||||||
)
|
)
|
||||||
@ -89,7 +89,7 @@ export function Toolbar({
|
|||||||
? send('CancelSketch')
|
? send('CancelSketch')
|
||||||
: send({
|
: send({
|
||||||
type: 'change tool',
|
type: 'change tool',
|
||||||
data: 'rectangle',
|
data: { tool: 'rectangle' },
|
||||||
}),
|
}),
|
||||||
{ enabled: !disableRectangle, scopes: ['sketch'] }
|
{ enabled: !disableRectangle, scopes: ['sketch'] }
|
||||||
)
|
)
|
||||||
@ -263,7 +263,7 @@ export function Toolbar({
|
|||||||
? send('CancelSketch')
|
? send('CancelSketch')
|
||||||
: send({
|
: send({
|
||||||
type: 'change tool',
|
type: 'change tool',
|
||||||
data: 'line',
|
data: { tool: 'line' },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
aria-pressed={state?.matches('Sketch.Line tool')}
|
aria-pressed={state?.matches('Sketch.Line tool')}
|
||||||
@ -293,7 +293,7 @@ export function Toolbar({
|
|||||||
? send('CancelSketch')
|
? send('CancelSketch')
|
||||||
: send({
|
: send({
|
||||||
type: 'change tool',
|
type: 'change tool',
|
||||||
data: 'tangentialArc',
|
data: { tool: 'tangentialArc' },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
aria-pressed={state.matches('Sketch.Tangential arc to')}
|
aria-pressed={state.matches('Sketch.Tangential arc to')}
|
||||||
@ -323,7 +323,7 @@ export function Toolbar({
|
|||||||
? send('CancelSketch')
|
? send('CancelSketch')
|
||||||
: send({
|
: send({
|
||||||
type: 'change tool',
|
type: 'change tool',
|
||||||
data: 'rectangle',
|
data: { tool: 'rectangle' },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
aria-pressed={state.matches('Sketch.Rectangle tool')}
|
aria-pressed={state.matches('Sketch.Rectangle tool')}
|
||||||
|
@ -28,6 +28,11 @@ export const CommandBarProvider = ({
|
|||||||
Object.keys(context.selectedCommand?.args).length === 0
|
Object.keys(context.selectedCommand?.args).length === 0
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
'All arguments are skippable': (context, _event) => {
|
||||||
|
return Object.values(context.selectedCommand!.args!).every(
|
||||||
|
(argConfig) => argConfig.skip
|
||||||
|
)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ function CommandComboBox({
|
|||||||
>
|
>
|
||||||
{filteredOptions?.map((option) => (
|
{filteredOptions?.map((option) => (
|
||||||
<Combobox.Option
|
<Combobox.Option
|
||||||
key={option.name}
|
key={option.groupId + option.name + (option.displayName || '')}
|
||||||
value={option}
|
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-2 px-4 py-1 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90"
|
||||||
>
|
>
|
||||||
|
@ -42,7 +42,7 @@ import {
|
|||||||
import { applyConstraintIntersect } from './Toolbar/Intersect'
|
import { applyConstraintIntersect } from './Toolbar/Intersect'
|
||||||
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
|
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
|
||||||
import useStateMachineCommands from 'hooks/useStateMachineCommands'
|
import useStateMachineCommands from 'hooks/useStateMachineCommands'
|
||||||
import { modelingMachineConfig } from 'lib/commandBarConfigs/modelingCommandConfig'
|
import { modelingMachineCommandConfig } from 'lib/commandBarConfigs/modelingCommandConfig'
|
||||||
import {
|
import {
|
||||||
STRAIGHT_SEGMENT,
|
STRAIGHT_SEGMENT,
|
||||||
TANGENTIAL_ARC_TO_SEGMENT,
|
TANGENTIAL_ARC_TO_SEGMENT,
|
||||||
@ -920,7 +920,7 @@ export const ModelingMachineProvider = ({
|
|||||||
state: modelingState,
|
state: modelingState,
|
||||||
send: modelingSend,
|
send: modelingSend,
|
||||||
actor: modelingActor,
|
actor: modelingActor,
|
||||||
commandBarConfig: modelingMachineConfig,
|
commandBarConfig: modelingMachineCommandConfig,
|
||||||
allCommandsRequireNetwork: true,
|
allCommandsRequireNetwork: true,
|
||||||
// TODO for when sketch tools are in the toolbar: This was added when we used one "Cancel" event,
|
// 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
|
// but we need to support "SketchCancel" and basically
|
||||||
|
@ -6,7 +6,11 @@ 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'
|
||||||
import { homeMachine } from 'machines/homeMachine'
|
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 { useKclContext } from 'lang/KclProvider'
|
||||||
import { useNetworkContext } from 'hooks/useNetworkContext'
|
import { useNetworkContext } from 'hooks/useNetworkContext'
|
||||||
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
||||||
@ -21,20 +25,20 @@ export type AllMachines =
|
|||||||
|
|
||||||
interface UseStateMachineCommandsArgs<
|
interface UseStateMachineCommandsArgs<
|
||||||
T extends AllMachines,
|
T extends AllMachines,
|
||||||
S extends CommandSetSchema<T>
|
S extends StateMachineCommandSetSchema<T>
|
||||||
> {
|
> {
|
||||||
machineId: T['id']
|
machineId: T['id']
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
send: Function
|
send: Function
|
||||||
actor: InterpreterFrom<T>
|
actor: InterpreterFrom<T>
|
||||||
commandBarConfig?: CommandSetConfig<T, S>
|
commandBarConfig?: StateMachineCommandSetConfig<T, S>
|
||||||
allCommandsRequireNetwork?: boolean
|
allCommandsRequireNetwork?: boolean
|
||||||
onCancel?: () => void
|
onCancel?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function useStateMachineCommands<
|
export default function useStateMachineCommands<
|
||||||
T extends AnyStateMachine,
|
T extends AnyStateMachine,
|
||||||
S extends CommandSetSchema<T>
|
S extends StateMachineCommandSetSchema<T>
|
||||||
>({
|
>({
|
||||||
machineId,
|
machineId,
|
||||||
state,
|
state,
|
||||||
@ -58,7 +62,7 @@ export default function useStateMachineCommands<
|
|||||||
const newCommands = state.nextEvents
|
const newCommands = state.nextEvents
|
||||||
.filter((_) => !allCommandsRequireNetwork || !disableAllButtons)
|
.filter((_) => !allCommandsRequireNetwork || !disableAllButtons)
|
||||||
.filter((e) => !['done.', 'error.'].some((n) => e.includes(n)))
|
.filter((e) => !['done.', 'error.'].some((n) => e.includes(n)))
|
||||||
.map((type) =>
|
.flatMap((type) =>
|
||||||
createMachineCommand<T, S>({
|
createMachineCommand<T, S>({
|
||||||
// The group is the owner machine's ID.
|
// The group is the owner machine's ID.
|
||||||
groupId: machineId,
|
groupId: machineId,
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { CommandSetConfig } from 'lib/commandTypes'
|
import { StateMachineCommandSetConfig } from 'lib/commandTypes'
|
||||||
import { authMachine } from 'machines/authMachine'
|
import { authMachine } from 'machines/authMachine'
|
||||||
|
|
||||||
type AuthCommandSchema = {}
|
type AuthCommandSchema = {}
|
||||||
|
|
||||||
export const authCommandBarConfig: CommandSetConfig<
|
export const authCommandBarConfig: StateMachineCommandSetConfig<
|
||||||
typeof authMachine,
|
typeof authMachine,
|
||||||
AuthCommandSchema
|
AuthCommandSchema
|
||||||
> = {
|
> = {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CommandSetConfig } from 'lib/commandTypes'
|
import { StateMachineCommandSetConfig } from 'lib/commandTypes'
|
||||||
import { homeMachine } from 'machines/homeMachine'
|
import { homeMachine } from 'machines/homeMachine'
|
||||||
|
|
||||||
export type HomeCommandSchema = {
|
export type HomeCommandSchema = {
|
||||||
@ -17,7 +17,7 @@ export type HomeCommandSchema = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const homeCommandBarConfig: CommandSetConfig<
|
export const homeCommandBarConfig: StateMachineCommandSetConfig<
|
||||||
typeof homeMachine,
|
typeof homeMachine,
|
||||||
HomeCommandSchema
|
HomeCommandSchema
|
||||||
> = {
|
> = {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Models } from '@kittycad/lib'
|
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 { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
||||||
import { Selections } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
import { modelingMachine } from 'machines/modelingMachine'
|
import { modelingMachine, SketchTool } from 'machines/modelingMachine'
|
||||||
|
|
||||||
type OutputFormat = Models['OutputFormat_type']
|
type OutputFormat = Models['OutputFormat_type']
|
||||||
type OutputTypeKey = OutputFormat['type']
|
type OutputTypeKey = OutputFormat['type']
|
||||||
@ -27,9 +27,12 @@ export type ModelingCommandSchema = {
|
|||||||
// result: (typeof EXTRUSION_RESULTS)[number]
|
// result: (typeof EXTRUSION_RESULTS)[number]
|
||||||
distance: KclCommandValue
|
distance: KclCommandValue
|
||||||
}
|
}
|
||||||
|
'change tool': {
|
||||||
|
tool: SketchTool
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const modelingMachineConfig: CommandSetConfig<
|
export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||||
typeof modelingMachine,
|
typeof modelingMachine,
|
||||||
ModelingCommandSchema
|
ModelingCommandSchema
|
||||||
> = {
|
> = {
|
||||||
@ -37,22 +40,47 @@ export const modelingMachineConfig: CommandSetConfig<
|
|||||||
description: 'Enter sketch mode.',
|
description: 'Enter sketch mode.',
|
||||||
icon: 'sketch',
|
icon: 'sketch',
|
||||||
},
|
},
|
||||||
// TODO the event is no 'change tool' with data: 'line', 'rectangle' etc
|
'change tool': [
|
||||||
// 'Equip Line tool': {
|
{
|
||||||
// description: 'Start drawing straight lines.',
|
description: 'Start drawing straight lines.',
|
||||||
// icon: 'line',
|
icon: 'line',
|
||||||
// displayName: 'Line',
|
displayName: 'Line',
|
||||||
// },
|
args: {
|
||||||
// 'Equip tangential arc to': {
|
tool: {
|
||||||
// description: 'Start drawing an arc tangent to the current segment.',
|
defaultValue: 'line',
|
||||||
// icon: 'arc',
|
required: true,
|
||||||
// displayName: 'Tangential Arc',
|
skip: true,
|
||||||
// },
|
inputType: 'string',
|
||||||
// 'Equip rectangle tool': {
|
},
|
||||||
// description: 'Start drawing a rectangle.',
|
},
|
||||||
// icon: 'rectangle',
|
},
|
||||||
// displayName: 'Rectangle',
|
{
|
||||||
// },
|
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: {
|
Export: {
|
||||||
description: 'Export the current model.',
|
description: 'Export the current model.',
|
||||||
icon: 'exportFile',
|
icon: 'exportFile',
|
||||||
|
@ -33,13 +33,13 @@ export interface KclExpressionWithVariable extends KclExpression {
|
|||||||
export type KclCommandValue = KclExpression | KclExpressionWithVariable
|
export type KclCommandValue = KclExpression | KclExpressionWithVariable
|
||||||
export type CommandInputType = (typeof INPUT_TYPES)[number]
|
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>
|
[EventType in EventFrom<T>['type']]: Record<string, any>
|
||||||
}>
|
}>
|
||||||
|
|
||||||
export type CommandSet<
|
export type StateMachineCommandSet<
|
||||||
T extends AllMachines,
|
T extends AllMachines,
|
||||||
Schema extends CommandSetSchema<T>
|
Schema extends StateMachineCommandSetSchema<T>
|
||||||
> = Partial<{
|
> = Partial<{
|
||||||
[EventType in EventFrom<T>['type']]: Command<
|
[EventType in EventFrom<T>['type']]: Command<
|
||||||
T,
|
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,
|
T extends AllMachines,
|
||||||
Schema extends CommandSetSchema<T>
|
Schema extends StateMachineCommandSetSchema<T>
|
||||||
> = Partial<{
|
> = Partial<{
|
||||||
[EventType in EventFrom<T>['type']]: CommandConfig<
|
[EventType in EventFrom<T>['type']]:
|
||||||
T,
|
| CommandConfig<T, EventFrom<T>['type'], Schema[EventType]>
|
||||||
EventFrom<T>['type'],
|
| CommandConfig<T, EventFrom<T>['type'], Schema[EventType]>[]
|
||||||
Schema[EventType]
|
|
||||||
>
|
|
||||||
}>
|
}>
|
||||||
|
|
||||||
export type Command<
|
export type Command<
|
||||||
T extends AnyStateMachine = AnyStateMachine,
|
T extends AnyStateMachine = AnyStateMachine,
|
||||||
CommandName extends EventFrom<T>['type'] = EventFrom<T>['type'],
|
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
|
name: CommandName
|
||||||
groupId: T['id']
|
groupId: T['id']
|
||||||
@ -81,7 +85,7 @@ export type Command<
|
|||||||
export type CommandConfig<
|
export type CommandConfig<
|
||||||
T extends AnyStateMachine = AnyStateMachine,
|
T extends AnyStateMachine = AnyStateMachine,
|
||||||
CommandName extends EventFrom<T>['type'] = EventFrom<T>['type'],
|
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<
|
> = Omit<
|
||||||
Command<T, CommandName, CommandSchema>,
|
Command<T, CommandName, CommandSchema>,
|
||||||
'name' | 'groupId' | 'onSubmit' | 'onCancel' | 'args' | 'needsReview'
|
'name' | 'groupId' | 'onSubmit' | 'onCancel' | 'args' | 'needsReview'
|
||||||
|
@ -11,20 +11,20 @@ import {
|
|||||||
CommandArgument,
|
CommandArgument,
|
||||||
CommandArgumentConfig,
|
CommandArgumentConfig,
|
||||||
CommandConfig,
|
CommandConfig,
|
||||||
CommandSetConfig,
|
StateMachineCommandSetConfig,
|
||||||
CommandSetSchema,
|
StateMachineCommandSetSchema,
|
||||||
} from './commandTypes'
|
} from './commandTypes'
|
||||||
|
|
||||||
interface CreateMachineCommandProps<
|
interface CreateMachineCommandProps<
|
||||||
T extends AnyStateMachine,
|
T extends AnyStateMachine,
|
||||||
S extends CommandSetSchema<T>
|
S extends StateMachineCommandSetSchema<T>
|
||||||
> {
|
> {
|
||||||
type: EventFrom<T>['type']
|
type: EventFrom<T>['type']
|
||||||
groupId: T['id']
|
groupId: T['id']
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
send: Function
|
send: Function
|
||||||
actor: InterpreterFrom<T>
|
actor: InterpreterFrom<T>
|
||||||
commandBarConfig?: CommandSetConfig<T, S>
|
commandBarConfig?: StateMachineCommandSetConfig<T, S>
|
||||||
onCancel?: () => void
|
onCancel?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ interface CreateMachineCommandProps<
|
|||||||
// from a more terse Command Bar Meta definition.
|
// from a more terse Command Bar Meta definition.
|
||||||
export function createMachineCommand<
|
export function createMachineCommand<
|
||||||
T extends AnyStateMachine,
|
T extends AnyStateMachine,
|
||||||
S extends CommandSetSchema<T>
|
S extends StateMachineCommandSetSchema<T>
|
||||||
>({
|
>({
|
||||||
groupId,
|
groupId,
|
||||||
type,
|
type,
|
||||||
@ -41,13 +41,30 @@ export function createMachineCommand<
|
|||||||
actor,
|
actor,
|
||||||
commandBarConfig,
|
commandBarConfig,
|
||||||
onCancel,
|
onCancel,
|
||||||
}: CreateMachineCommandProps<T, S>): Command<
|
}: CreateMachineCommandProps<T, S>):
|
||||||
T,
|
| Command<T, typeof type, S[typeof type]>
|
||||||
typeof type,
|
| Command<T, typeof type, S[typeof type]>[]
|
||||||
S[typeof type]
|
| null {
|
||||||
> | null {
|
|
||||||
const commandConfig = commandBarConfig && commandBarConfig[type]
|
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`
|
// Hide commands based on platform by returning `null`
|
||||||
// so the consumer can filter them out
|
// so the consumer can filter them out
|
||||||
@ -84,6 +101,10 @@ export function createMachineCommand<
|
|||||||
command.onCancel = onCancel
|
command.onCancel = onCancel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ('displayName' in commandConfig) {
|
||||||
|
command.displayName = commandConfig.displayName
|
||||||
|
}
|
||||||
|
|
||||||
return command
|
return command
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +113,7 @@ export function createMachineCommand<
|
|||||||
// bundled together into the args for a Command.
|
// bundled together into the args for a Command.
|
||||||
function buildCommandArguments<
|
function buildCommandArguments<
|
||||||
T extends AnyStateMachine,
|
T extends AnyStateMachine,
|
||||||
S extends CommandSetSchema<T>,
|
S extends StateMachineCommandSetSchema<T>,
|
||||||
CommandName extends EventFrom<T>['type'] = EventFrom<T>['type']
|
CommandName extends EventFrom<T>['type'] = EventFrom<T>['type']
|
||||||
>(
|
>(
|
||||||
state: StateFrom<T>,
|
state: StateFrom<T>,
|
||||||
@ -112,7 +133,7 @@ function buildCommandArguments<
|
|||||||
|
|
||||||
export function buildCommandArgument<
|
export function buildCommandArgument<
|
||||||
T extends AnyStateMachine,
|
T extends AnyStateMachine,
|
||||||
O extends CommandSetSchema<T> = CommandSetSchema<T>
|
O extends StateMachineCommandSetSchema<T> = StateMachineCommandSetSchema<T>
|
||||||
>(
|
>(
|
||||||
arg: CommandArgumentConfig<O, T>,
|
arg: CommandArgumentConfig<O, T>,
|
||||||
context: ContextFrom<T>,
|
context: ContextFrom<T>,
|
||||||
|
@ -66,7 +66,7 @@ export type CommandBarMachineEvent =
|
|||||||
|
|
||||||
export const commandBarMachine = createMachine(
|
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,
|
predictableActionArguments: true,
|
||||||
tsTypes: {} as import('./commandBarMachine.typegen').Typegen0,
|
tsTypes: {} as import('./commandBarMachine.typegen').Typegen0,
|
||||||
context: {
|
context: {
|
||||||
@ -147,6 +147,10 @@ export const commandBarMachine = createMachine(
|
|||||||
cond: 'Command has no arguments',
|
cond: 'Command has no arguments',
|
||||||
actions: ['Execute command'],
|
actions: ['Execute command'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
target: 'Checking Arguments',
|
||||||
|
cond: 'All arguments are skippable',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
target: 'Gathering arguments',
|
target: 'Gathering arguments',
|
||||||
actions: ['Set current argument to first non-skippable'],
|
actions: ['Set current argument to first non-skippable'],
|
||||||
|