Migration interaction map machinery to XState V5
This commit is contained in:
@ -1,4 +1,4 @@
|
|||||||
import { useMachine } from '@xstate/react'
|
import { createActorContext, useMachine } from '@xstate/react'
|
||||||
import { INTERACTION_MAP_SEPARATOR } from 'lib/constants'
|
import { INTERACTION_MAP_SEPARATOR } from 'lib/constants'
|
||||||
import {
|
import {
|
||||||
isModifierKey,
|
isModifierKey,
|
||||||
@ -30,161 +30,28 @@ type MachineContext<T extends AnyStateMachine> = {
|
|||||||
send: Prop<InterpreterFrom<T>, 'send'>
|
send: Prop<InterpreterFrom<T>, 'send'>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const InteractionMapMachineContext = createContext(
|
export const InteractionMapMachineContext = createActorContext(
|
||||||
{} as MachineContext<typeof interactionMapMachine>
|
interactionMapMachine
|
||||||
)
|
)
|
||||||
|
|
||||||
export function InteractionMapMachineProvider({
|
export const InteractionMapMachineProvider = ({
|
||||||
children,
|
children,
|
||||||
}: React.PropsWithChildren<{}>) {
|
}: {
|
||||||
const [state, send] = useMachine(interactionMapMachine, {
|
children: React.ReactNode
|
||||||
logger: (msg) => {
|
}) => {
|
||||||
console.log(msg)
|
return (
|
||||||
},
|
<InteractionMapMachineContext.Provider>
|
||||||
actions: {
|
<InteractionMapProviderInner>{children}</InteractionMapProviderInner>
|
||||||
'Add last interaction to sequence': assign({
|
</InteractionMapMachineContext.Provider>
|
||||||
currentSequence: (context, event) => {
|
)
|
||||||
const newSequence = event.data
|
}
|
||||||
? context.currentSequence
|
|
||||||
? context.currentSequence.concat(' ', event.data)
|
|
||||||
: event.data
|
|
||||||
: context.currentSequence
|
|
||||||
|
|
||||||
console.log('newSequence', newSequence)
|
function InteractionMapProviderInner({
|
||||||
return newSequence
|
children,
|
||||||
},
|
}: {
|
||||||
}),
|
children: React.ReactNode
|
||||||
'Clear sequence': assign({
|
}) {
|
||||||
currentSequence: () => {
|
const interactionMap = InteractionMapMachineContext.useActorRef()
|
||||||
console.log('clearing sequence')
|
|
||||||
return ''
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
'Add to interactionMap': assign({
|
|
||||||
interactionMap: (context, event) => {
|
|
||||||
const newInteractions: Record<string, InteractionMapItem> =
|
|
||||||
Object.fromEntries(
|
|
||||||
Object.entries(event.data.items).map(([name, item]) => [
|
|
||||||
name,
|
|
||||||
{
|
|
||||||
...item,
|
|
||||||
sequence: normalizeSequence(item.sequence),
|
|
||||||
},
|
|
||||||
])
|
|
||||||
)
|
|
||||||
|
|
||||||
const newInteractionMap = {
|
|
||||||
...context.interactionMap,
|
|
||||||
[event.data.ownerId]: {
|
|
||||||
...context.interactionMap[event.data.ownerId],
|
|
||||||
...newInteractions,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.log('newInteractionMap', newInteractionMap)
|
|
||||||
return newInteractionMap
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
'Remove from interactionMap': assign({
|
|
||||||
interactionMap: (context, event) => {
|
|
||||||
const newInteractionMap = { ...context.interactionMap }
|
|
||||||
if (event.data instanceof Array) {
|
|
||||||
event.data.forEach((key) => {
|
|
||||||
const [ownerId, itemName] = key.split(INTERACTION_MAP_SEPARATOR)
|
|
||||||
delete newInteractionMap[ownerId][itemName]
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
delete newInteractionMap[event.data]
|
|
||||||
}
|
|
||||||
return newInteractionMap
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
'Merge into overrides': assign({
|
|
||||||
overrides: (context, event) => {
|
|
||||||
return {
|
|
||||||
...context.overrides,
|
|
||||||
...event.data,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
'Persist keybinding overrides': (context) => {
|
|
||||||
console.log('Persisting keybinding overrides', context.overrides)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
services: {
|
|
||||||
'Resolve hotkey by prefix': (context, event) => {
|
|
||||||
const resolvedInteraction = resolveInteractionEvent(event.data)
|
|
||||||
|
|
||||||
// if the key is already a modifier key, skip everything else and reject
|
|
||||||
if (resolvedInteraction.isModifier) {
|
|
||||||
// We return an empty string so that we don't clear the currentSequence
|
|
||||||
return Promise.reject('')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find all the sequences that start with the current sequence
|
|
||||||
const searchString =
|
|
||||||
(context.currentSequence ? context.currentSequence + ' ' : '') +
|
|
||||||
resolvedInteraction.asString
|
|
||||||
const sortedInteractions = getSortedInteractionMapSequences(context)
|
|
||||||
|
|
||||||
const matches = sortedInteractions.filter(([sequence]) =>
|
|
||||||
sequence.startsWith(searchString)
|
|
||||||
)
|
|
||||||
|
|
||||||
console.log('matches', {
|
|
||||||
matches,
|
|
||||||
sortedInteractions,
|
|
||||||
searchString,
|
|
||||||
overrides: context.overrides,
|
|
||||||
})
|
|
||||||
|
|
||||||
// If we have no matches, reject the promise
|
|
||||||
if (matches.length === 0) {
|
|
||||||
return Promise.reject()
|
|
||||||
}
|
|
||||||
|
|
||||||
const exactMatches = matches.filter(
|
|
||||||
([sequence]) => sequence === searchString
|
|
||||||
)
|
|
||||||
console.log('exactMatches', exactMatches)
|
|
||||||
if (!exactMatches.length) {
|
|
||||||
// We have a prefix match.
|
|
||||||
// Reject the promise and return the step
|
|
||||||
// so we can add it to currentSequence
|
|
||||||
return Promise.reject(resolvedInteraction.asString)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve to just one exact match
|
|
||||||
const availableExactMatches = exactMatches.filter(
|
|
||||||
([_, item]) => !item.guard || item.guard(event.data)
|
|
||||||
)
|
|
||||||
|
|
||||||
console.log('availableExactMatches', availableExactMatches)
|
|
||||||
if (availableExactMatches.length === 0) {
|
|
||||||
return Promise.reject()
|
|
||||||
} else {
|
|
||||||
// return the last-added, available exact match
|
|
||||||
return Promise.resolve(
|
|
||||||
availableExactMatches[availableExactMatches.length - 1][1]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'Execute keymap action': async (_context, event) => {
|
|
||||||
try {
|
|
||||||
console.log('Executing action', event.data)
|
|
||||||
event.data.action()
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
toast.error('There was an error executing the action.')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
guards: {
|
|
||||||
'There are prefix matches': (_context, event) => {
|
|
||||||
return event.data !== undefined
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// Setting up global event listeners
|
// Setting up global event listeners
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -204,7 +71,7 @@ export function InteractionMapMachineProvider({
|
|||||||
'true'
|
'true'
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
send({ type: 'Fire event', data: event })
|
interactionMap.send({ type: 'Fire event', data: { event } })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,9 +84,5 @@ export function InteractionMapMachineProvider({
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return children
|
||||||
<InteractionMapMachineContext.Provider value={{ state, send }}>
|
|
||||||
{children}
|
|
||||||
</InteractionMapMachineContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
import { InteractionMapMachineContext } from 'components/InteractionMapMachineProvider'
|
import { InteractionMapMachineContext } from 'components/InteractionMapMachineProvider'
|
||||||
import { useContext } from 'react'
|
|
||||||
|
|
||||||
export const useInteractionMapContext = () => {
|
export const useInteractionMapContext = () => {
|
||||||
return useContext(InteractionMapMachineContext)
|
const interactionMapActor = InteractionMapMachineContext.useActorRef()
|
||||||
|
const interactionMapState = InteractionMapMachineContext.useSelector((state) => state)
|
||||||
|
return {
|
||||||
|
actor: interactionMapActor,
|
||||||
|
send: interactionMapActor.send,
|
||||||
|
state: interactionMapState,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,8 +69,9 @@ type ResolveKeymapEvent = {
|
|||||||
asString: string
|
asString: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type InteractionEvent = MouseEvent | KeyboardEvent
|
||||||
export function resolveInteractionEvent(
|
export function resolveInteractionEvent(
|
||||||
event: MouseEvent | KeyboardEvent
|
event: InteractionEvent
|
||||||
): ResolveKeymapEvent {
|
): ResolveKeymapEvent {
|
||||||
// First, determine if this is a key or mouse event
|
// First, determine if this is a key or mouse event
|
||||||
const action =
|
const action =
|
||||||
|
|||||||
@ -1,7 +1,13 @@
|
|||||||
import { INTERACTION_MAP_SEPARATOR } from 'lib/constants'
|
import { INTERACTION_MAP_SEPARATOR } from 'lib/constants'
|
||||||
import { mapKey, sortKeys } from 'lib/keyboard'
|
import {
|
||||||
|
InteractionEvent,
|
||||||
|
mapKey,
|
||||||
|
resolveInteractionEvent,
|
||||||
|
sortKeys,
|
||||||
|
} from 'lib/keyboard'
|
||||||
import { interactionMapCategories } from 'lib/settings/initialKeybindings'
|
import { interactionMapCategories } from 'lib/settings/initialKeybindings'
|
||||||
import { ContextFrom, createMachine } from 'xstate'
|
import toast from 'react-hot-toast'
|
||||||
|
import { assign, ContextFrom, createMachine, fromPromise, setup } from 'xstate'
|
||||||
|
|
||||||
export type MouseButtonName = `${'Left' | 'Middle' | 'Right'}Button`
|
export type MouseButtonName = `${'Left' | 'Middle' | 'Right'}Button`
|
||||||
|
|
||||||
@ -25,43 +31,232 @@ export type InteractionMap = {
|
|||||||
>
|
>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const interactionMapMachine = createMachine({
|
export type InteractionMapContext = {
|
||||||
|
interactionMap: InteractionMap
|
||||||
|
overrides: Record<string, string>
|
||||||
|
currentSequence: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InteractionMapEvents =
|
||||||
|
| {
|
||||||
|
type: 'Add to interaction map'
|
||||||
|
data: {
|
||||||
|
ownerId: string
|
||||||
|
items: {
|
||||||
|
[key: string]: InteractionMapItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'Remove from interaction map'
|
||||||
|
data: string | string[]
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'Update overrides'
|
||||||
|
data: Record<string, string>
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'Fire event'
|
||||||
|
data: {
|
||||||
|
event: KeyboardEvent | MouseEvent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'Add last interaction to sequence'
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'Clear sequence'
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'xstate.done.actor.resolveHotkeyByPrefix'
|
||||||
|
output: InteractionMapItem
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'xstate.error.actor.resolveHotkeyByPrefix'
|
||||||
|
data: string | undefined
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'xstate.done.actor.executeKeymapAction'
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'xstate.error.actor.executeKeymapAction'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const interactionMapMachine = setup({
|
||||||
/** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMAnAhgY3QEsB7VAAgFkcAHMgQQOKwGI6IIz1izCNt8ipMgFsaAbQAMAXUShqxWIUGpZIAB6IAzAE4AbADoATBIAcAdk0AWCQFYJmiRMMAaEAE9EAWgCMN7-t0JbRNdS0tDM29dbQBfGNc0TFwCEnIqWgYuFgAlMGFiADcwMgAzLGJhHj5k5RFxaVV5RWVVDQRow30TU29rbrsrb1cPBB8-AKDtXytg7R0TOITqgVTKGnpGLH0AGUJYTFReKFKmKqSV0mYAMUIsYrAijEkZJBAmpVTWxDmbfTMI7wmEyWYGGbThYZaUJGKw2SxwmwWSJmRYgRL8FJCdIbLL6XKwYgAGyKZAAFsR0ABrMBuZgQUhgfS8ArEan6O4E4lgAASFOpbgAQm4AAp3EqENTPRoKD6kL4IbzeTSaLreMyWXS6RWaXRmOaQhB2Aw2KyGawOEyInWo9E1VbYzJMPFwIkk8lUmnMbDlLbUQk4dAlJjCdkurm8j2CkViiVS17vFqvNreCSArq6Yw6iLaRyGGwGqK-bRzUKGbyGM0mYuGG3LTFpdaOrb413Fd38r1YH36P0BoNYEMc1sR-lC0VgcWS7wvOQyxOgZNOfxWeHRDPzMsGgFdPSObrKsxwywo+Jouu1B2bfQAUTUYDwAFdMGR+aJaA8wBg6QymagWWywDvR9MAAaRpN9MlSONZ2aT4k0QMIzH0YJvGCGYbGCVNdANTQ4X0DVUzsCswW6WJT1tC4GwyK9b3vJ9ilfdYPy-b0nV7QNg30QC6NA8CaEg0hoLeOc4IXBCQQCGxjDVawKz1eEt0tdMHDMboU20M0zBPJZznrNZqKyZgAFVqAgANikKb1CAgOAhITUT1EQbUVRBA9gRsEw1SGdwvHLExkLLCR1yCzVyxPU9UGIGz4FeCi9MvLJpVguV4NGbyRl8fRyx1XCTDBQxNH+MJa10i9GyvXZ9k-I4TiwM4MXnYTkpUVL-iQwwTFwzROvhM1bC3DTVSBXQHFsHVtBsEqGvtcrcRbLkyT5GkktlFqxIVY9fiCzyzXUk1DGwnyEGVfxyxzCxAhsXROu8Ka7SxWanVo4CGL499HnQFbGraY9FJVUJgQ1cJUP+CRLDiOIgA */
|
/** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMAnAhgY3QEsB7VAAgFkcAHMgQQOKwGI6IIz1izCNt8ipMgFsaAbQAMAXUShqxWIUGpZIAB6IAzAE4AbADoATBIAcAdk0AWCQFYJmiRMMAaEAE9EAWgCMN7-t0JbRNdS0tDM29dbQBfGNc0TFwCEnIqWgYuFgAlMGFiADcwMgAzLGJhHj5k5RFxaVV5RWVVDQRow30TU29rbrsrb1cPBB8-AKDtXytg7R0TOITqgVTKGnpGLH0AGUJYTFReKFKmKqSV0mYAMUIsYrAijEkZJBAmpVTWxDmbfTMI7wmEyWYGGbThYZaUJGKw2SxwmwWSJmRYgRL8FJCdIbLL6XKwYgAGyKZAAFsR0ABrMBuZgQUhgfS8ArEan6O4E4lgAASFOpbgAQm4AAp3EqENTPRoKD6kL4IbzeTSaLreMyWXS6RWaXRmOaQhB2Aw2KyGawOEyInWo9E1VbYzJMPFwIkk8lUmnMbDlLbUQk4dAlJjCdkurm8j2CkViiVS17vFqvNreCSArq6Yw6iLaRyGGwGqK-bRzUKGbyGM0mYuGG3LTFpdaOrb413Fd38r1YH36P0BoNYEMc1sR-lC0VgcWS7wvOQyxOgZNOfxWeHRDPzMsGgFdPSObrKsxwywo+Jouu1B2bfQAUTUYDwAFdMGR+aJaA8wBg6QymagWWywDvR9MAAaRpN9MlSONZ2aT4k0QMIzH0YJvGCGYbGCVNdANTQ4X0DVUzsCswW6WJT1tC4GwyK9b3vJ9ilfdYPy-b0nV7QNg30QC6NA8CaEg0hoLeOc4IXBCQQCGxjDVawKz1eEt0tdMHDMboU20M0zBPJZznrNZqKyZgAFVqAgANikKb1CAgOAhITUT1EQbUVRBA9gRsEw1SGdwvHLExkLLCR1yCzVyxPU9UGIGz4FeCi9MvLJpVguV4NGbyRl8fRyx1XCTDBQxNH+MJa10i9GyvXZ9k-I4TiwM4MXnYTkpUVL-iQwwTFwzROvhM1bC3DTVSBXQHFsHVtBsEqGvtcrcRbLkyT5GkktlFqxIVY9fiCzyzXUk1DGwnyEGVfxyxzCxAhsXROu8Ka7SxWanVo4CGL499HnQFbGraY9FJVUJgQ1cJUP+CRLDiOIgA */
|
||||||
context: {
|
types: {
|
||||||
interactionMap: {} as InteractionMap,
|
context: {} as InteractionMapContext,
|
||||||
overrides: {} as Record<string, string>,
|
events: {} as InteractionMapEvents,
|
||||||
currentSequence: '' as string,
|
|
||||||
},
|
},
|
||||||
predictableActionArguments: true,
|
actions: {
|
||||||
preserveActionOrder: true,
|
'Add last interaction to sequence': assign({
|
||||||
tsTypes: {} as import('./interactionMapMachine.typegen').Typegen0,
|
currentSequence: ({ context, event }) => {
|
||||||
schema: {
|
if (event.type !== 'xstate.error.actor.resolveHotkeyByPrefix') {
|
||||||
events: {} as
|
return context.currentSequence
|
||||||
| {
|
|
||||||
type: 'Add to interaction map'
|
|
||||||
data: {
|
|
||||||
ownerId: string
|
|
||||||
items: {
|
|
||||||
[key: string]: InteractionMapItem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
| {
|
const newSequence = event.data
|
||||||
type: 'Remove from interaction map'
|
? context.currentSequence
|
||||||
data: string | string[]
|
? context.currentSequence.concat(' ', event.data)
|
||||||
|
: event.data
|
||||||
|
: context.currentSequence
|
||||||
|
|
||||||
|
console.log('newSequence', newSequence)
|
||||||
|
return newSequence
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
'Clear sequence': assign({
|
||||||
|
currentSequence: () => {
|
||||||
|
console.log('clearing sequence')
|
||||||
|
return ''
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
'Add to interactionMap': assign({
|
||||||
|
interactionMap: ({ context, event }) => {
|
||||||
|
if (event.type !== 'Add to interaction map') {
|
||||||
|
return context.interactionMap
|
||||||
}
|
}
|
||||||
| { type: 'Fire event'; data: MouseEvent | KeyboardEvent }
|
const newInteractions: Record<string, InteractionMapItem> =
|
||||||
| { type: 'Execute keymap action'; data: InteractionMapItem }
|
Object.fromEntries(
|
||||||
| { type: 'Update prefix matrix' }
|
Object.entries(event.data.items).map(([name, item]) => [
|
||||||
| { type: 'Add last interaction to sequence' }
|
name,
|
||||||
| { type: 'Clear sequence' }
|
{
|
||||||
| { type: 'Update overrides'; data: { [key: string]: string } }
|
...item,
|
||||||
| { type: 'Resolve hotkey by prefix'; data: MouseEvent | KeyboardEvent }
|
sequence: normalizeSequence(item.sequence),
|
||||||
| { type: 'done.invoke.resolveHotkeyByPrefix'; data: InteractionMapItem }
|
},
|
||||||
| {
|
])
|
||||||
type: 'error.platform.resolveHotkeyByPrefix'
|
)
|
||||||
data: string | undefined
|
|
||||||
},
|
const newInteractionMap = {
|
||||||
|
...context.interactionMap,
|
||||||
|
[event.data.ownerId]: {
|
||||||
|
...context.interactionMap[event.data.ownerId],
|
||||||
|
...newInteractions,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log('newInteractionMap', newInteractionMap)
|
||||||
|
return newInteractionMap
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
'Remove from interactionMap': assign({
|
||||||
|
interactionMap: ({ context, event }) => {
|
||||||
|
if (event.type !== 'Remove from interaction map') {
|
||||||
|
return context.interactionMap
|
||||||
|
}
|
||||||
|
const newInteractionMap = { ...context.interactionMap }
|
||||||
|
if (event.data instanceof Array) {
|
||||||
|
event.data.forEach((key) => {
|
||||||
|
const [ownerId, itemName] = key.split(INTERACTION_MAP_SEPARATOR)
|
||||||
|
delete newInteractionMap[ownerId][itemName]
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
delete newInteractionMap[event.data]
|
||||||
|
}
|
||||||
|
return newInteractionMap
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
'Merge into overrides': assign({
|
||||||
|
overrides: ({ context, event }) => {
|
||||||
|
if (event.type !== 'Update overrides') {
|
||||||
|
return context.overrides
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...context.overrides,
|
||||||
|
...event.data,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
'Persist keybinding overrides': ({context}) => {
|
||||||
|
console.log('Persisting keybinding overrides', context.overrides)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actors: {
|
||||||
|
resolveHotkeyByPrefix: fromPromise(
|
||||||
|
({
|
||||||
|
input: { context, data },
|
||||||
|
}: {
|
||||||
|
input: { context: InteractionMapContext; data: InteractionEvent }
|
||||||
|
}) => {
|
||||||
|
return new Promise<InteractionMapItem>((resolve, reject) => {
|
||||||
|
const resolvedInteraction = resolveInteractionEvent(data)
|
||||||
|
|
||||||
|
// if the key is already a modifier key, skip everything else and reject
|
||||||
|
if (resolvedInteraction.isModifier) {
|
||||||
|
// We return an empty string so that we don't clear the currentSequence
|
||||||
|
reject('')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all the sequences that start with the current sequence
|
||||||
|
const searchString =
|
||||||
|
(context.currentSequence ? context.currentSequence + ' ' : '') +
|
||||||
|
resolvedInteraction.asString
|
||||||
|
const sortedInteractions = getSortedInteractionMapSequences(context)
|
||||||
|
|
||||||
|
const matches = sortedInteractions.filter(([sequence]) =>
|
||||||
|
sequence.startsWith(searchString)
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log('matches', {
|
||||||
|
matches,
|
||||||
|
sortedInteractions,
|
||||||
|
searchString,
|
||||||
|
overrides: context.overrides,
|
||||||
|
})
|
||||||
|
|
||||||
|
// If we have no matches, reject the promise
|
||||||
|
if (matches.length === 0) {
|
||||||
|
reject()
|
||||||
|
}
|
||||||
|
|
||||||
|
const exactMatches = matches.filter(
|
||||||
|
([sequence]) => sequence === searchString
|
||||||
|
)
|
||||||
|
console.log('exactMatches', exactMatches)
|
||||||
|
if (!exactMatches.length) {
|
||||||
|
// We have a prefix match.
|
||||||
|
// Reject the promise and return the step
|
||||||
|
// so we can add it to currentSequence
|
||||||
|
reject(resolvedInteraction.asString)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve to just one exact match
|
||||||
|
const availableExactMatches = exactMatches.filter(
|
||||||
|
([_, item]) => !item.guard || item.guard(data)
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log('availableExactMatches', availableExactMatches)
|
||||||
|
if (availableExactMatches.length === 0) {
|
||||||
|
reject()
|
||||||
|
} else {
|
||||||
|
// return the last-added, available exact match
|
||||||
|
resolve(
|
||||||
|
availableExactMatches[availableExactMatches.length - 1][1]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
),
|
||||||
|
'Execute keymap action': fromPromise(async ({ input }: { input: InteractionMapItem}) => {
|
||||||
|
try {
|
||||||
|
console.log('Executing action', input)
|
||||||
|
input.action()
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
toast.error('There was an error executing the action.')
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
guards: {
|
||||||
|
'There are prefix matches': ({ event }) => {
|
||||||
|
return event.type === 'xstate.error.actor.resolveHotkeyByPrefix' && event.data !== undefined
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).createMachine({
|
||||||
|
/** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMAnAhgY3QEsB7VAAgFkcAHMgQQOKwGI6IIz1izCNt8ipMgFsaAbQAMAXUShqxWIUGpZIAB6IAHBICsAOgCcANgMBGTUc2nrAFgDMOgDQgAnogBM7g3rtGbAdjMjdx1TIzsDHQBfKOc0TFwCEnIqWgYuFgAlMGFiADcwMgAzLGJhHj5E5RFxaVV5RWVVDQR-TXc9U3d-HR1wmy6zG2c3BFM7Gz0JdyMJCM0Iu08jGLjKgWTKGnpGFgBVaggcTDJ87CxCCDhJGSQQBqVk5sRAzT0bCU0DfzmdOwlzJoRoguqsQPF+EkhKkdhk9AAZQiwTCoXhQYpMCoJDakZgAMUIWEKYAKGBu9QUj1IzwQBgcen83RC-n8NiM7IGwIQvUmZncEmM4T8tjBEKqmxh6SYemysGIABsCmQABbEdAAazALmYEFIYD0vDyxE1eiJcsVYAAEmrNS4AEIuAAKRKKhDU5LuDyadxa1jsdj0Vh6fwiwbCXJM-j07msbXcmh0rJsbNF6yhKW2UqwMrgCqVqo1WuY52l1HlxyKTGEptzFuthftTpdbo9ckp3tALQMEiMhhmOm6wRMXScrkQRlMEgZ9gkn2s3wiplT2PTWzSuxz5vzNqLJezZYrVZrW6tO8bzrArvdplubcaTx9IOmph8yYnbJ02h7pi5bI6iYcRNzH9KwbGXSFqklDcAFE1DAPAAFcTltURaBJMAMB1PUDVQI0TTAODEMwABpLVUPSZJW3udsH07RBkyjAwrG+b4BlCdxhjHbkJj0HRvhMPpIgsOxwPFaFMxgwikMKFDtnQzC9z0A90ErLBqwI+DpNIlxyPTKivVo9R6ICQxmMCVlTHYzjRhsTQoyFfw-G+PivGiMFUGIK54DuMUcQzdcMgpe9qUfBAAFofy4uxrAZX4wiWf1EtEvy11haVEWRDC0QxLAsQgwyDJCujWnpSyegBUxjAWUcbLpHw2gnTQbD6PojDctYV0giS4VlPNCgLW0gqpFRQvGMxA0nSzZiMRy-ncLk5ujdw7HaAVQnGbpktXKC4VgzTkLIuTSXQIaOyMhAAl-VkfBmWc2o+WdTBTGIoiAA */
|
||||||
|
context: {
|
||||||
|
interactionMap: {},
|
||||||
|
overrides: {},
|
||||||
|
currentSequence: '',
|
||||||
},
|
},
|
||||||
id: 'Interaction Map Actor',
|
id: 'Interaction Map Actor',
|
||||||
initial: 'Listening for interaction',
|
initial: 'Listening for interaction',
|
||||||
@ -78,6 +273,19 @@ export const interactionMapMachine = createMachine({
|
|||||||
'Resolve hotkey': {
|
'Resolve hotkey': {
|
||||||
invoke: {
|
invoke: {
|
||||||
id: 'resolveHotkeyByPrefix',
|
id: 'resolveHotkeyByPrefix',
|
||||||
|
input: ({ context, event }) => {
|
||||||
|
if (event.type === 'Fire event') {
|
||||||
|
return {
|
||||||
|
context,
|
||||||
|
data: event.data.event,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
context,
|
||||||
|
data: {} as InteractionEvent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
onDone: {
|
onDone: {
|
||||||
target: 'Execute keymap event',
|
target: 'Execute keymap event',
|
||||||
},
|
},
|
||||||
@ -87,7 +295,7 @@ export const interactionMapMachine = createMachine({
|
|||||||
actions: {
|
actions: {
|
||||||
type: 'Add last interaction to sequence',
|
type: 'Add last interaction to sequence',
|
||||||
},
|
},
|
||||||
cond: {
|
guard: {
|
||||||
type: 'There are prefix matches',
|
type: 'There are prefix matches',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -98,7 +306,7 @@ export const interactionMapMachine = createMachine({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
src: 'Resolve hotkey by prefix',
|
src: 'resolveHotkeyByPrefix',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'Execute keymap event': {
|
'Execute keymap event': {
|
||||||
@ -107,6 +315,12 @@ export const interactionMapMachine = createMachine({
|
|||||||
},
|
},
|
||||||
invoke: {
|
invoke: {
|
||||||
id: 'executeKeymapAction',
|
id: 'executeKeymapAction',
|
||||||
|
input: ({ event }) => {
|
||||||
|
if (event.type !== 'xstate.done.actor.resolveHotkeyByPrefix') {
|
||||||
|
return {} as InteractionMapItem
|
||||||
|
}
|
||||||
|
return event.output
|
||||||
|
},
|
||||||
onDone: {
|
onDone: {
|
||||||
target: 'Listening for interaction',
|
target: 'Listening for interaction',
|
||||||
},
|
},
|
||||||
@ -129,7 +343,7 @@ export const interactionMapMachine = createMachine({
|
|||||||
|
|
||||||
'Remove from interaction map': {
|
'Remove from interaction map': {
|
||||||
target: '#Interaction Map Actor',
|
target: '#Interaction Map Actor',
|
||||||
internal: true,
|
reenter: false,
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'Remove from interactionMap',
|
type: 'Remove from interactionMap',
|
||||||
@ -139,7 +353,7 @@ export const interactionMapMachine = createMachine({
|
|||||||
|
|
||||||
'Update overrides': {
|
'Update overrides': {
|
||||||
target: '#Interaction Map Actor',
|
target: '#Interaction Map Actor',
|
||||||
internal: true,
|
reenter: false,
|
||||||
actions: ['Merge into overrides', 'Persist keybinding overrides'],
|
actions: ['Merge into overrides', 'Persist keybinding overrides'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user