import { AnyStateMachine, ContextFrom, EventFrom, InterpreterFrom, StateFrom, } from 'xstate' import { isTauri } from './isTauri' import { Command, CommandArgument, CommandArgumentConfig, CommandConfig, CommandSetConfig, CommandSetSchema, } from './commandTypes' interface CreateMachineCommandProps< T extends AnyStateMachine, S extends CommandSetSchema > { type: EventFrom['type'] groupId: T['id'] state: StateFrom send: Function actor: InterpreterFrom commandBarConfig?: CommandSetConfig onCancel?: () => void } // Creates a command with subcommands, ready for use in the CommandBar component, // from a more terse Command Bar Meta definition. export function createMachineCommand< T extends AnyStateMachine, S extends CommandSetSchema >({ groupId, type, state, send, actor, commandBarConfig, onCancel, }: CreateMachineCommandProps): Command< T, typeof type, S[typeof type] > | null { const commandConfig = commandBarConfig && commandBarConfig[type] if (!commandConfig) return null // Hide commands based on platform by returning `null` // so the consumer can filter them out if ('hide' in commandConfig) { const { hide } = commandConfig if (hide === 'both') return null else if (hide === 'desktop' && isTauri()) return null else if (hide === 'web' && !isTauri()) return null } const icon = ('icon' in commandConfig && commandConfig.icon) || undefined const command: Command = { name: type, groupId, icon, needsReview: commandConfig.needsReview || false, onSubmit: (data?: S[typeof type]) => { if (data !== undefined && data !== null) { send(type, { data }) } else { send(type) } }, } if (commandConfig.args) { const newArgs = buildCommandArguments(state, commandConfig.args, actor) command.args = newArgs } if (onCancel) { command.onCancel = onCancel } return command } // Takes the args from a CommandConfig and creates // a finalized CommandArgument object for each one, // bundled together into the args for a Command. function buildCommandArguments< T extends AnyStateMachine, S extends CommandSetSchema, CommandName extends EventFrom['type'] = EventFrom['type'] >( state: StateFrom, args: CommandConfig['args'], machineActor: InterpreterFrom ): NonNullable['args']> { const newArgs = {} as NonNullable['args']> for (const arg in args) { const argConfig = args[arg] as CommandArgumentConfig const newArg = buildCommandArgument(argConfig, state.context, machineActor) newArgs[arg] = newArg } return newArgs } export function buildCommandArgument< T extends AnyStateMachine, O extends CommandSetSchema = CommandSetSchema >( arg: CommandArgumentConfig, context: ContextFrom, machineActor: InterpreterFrom ): CommandArgument & { inputType: typeof arg.inputType } { const baseCommandArgument = { description: arg.description, required: arg.required, skip: arg.skip, machineActor, } satisfies Omit, 'inputType'> if (arg.inputType === 'options') { if (!(arg.options || arg.optionsFromContext)) { throw new Error('Options must be provided for options input type') } return { inputType: arg.inputType, ...baseCommandArgument, defaultValue: arg.defaultValueFromContext ? arg.defaultValueFromContext(context) : arg.defaultValue, options: arg.optionsFromContext ? arg.optionsFromContext(context) : arg.options, } satisfies CommandArgument & { inputType: 'options' } } else if (arg.inputType === 'selection') { return { inputType: arg.inputType, ...baseCommandArgument, multiple: arg.multiple, selectionTypes: arg.selectionTypes, } satisfies CommandArgument & { inputType: 'selection' } } else if (arg.inputType === 'kcl') { return { inputType: arg.inputType, defaultValue: arg.defaultValue, ...baseCommandArgument, } satisfies CommandArgument & { inputType: 'kcl' } } else { return { inputType: arg.inputType, defaultValue: arg.defaultValueFromContext ? arg.defaultValueFromContext(context) : arg.defaultValue, ...baseCommandArgument, } } }