Migrate to XState v5 (#3735)
* migrate settingsMachine
* Guard events with properties instead
* migrate settingsMachine
* Migrate auth machine
* Migrate file machine
* Migrate depracated types
* Migrate home machine
* Migrate command bar machine
* Version fixes
* Migrate command bar machine
* Migrate modeling machine
* Migrate types, state.can, state.matches and state.nextEvents
* Fix syntax
* Pass in modelingState into editor manager instead of modeling event
* Fix issue with missing command bar provider
* Fix state transition
* Fix type issue in Home
* Make sure no guards rely on event type
* Fix up command bar submission logic
* Home machine tweaks to get things running
* Fix AST fillet function args
* Handle "Set selection" when it is called by actor onDone
* Remove unused imports
* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)
* Fix injectin project to the fileTree machine
* Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)"
This reverts commit 4b43ff69d1
.
* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)
* Re-run CI
* Restore success toasts on file/folder deletion
* Replace casting with guarding against event.type
* Remove console.log
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
* Replace all instances of event casting with guards against event.type
---------
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
Co-authored-by: Frank Noirot <frank@zoo.dev>
This commit is contained in:
committed by
GitHub
parent
7c2cfba0ac
commit
5f8d4f8294
@ -1,4 +1,4 @@
|
||||
import { assign, createMachine } from 'xstate'
|
||||
import { assign, fromPromise, setup } from 'xstate'
|
||||
import {
|
||||
Command,
|
||||
CommandArgument,
|
||||
@ -25,7 +25,7 @@ export type CommandBarMachineEvent =
|
||||
data: { command: Command; argDefaultValues?: { [x: string]: unknown } }
|
||||
}
|
||||
| { type: 'Deselect command' }
|
||||
| { type: 'Submit command'; data: { [x: string]: unknown } }
|
||||
| { type: 'Submit command'; output: { [x: string]: unknown } }
|
||||
| {
|
||||
type: 'Add argument'
|
||||
data: { argument: CommandArgumentWithName<unknown> }
|
||||
@ -48,12 +48,16 @@ export type CommandBarMachineEvent =
|
||||
}
|
||||
| { type: 'Submit argument'; data: { [x: string]: unknown } }
|
||||
| {
|
||||
type: 'done.invoke.validateArguments'
|
||||
data: { [x: string]: unknown }
|
||||
type: 'xstate.done.actor.validateSingleArgument'
|
||||
output: { [x: string]: unknown }
|
||||
}
|
||||
| {
|
||||
type: 'error.platform.validateArguments'
|
||||
data: { message: string; arg: CommandArgumentWithName<unknown> }
|
||||
type: 'xstate.done.actor.validateArguments'
|
||||
output: { [x: string]: unknown }
|
||||
}
|
||||
| {
|
||||
type: 'xstate.error.actor.validateArguments'
|
||||
error: { message: string; arg: CommandArgumentWithName<unknown> }
|
||||
}
|
||||
| {
|
||||
type: 'Find and select command'
|
||||
@ -68,382 +72,199 @@ export type CommandBarMachineEvent =
|
||||
data: { [x: string]: CommandArgumentWithName<unknown> }
|
||||
}
|
||||
|
||||
export const commandBarMachine = createMachine(
|
||||
{
|
||||
/** @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: {
|
||||
commands: [],
|
||||
selectedCommand: undefined,
|
||||
currentArgument: undefined,
|
||||
selectionRanges: {
|
||||
otherSelections: [],
|
||||
codeBasedSelections: [],
|
||||
},
|
||||
argumentsToSubmit: {},
|
||||
} as CommandBarContext,
|
||||
id: 'Command Bar',
|
||||
initial: 'Closed',
|
||||
states: {
|
||||
Closed: {
|
||||
on: {
|
||||
Open: {
|
||||
target: 'Selecting command',
|
||||
},
|
||||
|
||||
'Find and select command': {
|
||||
target: 'Command selected',
|
||||
actions: [
|
||||
'Find and select command',
|
||||
'Initialize arguments to submit',
|
||||
],
|
||||
},
|
||||
|
||||
'Add commands': {
|
||||
target: 'Closed',
|
||||
|
||||
actions: [
|
||||
assign({
|
||||
commands: (context, event) =>
|
||||
[...context.commands, ...event.data.commands].sort(
|
||||
sortCommands
|
||||
),
|
||||
}),
|
||||
],
|
||||
|
||||
internal: true,
|
||||
},
|
||||
|
||||
'Remove commands': {
|
||||
target: 'Closed',
|
||||
|
||||
actions: [
|
||||
assign({
|
||||
commands: (context, event) =>
|
||||
context.commands.filter(
|
||||
(c) =>
|
||||
!event.data.commands.some(
|
||||
(c2) => c2.name === c.name && c2.groupId === c.groupId
|
||||
)
|
||||
),
|
||||
}),
|
||||
],
|
||||
|
||||
internal: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
'Selecting command': {
|
||||
on: {
|
||||
'Select command': {
|
||||
target: 'Command selected',
|
||||
actions: ['Set selected command', 'Initialize arguments to submit'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
'Command selected': {
|
||||
always: [
|
||||
{
|
||||
target: 'Closed',
|
||||
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'],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
'Gathering arguments': {
|
||||
states: {
|
||||
'Awaiting input': {
|
||||
on: {
|
||||
'Submit argument': {
|
||||
target: 'Validating',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Validating: {
|
||||
invoke: {
|
||||
src: 'Validate argument',
|
||||
id: 'validateArgument',
|
||||
onDone: {
|
||||
target: '#Command Bar.Checking Arguments',
|
||||
actions: [
|
||||
assign({
|
||||
argumentsToSubmit: (context, event) => {
|
||||
const [argName, argData] = Object.entries(event.data)[0]
|
||||
const { currentArgument } = context
|
||||
if (!currentArgument) return {}
|
||||
return {
|
||||
...context.argumentsToSubmit,
|
||||
[argName]: argData,
|
||||
}
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
onError: [
|
||||
{
|
||||
target: 'Awaiting input',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
initial: 'Awaiting input',
|
||||
|
||||
on: {
|
||||
'Change current argument': {
|
||||
target: 'Gathering arguments',
|
||||
internal: true,
|
||||
actions: ['Set current argument'],
|
||||
},
|
||||
|
||||
'Deselect command': {
|
||||
target: 'Selecting command',
|
||||
actions: [
|
||||
assign({
|
||||
selectedCommand: (_c, _e) => undefined,
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Review: {
|
||||
entry: ['Clear current argument'],
|
||||
on: {
|
||||
'Submit command': {
|
||||
target: 'Closed',
|
||||
actions: ['Execute command'],
|
||||
},
|
||||
|
||||
'Add argument': {
|
||||
target: 'Gathering arguments',
|
||||
actions: ['Set current argument'],
|
||||
},
|
||||
|
||||
'Remove argument': {
|
||||
target: 'Review',
|
||||
actions: ['Remove argument'],
|
||||
},
|
||||
|
||||
'Edit argument': {
|
||||
target: 'Gathering arguments',
|
||||
actions: ['Set current argument'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
'Checking Arguments': {
|
||||
invoke: {
|
||||
src: 'Validate all arguments',
|
||||
id: 'validateArguments',
|
||||
onDone: [
|
||||
{
|
||||
target: 'Review',
|
||||
cond: 'Command needs review',
|
||||
},
|
||||
{
|
||||
target: 'Closed',
|
||||
actions: 'Execute command',
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
target: 'Gathering arguments',
|
||||
actions: ['Set current argument to first non-skippable'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
on: {
|
||||
Close: {
|
||||
target: '.Closed',
|
||||
},
|
||||
|
||||
Clear: {
|
||||
target: '#Command Bar',
|
||||
internal: true,
|
||||
actions: ['Clear argument data'],
|
||||
},
|
||||
},
|
||||
schema: {
|
||||
events: {} as CommandBarMachineEvent,
|
||||
},
|
||||
preserveActionOrder: true,
|
||||
export const commandBarMachine = setup({
|
||||
types: {
|
||||
context: {} as CommandBarContext,
|
||||
events: {} as CommandBarMachineEvent,
|
||||
},
|
||||
{
|
||||
actions: {
|
||||
'Execute command': (context, event) => {
|
||||
const { selectedCommand } = context
|
||||
if (!selectedCommand) return
|
||||
if (
|
||||
(selectedCommand?.args && event.type === 'Submit command') ||
|
||||
event.type === 'done.invoke.validateArguments'
|
||||
) {
|
||||
const resolvedArgs = {} as { [x: string]: unknown }
|
||||
for (const [argName, argValue] of Object.entries(
|
||||
getCommandArgumentKclValuesOnly(event.data)
|
||||
)) {
|
||||
resolvedArgs[argName] =
|
||||
typeof argValue === 'function' ? argValue(context) : argValue
|
||||
}
|
||||
selectedCommand?.onSubmit(resolvedArgs)
|
||||
} else {
|
||||
selectedCommand?.onSubmit()
|
||||
actions: {
|
||||
enqueueValidArgsToSubmit: assign({
|
||||
argumentsToSubmit: ({ context, event }) => {
|
||||
if (event.type !== 'xstate.done.actor.validateSingleArgument') return {}
|
||||
const [argName, argData] = Object.entries(event.output)[0]
|
||||
const { currentArgument } = context
|
||||
if (!currentArgument) return {}
|
||||
return {
|
||||
...context.argumentsToSubmit,
|
||||
[argName]: argData,
|
||||
}
|
||||
},
|
||||
'Set current argument to first non-skippable': assign({
|
||||
currentArgument: (context, event) => {
|
||||
const { selectedCommand } = context
|
||||
if (!(selectedCommand && selectedCommand.args)) return undefined
|
||||
const rejectedArg = 'data' in event && event.data.arg
|
||||
}),
|
||||
'Execute command': ({ context, event }) => {
|
||||
const { selectedCommand } = context
|
||||
if (!selectedCommand) return
|
||||
if (
|
||||
(selectedCommand?.args && event.type === 'Submit command') ||
|
||||
event.type === 'xstate.done.actor.validateArguments'
|
||||
) {
|
||||
const resolvedArgs = {} as { [x: string]: unknown }
|
||||
for (const [argName, argValue] of Object.entries(
|
||||
getCommandArgumentKclValuesOnly(event.output)
|
||||
)) {
|
||||
resolvedArgs[argName] =
|
||||
typeof argValue === 'function' ? argValue(context) : argValue
|
||||
}
|
||||
selectedCommand?.onSubmit(resolvedArgs)
|
||||
} else {
|
||||
selectedCommand?.onSubmit()
|
||||
}
|
||||
},
|
||||
'Set current argument to first non-skippable': assign({
|
||||
currentArgument: ({ context, event }) => {
|
||||
const { selectedCommand } = context
|
||||
if (!(selectedCommand && selectedCommand.args)) return undefined
|
||||
const rejectedArg =
|
||||
'data' in event && 'arg' in event.data && event.data.arg
|
||||
|
||||
// Find the first argument that is not to be skipped:
|
||||
// that is, the first argument that is not already in the argumentsToSubmit
|
||||
// or that is not undefined, or that is not marked as "skippable".
|
||||
// TODO validate the type of the existing arguments
|
||||
let argIndex = 0
|
||||
// Find the first argument that is not to be skipped:
|
||||
// that is, the first argument that is not already in the argumentsToSubmit
|
||||
// or that is not undefined, or that is not marked as "skippable".
|
||||
// TODO validate the type of the existing arguments
|
||||
let argIndex = 0
|
||||
|
||||
while (argIndex < Object.keys(selectedCommand.args).length) {
|
||||
const [argName, argConfig] = Object.entries(selectedCommand.args)[
|
||||
argIndex
|
||||
]
|
||||
const argIsRequired =
|
||||
typeof argConfig.required === 'function'
|
||||
? argConfig.required(context)
|
||||
: argConfig.required
|
||||
const mustNotSkipArg =
|
||||
argIsRequired &&
|
||||
(!context.argumentsToSubmit.hasOwnProperty(argName) ||
|
||||
context.argumentsToSubmit[argName] === undefined ||
|
||||
(rejectedArg && rejectedArg.name === argName))
|
||||
while (argIndex < Object.keys(selectedCommand.args).length) {
|
||||
const [argName, argConfig] = Object.entries(selectedCommand.args)[
|
||||
argIndex
|
||||
]
|
||||
const argIsRequired =
|
||||
typeof argConfig.required === 'function'
|
||||
? argConfig.required(context)
|
||||
: argConfig.required
|
||||
const mustNotSkipArg =
|
||||
argIsRequired &&
|
||||
(!context.argumentsToSubmit.hasOwnProperty(argName) ||
|
||||
context.argumentsToSubmit[argName] === undefined ||
|
||||
(rejectedArg &&
|
||||
typeof rejectedArg === 'object' &&
|
||||
'name' in rejectedArg &&
|
||||
rejectedArg.name === argName))
|
||||
|
||||
if (
|
||||
mustNotSkipArg === true ||
|
||||
argIndex + 1 === Object.keys(selectedCommand.args).length
|
||||
) {
|
||||
// If we have reached the end of the arguments and none are skippable,
|
||||
// return the last argument.
|
||||
return {
|
||||
...selectedCommand.args[argName],
|
||||
name: argName,
|
||||
}
|
||||
if (
|
||||
mustNotSkipArg === true ||
|
||||
argIndex + 1 === Object.keys(selectedCommand.args).length
|
||||
) {
|
||||
// If we have reached the end of the arguments and none are skippable,
|
||||
// return the last argument.
|
||||
return {
|
||||
...selectedCommand.args[argName],
|
||||
name: argName,
|
||||
}
|
||||
argIndex++
|
||||
}
|
||||
argIndex++
|
||||
}
|
||||
|
||||
// TODO: use an XState service to continue onto review step
|
||||
// if all arguments are skippable and contain values.
|
||||
return undefined
|
||||
},
|
||||
}),
|
||||
'Clear current argument': assign({
|
||||
currentArgument: undefined,
|
||||
}),
|
||||
'Remove argument': assign({
|
||||
argumentsToSubmit: (context, event) => {
|
||||
if (event.type !== 'Remove argument') return context.argumentsToSubmit
|
||||
const argToRemove = Object.values(event.data)[0]
|
||||
// Extract all but the argument to remove and return it
|
||||
const { [argToRemove.name]: _, ...rest } = context.argumentsToSubmit
|
||||
return rest
|
||||
},
|
||||
}),
|
||||
'Set current argument': assign({
|
||||
currentArgument: (context, event) => {
|
||||
switch (event.type) {
|
||||
case 'Edit argument':
|
||||
return event.data.arg
|
||||
case 'Change current argument':
|
||||
return Object.values(event.data)[0]
|
||||
default:
|
||||
return context.currentArgument
|
||||
}
|
||||
},
|
||||
}),
|
||||
'Clear argument data': assign({
|
||||
selectedCommand: undefined,
|
||||
currentArgument: undefined,
|
||||
argumentsToSubmit: {},
|
||||
}),
|
||||
'Set selected command': assign({
|
||||
selectedCommand: (c, e) =>
|
||||
e.type === 'Select command' ? e.data.command : c.selectedCommand,
|
||||
}),
|
||||
'Find and select command': assign({
|
||||
selectedCommand: (c, e) => {
|
||||
if (e.type !== 'Find and select command') return c.selectedCommand
|
||||
const found = c.commands.find(
|
||||
(cmd) => cmd.name === e.data.name && cmd.groupId === e.data.groupId
|
||||
)
|
||||
|
||||
return !!found ? found : c.selectedCommand
|
||||
},
|
||||
}),
|
||||
'Initialize arguments to submit': assign({
|
||||
argumentsToSubmit: (c, e) => {
|
||||
const command =
|
||||
'command' in e.data ? e.data.command : c.selectedCommand
|
||||
if (!command?.args) return {}
|
||||
const args: { [x: string]: unknown } = {}
|
||||
for (const [argName, arg] of Object.entries(command.args)) {
|
||||
args[argName] =
|
||||
e.data.argDefaultValues && argName in e.data.argDefaultValues
|
||||
? e.data.argDefaultValues[argName]
|
||||
: arg.skip && 'defaultValue' in arg
|
||||
? arg.defaultValue
|
||||
: undefined
|
||||
}
|
||||
return args
|
||||
},
|
||||
}),
|
||||
},
|
||||
guards: {
|
||||
'Command needs review': (context, _) =>
|
||||
context.selectedCommand?.needsReview || false,
|
||||
},
|
||||
services: {
|
||||
'Validate argument': (context, event) => {
|
||||
if (event.type !== 'Submit argument') return Promise.reject()
|
||||
return new Promise((resolve, reject) => {
|
||||
// TODO: figure out if we should validate argument data here or in the form itself,
|
||||
// and if we should support people configuring a argument's validation function
|
||||
|
||||
resolve(event.data)
|
||||
})
|
||||
// TODO: use an XState service to continue onto review step
|
||||
// if all arguments are skippable and contain values.
|
||||
return undefined
|
||||
},
|
||||
'Validate all arguments': (context, _) => {
|
||||
}),
|
||||
'Clear current argument': assign({
|
||||
currentArgument: undefined,
|
||||
}),
|
||||
'Remove argument': assign({
|
||||
argumentsToSubmit: ({ context, event }) => {
|
||||
if (event.type !== 'Remove argument') return context.argumentsToSubmit
|
||||
const argToRemove = Object.values(event.data)[0]
|
||||
// Extract all but the argument to remove and return it
|
||||
const { [argToRemove.name]: _, ...rest } = context.argumentsToSubmit
|
||||
return rest
|
||||
},
|
||||
}),
|
||||
'Set current argument': assign({
|
||||
currentArgument: ({ context, event }) => {
|
||||
switch (event.type) {
|
||||
case 'Edit argument':
|
||||
return event.data.arg
|
||||
case 'Change current argument':
|
||||
return Object.values(event.data)[0]
|
||||
default:
|
||||
return context.currentArgument
|
||||
}
|
||||
},
|
||||
}),
|
||||
'Clear argument data': assign({
|
||||
selectedCommand: undefined,
|
||||
currentArgument: undefined,
|
||||
argumentsToSubmit: {},
|
||||
}),
|
||||
'Set selected command': assign({
|
||||
selectedCommand: ({ context, event }) =>
|
||||
event.type === 'Select command'
|
||||
? event.data.command
|
||||
: context.selectedCommand,
|
||||
}),
|
||||
'Find and select command': assign({
|
||||
selectedCommand: ({ context, event }) => {
|
||||
if (event.type !== 'Find and select command')
|
||||
return context.selectedCommand
|
||||
const found = context.commands.find(
|
||||
(cmd) =>
|
||||
cmd.name === event.data.name && cmd.groupId === event.data.groupId
|
||||
)
|
||||
|
||||
return !!found ? found : context.selectedCommand
|
||||
},
|
||||
}),
|
||||
'Initialize arguments to submit': assign({
|
||||
argumentsToSubmit: ({ context, event }) => {
|
||||
if (
|
||||
event.type !== 'Select command' &&
|
||||
event.type !== 'Find and select command'
|
||||
)
|
||||
return {}
|
||||
const command =
|
||||
'data' in event && 'command' in event.data
|
||||
? event.data.command
|
||||
: context.selectedCommand
|
||||
if (!command?.args) return {}
|
||||
const args: { [x: string]: unknown } = {}
|
||||
for (const [argName, arg] of Object.entries(command.args)) {
|
||||
args[argName] =
|
||||
event.data.argDefaultValues &&
|
||||
argName in event.data.argDefaultValues
|
||||
? event.data.argDefaultValues[argName]
|
||||
: arg.skip && 'defaultValue' in arg
|
||||
? arg.defaultValue
|
||||
: undefined
|
||||
}
|
||||
return args
|
||||
},
|
||||
}),
|
||||
},
|
||||
guards: {
|
||||
'Command needs review': ({ context }) =>
|
||||
context.selectedCommand?.needsReview || false,
|
||||
'Command has no arguments': () => false,
|
||||
'All arguments are skippable': () => false,
|
||||
},
|
||||
actors: {
|
||||
'Validate argument': fromPromise(({ input }) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// TODO: figure out if we should validate argument data here or in the form itself,
|
||||
// and if we should support people configuring a argument's validation function
|
||||
|
||||
resolve(input)
|
||||
})
|
||||
}),
|
||||
'Validate all arguments': fromPromise(
|
||||
({ input }: { input: CommandBarContext }) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
for (const [argName, argConfig] of Object.entries(
|
||||
context.selectedCommand!.args!
|
||||
input.selectedCommand!.args!
|
||||
)) {
|
||||
let arg = context.argumentsToSubmit[argName]
|
||||
let argValue = typeof arg === 'function' ? arg(context) : arg
|
||||
let arg = input.argumentsToSubmit[argName]
|
||||
let argValue = typeof arg === 'function' ? arg(input) : arg
|
||||
|
||||
try {
|
||||
const isRequired =
|
||||
typeof argConfig.required === 'function'
|
||||
? argConfig.required(context)
|
||||
? argConfig.required(input)
|
||||
: argConfig.required
|
||||
|
||||
const resolvedDefaultValue =
|
||||
'defaultValue' in argConfig
|
||||
? typeof argConfig.defaultValue === 'function'
|
||||
? argConfig.defaultValue(context)
|
||||
? argConfig.defaultValue(input)
|
||||
: argConfig.defaultValue
|
||||
: undefined
|
||||
|
||||
@ -461,7 +282,7 @@ export const commandBarMachine = createMachine(
|
||||
!(
|
||||
typeof argConfig.options === 'function'
|
||||
? argConfig.options(
|
||||
context,
|
||||
input,
|
||||
argConfig.machineActor.getSnapshot().context
|
||||
)
|
||||
: argConfig.options
|
||||
@ -502,13 +323,214 @@ export const commandBarMachine = createMachine(
|
||||
}
|
||||
}
|
||||
|
||||
return resolve(context.argumentsToSubmit)
|
||||
return resolve(input.argumentsToSubmit)
|
||||
})
|
||||
}
|
||||
),
|
||||
},
|
||||
}).createMachine({
|
||||
/** @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 */
|
||||
context: {
|
||||
commands: [],
|
||||
selectedCommand: undefined,
|
||||
currentArgument: undefined,
|
||||
selectionRanges: {
|
||||
otherSelections: [],
|
||||
codeBasedSelections: [],
|
||||
},
|
||||
argumentsToSubmit: {},
|
||||
},
|
||||
id: 'Command Bar',
|
||||
initial: 'Closed',
|
||||
states: {
|
||||
Closed: {
|
||||
on: {
|
||||
Open: {
|
||||
target: 'Selecting command',
|
||||
},
|
||||
|
||||
'Find and select command': {
|
||||
target: 'Command selected',
|
||||
actions: [
|
||||
'Find and select command',
|
||||
'Initialize arguments to submit',
|
||||
],
|
||||
},
|
||||
|
||||
'Add commands': {
|
||||
target: 'Closed',
|
||||
|
||||
actions: [
|
||||
assign({
|
||||
commands: ({ context, event }) =>
|
||||
[...context.commands, ...event.data.commands].sort(
|
||||
sortCommands
|
||||
),
|
||||
}),
|
||||
],
|
||||
|
||||
reenter: false,
|
||||
},
|
||||
|
||||
'Remove commands': {
|
||||
target: 'Closed',
|
||||
|
||||
actions: [
|
||||
assign({
|
||||
commands: ({ context, event }) =>
|
||||
context.commands.filter(
|
||||
(c) =>
|
||||
!event.data.commands.some(
|
||||
(c2) => c2.name === c.name && c2.groupId === c.groupId
|
||||
)
|
||||
),
|
||||
}),
|
||||
],
|
||||
|
||||
reenter: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
delays: {},
|
||||
}
|
||||
)
|
||||
|
||||
'Selecting command': {
|
||||
on: {
|
||||
'Select command': {
|
||||
target: 'Command selected',
|
||||
actions: ['Set selected command', 'Initialize arguments to submit'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
'Command selected': {
|
||||
always: [
|
||||
{
|
||||
target: 'Closed',
|
||||
guard: 'Command has no arguments',
|
||||
actions: ['Execute command'],
|
||||
},
|
||||
{
|
||||
target: 'Checking Arguments',
|
||||
guard: 'All arguments are skippable',
|
||||
},
|
||||
{
|
||||
target: 'Gathering arguments',
|
||||
actions: ['Set current argument to first non-skippable'],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
'Gathering arguments': {
|
||||
states: {
|
||||
'Awaiting input': {
|
||||
on: {
|
||||
'Submit argument': {
|
||||
target: 'Validating',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Validating: {
|
||||
invoke: {
|
||||
src: 'Validate argument',
|
||||
id: 'validateSingleArgument',
|
||||
input: ({ event }) => {
|
||||
if (event.type !== 'Submit argument') return {}
|
||||
return event.data
|
||||
},
|
||||
onDone: {
|
||||
target: '#Command Bar.Checking Arguments',
|
||||
actions: ['enqueueValidArgsToSubmit'],
|
||||
},
|
||||
onError: [
|
||||
{
|
||||
target: 'Awaiting input',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
initial: 'Awaiting input',
|
||||
|
||||
on: {
|
||||
'Change current argument': {
|
||||
target: 'Gathering arguments',
|
||||
internal: true,
|
||||
actions: ['Set current argument'],
|
||||
},
|
||||
|
||||
'Deselect command': {
|
||||
target: 'Selecting command',
|
||||
actions: [
|
||||
assign({
|
||||
selectedCommand: (_c, _e) => undefined,
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Review: {
|
||||
entry: ['Clear current argument'],
|
||||
on: {
|
||||
'Submit command': {
|
||||
target: 'Closed',
|
||||
actions: ['Execute command'],
|
||||
},
|
||||
|
||||
'Add argument': {
|
||||
target: 'Gathering arguments',
|
||||
actions: ['Set current argument'],
|
||||
},
|
||||
|
||||
'Remove argument': {
|
||||
target: 'Review',
|
||||
actions: ['Remove argument'],
|
||||
},
|
||||
|
||||
'Edit argument': {
|
||||
target: 'Gathering arguments',
|
||||
actions: ['Set current argument'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
'Checking Arguments': {
|
||||
invoke: {
|
||||
src: 'Validate all arguments',
|
||||
id: 'validateArguments',
|
||||
input: ({ context }) => context,
|
||||
onDone: [
|
||||
{
|
||||
target: 'Review',
|
||||
guard: 'Command needs review',
|
||||
},
|
||||
{
|
||||
target: 'Closed',
|
||||
actions: 'Execute command',
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
target: 'Gathering arguments',
|
||||
actions: ['Set current argument to first non-skippable'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
on: {
|
||||
Close: {
|
||||
target: '.Closed',
|
||||
},
|
||||
|
||||
Clear: {
|
||||
target: '#Command Bar',
|
||||
reenter: false,
|
||||
actions: ['Clear argument data'],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
function sortCommands(a: Command, b: Command) {
|
||||
if (b.groupId === 'auth' && !(a.groupId === 'auth')) return -2
|
||||
|
Reference in New Issue
Block a user