Last active
May 2, 2021 02:52
-
-
Save ChrisShank/6943d62f4283b6c027c4cdb2612cdc2f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { inspect } from '@xstate/inspect'; | |
| import { | |
| assign, | |
| createMachine, | |
| interpret, | |
| sendParent, | |
| spawn, | |
| actions, | |
| send, | |
| SpawnedActorRef, | |
| } from 'xstate'; | |
| import { createModel } from 'xstate/lib/model'; | |
| inspect({ iframe: false }); | |
| type Key = 'ArrowRight' | 'ArrowLeft' | 'ArrowUp' | 'ArrowDown' | 'x'; | |
| function createKeyMachine(key: Key, x: number, y: number) { | |
| const keyModel = createModel( | |
| { x, y }, | |
| { | |
| events: { | |
| release: () => ({}), | |
| press: () => ({}), | |
| focus: () => ({}), | |
| unfocus: () => ({}), | |
| move: (dx: number, dy: number) => ({ dx, dy }), | |
| }, | |
| } | |
| ); | |
| const id = `key-${key}`; | |
| const keyMachine = createMachine<typeof keyModel>({ | |
| id, | |
| context: keyModel.initialContext, | |
| type: 'parallel', | |
| states: { | |
| press: { | |
| initial: 'up', | |
| states: { | |
| up: {}, | |
| down: { | |
| on: { release: 'up' }, | |
| }, | |
| }, | |
| on: { | |
| press: { target: '.down', actions: sendParent({ type: 'press', key }) }, | |
| }, | |
| }, | |
| focus: { | |
| initial: 'unfocused', | |
| states: { | |
| unfocused: { | |
| on: { focus: 'focused' }, | |
| }, | |
| focused: { | |
| on: { | |
| unfocus: 'unfocused', | |
| move: { | |
| actions: assign({ | |
| x: (context, event) => context.x + event.dx, | |
| y: (context, event) => context.y + event.dy, | |
| }), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| invoke: { | |
| id: 'keypress', | |
| src: () => (sendBack) => { | |
| // Once the correct key is pressed, only send back an event on every animation frame | |
| const onKeyDown = (e: KeyboardEvent) => { | |
| if (e.key === key) { | |
| sendBack(keyModel.events.press()); | |
| } | |
| }; | |
| window.addEventListener('keydown', onKeyDown); | |
| const onKeyUp = (e: KeyboardEvent) => { | |
| if (e.key === key) { | |
| sendBack(keyModel.events.release()); | |
| } | |
| }; | |
| window.addEventListener('keyup', onKeyUp); | |
| return () => { | |
| window.removeEventListener('keydown', onKeyDown); | |
| window.removeEventListener('keyup', onKeyUp); | |
| }; | |
| }, | |
| }, | |
| }); | |
| return keyMachine; | |
| } | |
| const appModel = createModel( | |
| { | |
| focusedKey: 'x' as Key, | |
| // Pretend actors exist since they are assigned in the entry action to register with | |
| ArrowUp: (null as unknown) as SpawnedActorRef<any, any>, | |
| ArrowDown: (null as unknown) as SpawnedActorRef<any, any>, | |
| ArrowLeft: (null as unknown) as SpawnedActorRef<any, any>, | |
| ArrowRight: (null as unknown) as SpawnedActorRef<any, any>, | |
| x: (null as unknown) as SpawnedActorRef<any, any>, | |
| }, | |
| { | |
| events: { | |
| press: (key: Key) => ({ key }), | |
| }, | |
| } | |
| ); | |
| const appMachine = createMachine<typeof appModel>({ | |
| id: 'app', | |
| context: appModel.initialContext, | |
| entry: [ | |
| assign({ | |
| ArrowUp: (context) => spawn(createKeyMachine('ArrowUp', 0, 0), 'ArrowUp'), | |
| ArrowDown: (context) => spawn(createKeyMachine('ArrowDown', 0, 0), 'ArrowDown'), | |
| ArrowLeft: (context) => spawn(createKeyMachine('ArrowLeft', 0, 0), 'ArrowLeft'), | |
| ArrowRight: (context) => spawn(createKeyMachine('ArrowRight', 0, 0), 'ArrowRight'), | |
| x: (context) => spawn(createKeyMachine('x', 0, 0), 'x'), | |
| }), | |
| send('focus', { to: (context) => context[context.focusedKey] }), | |
| ], | |
| initial: 'idle', | |
| states: { | |
| idle: { | |
| on: { | |
| press: { | |
| actions: actions.pure((context, event) => { | |
| const { focusedKey } = context; | |
| if (event.key !== 'x') { | |
| const dx = event.key === 'ArrowLeft' ? -1 : event.key === 'ArrowRight' ? 1 : 0; | |
| const dy = event.key === 'ArrowDown' ? -1 : event.key === 'ArrowUp' ? 1 : 0; | |
| return send({ type: 'move', dx, dy }, { to: () => context[focusedKey] }); | |
| } | |
| const nextFocusedKey = getNextKey(focusedKey); | |
| return [ | |
| send('unfocus', { to: () => context[focusedKey] }), | |
| assign({ focusedKey: nextFocusedKey }), | |
| send('focus', { to: () => context[nextFocusedKey] }), | |
| ]; | |
| }), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }); | |
| function getNextKey(key: Key): Key { | |
| if (key === 'x') return 'ArrowLeft'; | |
| else if (key === 'ArrowLeft') return 'ArrowUp'; | |
| else if (key === 'ArrowUp') return 'ArrowRight'; | |
| else if (key === 'ArrowRight') return 'ArrowDown'; | |
| return 'x'; | |
| } | |
| const service = interpret(appMachine, { devTools: true }); | |
| service.start(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment