import { createMachine, assign, actions as xActions, sendParent, send } from 'xstate'; const { cancel } = xActions; export interface DelayContext { elapsed: number; duration: number; // Note: this will come from the parent form progress: number; interval: number; initialDelay: number; // Note: this will come from field type speed: number; // Note: this will come from the parent form broadcastEvent: string; queueImmediately: boolean; } // Note: 'any' due to current typing bug when sending to parent --> https://github.com/davidkpiano/xstate/issues/1198 const delayId = 'delayBeforePlay'; export const delayMachine = createMachine( { context: { elapsed: 0, duration: 1000, progress: 0, interval: 100, initialDelay: 1000, speed: 1, broadcastEvent: 'SUBMIT_NODE', queueImmediately: false }, tsTypes: {} as import("./delayMachine.typegen").Typegen0, schema: { context: {} as DelayContext, events: {} as { type: 'QUEUE'; } | { type: 'UNQUEUE'; } | // { type: 'PAUSE'; id: any } | // { type: 'PLAY'; value: any } | { type: 'PLAY_PAUSE' } | { type: 'DURATION'; duration: any } | { type: 'SET_SPEED'; speed: any } | { type: 'RESET_QUEUE'; value: any } | { type: 'TICK'; value: any } }, initial: 'idle', states: { idle: { entry: [ (c,e) => console.log("RESETING DELAY!", c, e), 'resetDelay'], always: { target: 'waiting', cond: (context, event) => context.queueImmediately }, on: { QUEUE: 'waiting' } }, waiting: { entry: 'delayBeforePlay', on: { RESET_QUEUE: 'idle', PLAY_PAUSE: { target: 'running', actions: [ 'cancelDelayBeforeSubmit' ] } } }, running: { invoke: { src: 'runTicks', }, always: [ /* Note: https://github.com/davidkpiano/xstate/releases/tag/xstate%404.11.0 */ { target: 'completed', cond: 'timerIsFinished' } ], on: { RESET_QUEUE: 'idle', PLAY_PAUSE: 'paused', TICK: { actions: ['updateTimer', 'updateProgess'] } } }, paused: { on: { RESET_QUEUE: 'idle', PLAY_PAUSE: { target: 'running', cond: 'timerStillRunning' } } }, completed: { entry: [ 'notifyParent', 'cancelDelayBeforeSubmit' ], type: 'final' } }, on: { DURATION: { actions: 'assignDuration' }, SET_SPEED: { actions: 'assignSpeed' }, UNQUEUE: { target: 'idle', actions: [ 'cancelDelayBeforeSubmit' ] } } }, { services: { runTicks: (context, event) => (cb) => { const interval = setInterval(() => cb('TICK'), context.interval/context.speed); return () => clearInterval(interval); } }, actions: { notifyParent: sendParent((context, event) => { console.log("notifyiung parent in delay: ", event, context.broadcastEvent); return { type: context.broadcastEvent }; } //, { delay: (context, event) => 1000/context.speed } ), // TODO: this should be configurable assignDuration: assign({ duration: (context, event) => event.duration }), assignSpeed: assign({ speed: (context, event ) => { console.log("assigning speed in delayMachine: ", context, event) return event.speed } }), updateTimer: assign({ elapsed: (context) => +( (context.elapsed + context.interval).toFixed(2) ) }), updateProgess: assign({ progress: (context) => +( (context.elapsed / context.duration).toFixed(2) ) }), resetDelay: assign({ elapsed: 0 }), delayBeforePlay: send('PLAY_PAUSE', { id: delayId, delay: (context) => context.initialDelay }), cancelDelayBeforeSubmit: cancel(delayId) }, guards: { timerStillRunning: (context) => context.elapsed < context.duration, timerIsFinished: (context) => { // console.log("DELA~Y CONTEXT: ", context.duration, context.initialDelay); return context.elapsed >= context.duration } } });