Created
March 17, 2023 19:24
-
-
Save charpeni/176b624cf3e7091bfaa5523268dc41c9 to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
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
const CategoryName = { | |
RecoveryFlowRequest: 'RECOVERY_FLOW_REQUEST', | |
RecoveryFlowAckTimeout: 'RECOVERY_FLOW_ACK_TIMEOUT', | |
CKAutoSave: 'CK_AUTO_SAVE', | |
CKVersionMismatch: 'CK_VERSION_MISMATCH', | |
CKInternalConnection: 'CK_INTERNAL_CONNECTION', | |
CKComment: 'CK_COMMENT', | |
CKCollabComment: 'CK_COLLAB_COMMENT', | |
CKNameOfUndefined: 'CK_NAME_OF_UNDEFINED', | |
CKSessionNotFound: 'CK_SESSION_NOT_FOUND', | |
CKNullAttributes: 'CK_NULL_ATTRIBUTES', | |
CKEditorInitalError: 'CK_EDITOR_INITAL_ERROR', | |
CKOther: 'CK_OTHER', | |
Unknown: 'UNKNOWN', | |
}; | |
const Errors = { CategoryName }; | |
function isRecoveryReady(context) { | |
return context.doc && context.doc.recovery.status === 'ready'; | |
} | |
function isRecoveryAwaitingAcks(context) { | |
return context.doc && context.doc.recovery.status === 'awaiting-acks'; | |
} | |
function isAutoSaved(context) { | |
return context.lastAutoSavedVersion === context.lastSeenVersion; | |
} | |
function createMachine(options) { | |
return Machine( | |
{ | |
id: 'supervisor', | |
type: 'parallel', | |
initial: 'main', | |
context: { | |
attemptNumber: 0, | |
autoSaveFailures: 0, | |
beforeDestroy: options.beforeDestroy, | |
currentEditor: null, | |
doc: options.doc, | |
docEvents: options.docEvents, | |
handledError: null, | |
initCount: 0, | |
initEditor: options.initEditor, | |
lastAutoSavedVersion: 0, | |
lastError: null, | |
lastSeenVersion: 0, | |
showEditorUpdateMessage: options.showEditorUpdateMessage, | |
showRedErrorMessage: options.showRedErrorMessage, | |
sendGetDocRequest: options.sendGetDocRequest, | |
sendRecoveryStartRequest: options.sendRecoveryStartRequest, | |
sendRecoveryAckRequest: options.sendRecoveryAckRequest, | |
sendRecoveryResetRequest: options.sendRecoveryResetRequest, | |
startRedirectingInput: options.startRedirectingInput, | |
stopRedirectingInput: options.stopRedirectingInput, | |
onRecoverySuccess: options.onRecoverySuccess, | |
onRecoveryPending: options.onRecoveryPending, | |
totalErrors: 0, | |
windowEvents: options.windowEvents, | |
}, | |
states: { | |
// ## MACHINE: Editor Object | |
// | |
// ### Context | |
// | |
// - beforeDestroy | |
// - A function that runs external side-effects before destroying the editor | |
// | |
// - currentEditor | |
// - The editor instance from the last resolved initEditor promise | |
// | |
// - initCount | |
// - The number of times the editor has attempted to initialize | |
// | |
// - initEditor | |
// - A function that returns a promise that resolves with an editor instance or an error | |
// | |
// ### Requests | |
// | |
// - EDITOR_OBJECT.INITIALIZE | |
// - Initialize the editor | |
// | |
// - EDITOR_OBJECT.READ_ONLY | |
// - Set the editor to read-only mode | |
// | |
// - EDITOR_OBJECT.STOP | |
// - Destroy the editor and stop listening to events | |
// | |
// ### Indications | |
// | |
// - EDITOR_OBJECT.ERROR | |
// - Triggered when the initEditor promise resolves with an object with an err property | |
// | |
// - EDITOR_OBJECT.STOPPED | |
// - Triggered when entering the stopped state | |
// | |
editorObject: { | |
initial: 'unknown', | |
on: { | |
'EDITOR_OBJECT.STOP': '.stopped', | |
}, | |
states: { | |
unknown: { | |
on: { | |
'EDITOR_OBJECT.INITIALIZE': { | |
target: 'initializing', | |
}, | |
}, | |
}, | |
initializing: { | |
entry: [assign({ initCount: (ctx) => ctx.initCount + 1 }), 'startRedirectingInput', 'destroyEditor'], | |
invoke: { | |
src: 'initializeEditor', | |
onDone: { | |
target: 'editable', | |
actions: [assign({ currentEditor: (ctx, event) => event.data }), send('EDITOR_OBJECT.INITIALIZED')], | |
}, | |
onError: { | |
target: 'readOnly', | |
actions: 'sendEditorObjectError', | |
}, | |
}, | |
}, | |
readOnly: { | |
entry: ['startRedirectingInput', 'setReadOnly'], | |
on: { | |
'EDITOR_OBJECT.INITIALIZE': { target: 'initializing' }, | |
}, | |
}, | |
editable: { | |
entry: 'stopRedirectingInput', | |
on: { | |
'EDITOR_OBJECT.READ_ONLY': { target: 'readOnly' }, | |
}, | |
}, | |
stopped: { | |
entry: ['destroyEditor', send('EDITOR_OBJECT.STOPPED')], | |
type: 'final', | |
}, | |
}, | |
}, | |
// ## MACHINE: Document Data | |
// | |
// ### Context | |
// | |
// - doc | |
// - The current cached doc properties | |
// | |
// - sendGetDocRequest | |
// - A function that resolves with the latest document data | |
// | |
// ### Requests | |
// | |
// - DOCUMENT_DATA.EXPIRE | |
// - Expire the data to force a refetch | |
// | |
// - DOCUMENT_DATA.FETCH | |
// - Start fetching data once it's expired | |
// | |
// ### Indications | |
// | |
// - DOCUMENT_DATA.EXPIRED | |
// - Triggered when entering the expired state | |
// | |
// - DOCUMENT_DATA.READY | |
// - Triggered when entering the ready state | |
// | |
// - DOCUMENT_DATA.ERROR | |
// - Triggered when the sendGetDocRequest promise is rejected | |
// | |
documentData: { | |
initial: 'checking', | |
states: { | |
checking: { | |
always: [ | |
{ | |
cond: isRecoveryReady, | |
target: 'ready', | |
}, | |
{ | |
cond: isRecoveryAwaitingAcks, | |
target: 'expired', | |
}, | |
], | |
}, | |
ready: { | |
entry: send('DOCUMENT_DATA.READY'), | |
on: { | |
'DOCUMENT_DATA.EXPIRE': { target: 'expired' }, | |
}, | |
}, | |
expired: { | |
initial: 'idle', | |
onDone: 'checking', | |
entry: send('DOCUMENT_DATA.EXPIRED'), | |
states: { | |
idle: { | |
on: { | |
'DOCUMENT_DATA.FETCH': { | |
target: 'fetching', | |
}, | |
}, | |
}, | |
fetching: { | |
invoke: { | |
src: 'sendGetDocRequest', | |
onDone: { | |
target: 'fetched', | |
actions: assign({ doc: (ctx, event) => Object.assign({}, ctx.doc, event.data.data.node) }), | |
}, | |
onError: { target: 'error' }, | |
}, | |
}, | |
fetched: { type: 'final' }, | |
error: { | |
entry: send('DOCUMENT_DATA.ERROR'), | |
on: { | |
'DOCUMENT_DATA.FETCH': { | |
target: 'fetching', | |
}, | |
}, | |
}, | |
}, | |
}, | |
}, | |
}, | |
// ## MACHINE: Network State | |
// | |
// ### Requests | |
// | |
// - NETWORK_STATE.OFFLINE | |
// - Set the state to offline | |
// | |
// - NETWORK_STATE.ONLINE | |
// - Set the state to online | |
// | |
// ### Indications | |
// | |
// - NETWORK_STATE.RECONNECTED | |
// - Triggered when the state changes from offline to online | |
// | |
networkState: { | |
initial: 'online', | |
states: { | |
online: { | |
on: { | |
'NETWORK_STATE.OFFLINE': { | |
target: 'offline', | |
}, | |
}, | |
}, | |
offline: { | |
exit: send('NETWORK_STATE.RECONNECTED'), | |
on: { | |
'NETWORK_STATE.ONLINE': { | |
target: 'online', | |
}, | |
}, | |
}, | |
}, | |
}, | |
// ## MACHINE: Error Handler | |
// | |
// ### Context | |
// | |
// - handledError | |
// - The last error that was handled by the error handler | |
// | |
// - lastError | |
// - The last error that was detected by the error handler | |
// | |
// - totalErrors | |
// - The total number of errors detected by the error handler | |
// | |
// ### Requests | |
// | |
// - ERROR_HANDLER.ERROR_DETECTED | |
// - Send an error to the error handler | |
// | |
// - ERROR_HANDLER.RESET | |
// - Reset the error handler so that recovery can be attempted again if necessary | |
// | |
// ### Indications | |
// | |
// - ERROR_HANDLER.REQUEST_RECOVERY | |
// - Triggered when the error handler wants to start the recovery flow | |
// | |
// - ERROR_HANDLER.DISABLE_RECOVERY | |
// - Triggered when the error handler wants to disable the recovery flow | |
// | |
// - ERROR_HANDLER.MANUAL_REFRESH | |
// - Triggered when the error handler gives up on recovery and asks the user to refresh their tab | |
// | |
// - ERROR_HANDLER.VERSION_MISMATCH | |
// - Triggered when a version mismatch error is detected | |
// | |
errorHandler: { | |
initial: 'ok', | |
states: { | |
ok: { | |
on: { | |
'ERROR_HANDLER.ERROR_DETECTED': { | |
target: 'filtering', | |
actions: 'saveError', | |
}, | |
}, | |
}, | |
filtering: { | |
always: [ | |
{ | |
cond: 'isHandlingError', | |
target: 'error', | |
}, | |
{ | |
target: 'ok', | |
}, | |
], | |
}, | |
error: { | |
initial: 'dispatching', | |
onDone: 'ok', | |
states: { | |
dispatching: { | |
always: [ | |
{ | |
cond: 'isMaxErrors', | |
actions: [send('ERROR_HANDLER.REQUEST_MANUAL_REFRESH'), send('ERROR_HANDLER.MAX_ERRORS')], | |
target: 'waiting', | |
}, | |
{ | |
cond: { | |
type: 'isErrorInCategory', | |
category: [ | |
Errors.CategoryName.CKInternalConnection, | |
Errors.CategoryName.CKEditorInitalError, | |
Errors.CategoryName.CKSessionNotFound, | |
Errors.CategoryName.CKNullAttributes, | |
], | |
}, | |
actions: send('ERROR_HANDLER.REQUEST_REINIT'), | |
target: 'reinitializing', | |
}, | |
{ | |
cond: { | |
type: 'isErrorInCategory', | |
category: Errors.CategoryName.CKAutoSave, | |
}, | |
target: 'autoSaving', | |
}, | |
{ | |
cond: { | |
type: 'isErrorInCategory', | |
category: [Errors.CategoryName.CKNameOfUndefined, Errors.CategoryName.CKOther], | |
}, | |
actions: send('ERROR_HANDLER.REQUEST_RECOVERY'), | |
target: 'recovering', | |
}, | |
{ | |
cond: { | |
type: 'isErrorInCategory', | |
category: Errors.CategoryName.CKVersionMismatch, | |
}, | |
actions: [ | |
send('ERROR_HANDLER.MUTE_RECOVERY'), | |
send('ERROR_HANDLER.REQUEST_RECOVERY'), | |
send('ERROR_HANDLER.VERSION_MISMATCH'), | |
], | |
target: 'recovering', | |
}, | |
{ | |
cond: { | |
type: 'isErrorInCategory', | |
category: Errors.CategoryName.RecoveryFlowRequest, | |
}, | |
actions: [send('ERROR_HANDLER.DISABLE_RECOVERY'), send('ERROR_HANDLER.REQUEST_MANUAL_REFRESH')], | |
target: 'waiting', | |
}, | |
{ | |
cond: { | |
type: 'isErrorInCategory', | |
category: Errors.CategoryName.RecoveryFlowAckTimeout, | |
}, | |
actions: [send('ERROR_HANDLER.MUTE_RECOVERY'), send('ERROR_HANDLER.REQUEST_MANUAL_REFRESH')], | |
target: 'waiting', | |
}, | |
{ | |
target: 'waiting', | |
actions: send('ERROR_HANDLER.REQUEST_MANUAL_REFRESH'), | |
}, | |
], | |
}, | |
autoSaving: { | |
entry: assign({ autoSaveFailures: (ctx) => ctx.autoSaveFailures + 1 }), | |
always: [ | |
{ | |
cond: (ctx) => ctx.autoSaveFailures >= 3, | |
actions: [send('ERROR_HANDLER.DISABLE_RECOVERY'), send('ERROR_HANDLER.REQUEST_MANUAL_REFRESH')], | |
target: 'handled', | |
}, | |
{ target: 'handled' }, | |
], | |
}, | |
recovering: { | |
on: { | |
'ERROR_HANDLER.RECOVERY_READY': { | |
target: 'handled', | |
}, | |
'ERROR_HANDLER.ERROR_DETECTED': [ | |
{ | |
cond: 'isMaxErrors', | |
actions: [ | |
'saveError', | |
send('ERROR_HANDLER.REQUEST_MANUAL_REFRESH'), | |
send('ERROR_HANDLER.MAX_ERRORS'), | |
], | |
target: 'waiting', | |
}, | |
{ actions: 'saveError' }, | |
], | |
}, | |
}, | |
reinitializing: { | |
on: { | |
'ERROR_HANDLER.ERROR_DETECTED': [ | |
{ | |
cond: 'isMaxErrors', | |
actions: [ | |
'saveError', | |
send('ERROR_HANDLER.REQUEST_MANUAL_REFRESH'), | |
send('ERROR_HANDLER.MAX_ERRORS'), | |
], | |
target: 'waiting', | |
}, | |
{ | |
cond: { | |
type: 'isErrorInCategory', | |
category: Errors.CategoryName.CKInternalConnection, | |
}, | |
actions: ['saveError', send('ERROR_HANDLER.REQUEST_MANUAL_REFRESH')], | |
}, | |
{ actions: 'saveError' }, | |
], | |
'ERROR_HANDLER.EDITOR_INITIALIZED': { | |
target: 'handled', | |
actions: assign({ autoSaveFailures: 0 }), | |
}, | |
}, | |
}, | |
waiting: { | |
on: { | |
'ERROR_HANDLER.ERROR_DETECTED': { actions: 'saveError' }, | |
}, | |
}, | |
handled: { | |
type: 'final', | |
}, | |
}, | |
}, | |
}, | |
}, | |
// ## MACHINE: Recovery Flow | |
// | |
// ### Context | |
// | |
// - attemptNumber | |
// - The count of recovery flow attempts | |
// | |
// - doc | |
// - The current cached doc properties | |
// | |
// - lastAutoSavedVersion | |
// - The last auto-save that was completed | |
// | |
// - lastSeenVersion | |
// - The last auto-save that was started | |
// | |
// ### Requests | |
// | |
// - RECOVERY_FLOW.READY | |
// - Set the recovery flow status to ready | |
// | |
// - RECOVERY_FLOW.AWAITING_ACKS | |
// - Set the recovery flow status to awaitingAcks | |
// | |
// - RECOVERY_FLOW.DISABLE | |
// - Disable the recovery flow | |
// | |
// - RECOVERY_FLOW.REQUEST_START | |
// - Start the recovery flow if it hasn't started already | |
// | |
// - RECOVERY_FLOW.AUTO_SAVE_FINISH | |
// - Notify the recovery flow of successful auto-saves so it can determine when to send acknowledgement messages | |
// | |
// ### Indications | |
// | |
// - RECOVERY_FLOW.STARTED | |
// - Triggered when the recovery flow start request is sent by the local supervisor | |
// | |
// - RECOVERY_FLOW.MAX_ATTEMPTS | |
// - Triggered when the recovery flow is unable to fix the error | |
// | |
// - RECOVERY_FLOW.ERROR | |
// - Triggered when either the sendRecoveryStartRequest or the sendRecoveryAckRequest promises are rejected | |
// | |
recoveryFlow: { | |
type: 'parallel', | |
states: { | |
notifications: { | |
initial: 'enabled', | |
states: { | |
enabled: { | |
on: { | |
'RECOVERY_FLOW.READY': { actions: 'onRecoverySuccess' }, | |
'RECOVERY_FLOW.AWAITING_ACKS': { actions: 'onRecoveryPending' }, | |
'RECOVERY_FLOW.MUTE': { target: 'disabled' }, | |
}, | |
}, | |
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | |
// @ts-ignore | |
disabled: { target: 'final' }, | |
}, | |
}, | |
requests: { | |
initial: 'unknown', | |
on: { | |
'RECOVERY_FLOW.READY': { target: '.ready' }, | |
'RECOVERY_FLOW.AWAITING_ACKS': { target: '.awaitingAcks' }, | |
'RECOVERY_FLOW.DISABLE': { target: '.disabled' }, | |
}, | |
states: { | |
unknown: { | |
always: [ | |
{ | |
cond: isRecoveryReady, | |
target: 'ready', | |
}, | |
{ | |
cond: isRecoveryAwaitingAcks, | |
target: 'awaitingAcks', | |
}, | |
], | |
}, | |
ready: { | |
initial: 'idle', | |
states: { | |
idle: { | |
on: { | |
'RECOVERY_FLOW.REQUEST_START': [ | |
{ | |
cond: 'isMaxRecoveryAttempts', | |
actions: send('RECOVERY_FLOW.MAX_ATTEMPTS'), | |
}, | |
{ | |
target: 'started', | |
}, | |
], | |
}, | |
}, | |
started: { | |
entry: [send('RECOVERY_FLOW.STARTED'), assign({ attemptNumber: (ctx) => ctx.attemptNumber + 1 })], | |
always: { | |
target: 'sendingStartRequest', | |
}, | |
on: { | |
'RECOVERY_FLOW.EDITOR_INITIALIZED': { | |
target: 'sendingStartRequest', | |
}, | |
}, | |
}, | |
sendingStartRequest: { | |
invoke: { | |
src: 'sendRecoveryStartRequest', | |
onDone: { target: 'idle' }, | |
onError: { | |
target: 'idle', | |
actions: 'sendRecoveryFlowRequestError', | |
}, | |
}, | |
}, | |
}, | |
}, | |
awaitingAcks: { | |
type: 'parallel', | |
states: { | |
timeout: { | |
initial: 'waiting', | |
states: { | |
waiting: { | |
after: { | |
15000: { | |
target: 'resetting', | |
actions: 'sendRecoveryFlowAckTimeoutError', | |
}, | |
}, | |
}, | |
resetting: { | |
invoke: { | |
src: 'sendRecoveryResetRequest', | |
onDone: { target: 'idle' }, | |
onError: { | |
target: 'idle', | |
actions: 'sendRecoveryFlowRequestError', | |
}, | |
}, | |
}, | |
idle: {}, | |
}, | |
}, | |
main: { | |
initial: 'waiting', | |
states: { | |
waiting: { | |
after: { | |
2000: 'checking', | |
}, | |
}, | |
checking: { | |
always: { | |
cond: isAutoSaved, | |
target: 'started', | |
}, | |
on: { | |
'RECOVERY_FLOW.AUTO_SAVE_FINISH': { | |
cond: isAutoSaved, | |
target: 'started', | |
}, | |
}, | |
}, | |
started: { | |
always: { | |
cond: (ctx) => ctx.currentEditor, | |
target: 'sendingAckRequest', | |
}, | |
on: { | |
'RECOVERY_FLOW.EDITOR_INITIALIZED': { | |
target: 'sendingAckRequest', | |
}, | |
}, | |
}, | |
sendingAckRequest: { | |
invoke: { | |
src: 'sendRecoveryAckRequest', | |
onDone: { target: 'idle' }, | |
onError: { | |
target: 'idle', | |
actions: 'sendRecoveryFlowRequestError', | |
}, | |
}, | |
}, | |
idle: {}, | |
}, | |
}, | |
}, | |
}, | |
disabled: { | |
type: 'final', | |
}, | |
}, | |
}, | |
}, | |
}, | |
// ## MACHINE: Main | |
// | |
// ### Context | |
// | |
// - docEvents | |
// - An EventSource for document events | |
// | |
// - lastAutoSavedVersion | |
// - The last auto-save that was completed | |
// | |
// - lastSeenVersion | |
// - The last auto-save that was started | |
// | |
// - showRedErrorMessage | |
// - A function to display a message to ask the user to manually refresh the tab | |
// | |
// - showRedErrorMessage | |
// - A function to display a message to tell the user there's a new version of the editor | |
// | |
// - windowEvents | |
// - The global window object (or a stub EventTarget for testing) | |
// | |
main: { | |
initial: 'started', | |
states: { | |
started: { | |
initial: 'active', | |
invoke: { src: 'addEventListeners' }, | |
on: { | |
'ERROR_HANDLER.MAX_ERRORS': { | |
actions: send('SUPERVISOR.STOP'), | |
}, | |
'RECOVERY_FLOW.MAX_ATTEMPTS': { | |
actions: send('SUPERVISOR.MANUAL_REFRESH'), | |
}, | |
'SUPERVISOR.STOP': { | |
target: 'stopping', | |
}, | |
'RECOVERY_FLOW.AWAITING_ACKS': { actions: send('DOCUMENT_DATA.EXPIRE') }, | |
'RECOVERY_FLOW.ERROR': { actions: 'sendErrorHandlerErrorDetected' }, | |
'RECOVERY_FLOW.READY': { | |
actions: [send('DOCUMENT_DATA.FETCH'), send('ERROR_HANDLER.RECOVERY_READY')], | |
}, | |
'RECOVERY_FLOW.STARTED': { actions: send('EDITOR_OBJECT.READ_ONLY') }, | |
'EDITOR_OBJECT.INITIALIZED': { | |
actions: [send('RECOVERY_FLOW.EDITOR_INITIALIZED'), send('ERROR_HANDLER.EDITOR_INITIALIZED')], | |
}, | |
}, | |
states: { | |
active: { | |
on: { | |
'DOCUMENT_DATA.ERROR': { actions: 'sendErrorHandlerErrorDetected' }, | |
'DOCUMENT_DATA.EXPIRED': { actions: send('EDITOR_OBJECT.READ_ONLY') }, | |
'DOCUMENT_DATA.READY': { actions: send('EDITOR_OBJECT.INITIALIZE') }, | |
'EDITOR_OBJECT.ERROR': { actions: 'sendErrorHandlerErrorDetected' }, | |
'ERROR_HANDLER.DISABLE_RECOVERY': { actions: send('RECOVERY_FLOW.DISABLE') }, | |
'ERROR_HANDLER.MUTE_RECOVERY': { actions: send('RECOVERY_FLOW.MUTE') }, | |
'ERROR_HANDLER.REQUEST_RECOVERY': { actions: send('RECOVERY_FLOW.REQUEST_START') }, | |
// FIXME: @rads: Why does send('SUPERVISOR.MANUAL_REFRESH') not work here? | |
'ERROR_HANDLER.REQUEST_MANUAL_REFRESH': { target: 'redErrorMessage' }, | |
'ERROR_HANDLER.REQUEST_REINIT': { | |
actions: [send('DOCUMENT_DATA.EXPIRE'), send('DOCUMENT_DATA.FETCH')], | |
}, | |
'ERROR_HANDLER.VERSION_MISMATCH': { target: 'editorUpdateMessage' }, | |
'NETWORK_STATE.RECONNECTED': { | |
actions: [send('DOCUMENT_DATA.EXPIRE'), send('DOCUMENT_DATA.FETCH')], | |
}, | |
'SUPERVISOR.AUTO_SAVE_START': { | |
actions: assign({ lastSeenVersion: (ctx, event) => event.version }), | |
}, | |
'SUPERVISOR.AUTO_SAVE_FINISH': { | |
actions: [ | |
assign({ lastAutoSavedVersion: (ctx, event) => event.version }), | |
send('RECOVERY_FLOW.AUTO_SAVE_FINISH'), | |
], | |
}, | |
'SUPERVISOR.MANUAL_REFRESH': { target: 'redErrorMessage' }, | |
}, | |
}, | |
redErrorMessage: { | |
entry: [send('EDITOR_OBJECT.READ_ONLY'), 'showRedErrorMessage'], | |
type: 'final', | |
}, | |
editorUpdateMessage: { | |
entry: [send('EDITOR_OBJECT.READ_ONLY'), 'showEditorUpdateMessage'], | |
type: 'final', | |
}, | |
}, | |
}, | |
stopping: { | |
entry: send('EDITOR_OBJECT.STOP'), | |
on: { | |
'EDITOR_OBJECT.STOPPED': { | |
target: 'stopped', | |
}, | |
}, | |
}, | |
stopped: { | |
type: 'final', | |
}, | |
}, | |
}, | |
}, | |
}, | |
{ | |
activities: options.activities, | |
actions: options.actions, | |
guards: options.guards, | |
services: options.services, | |
delays: options.delays, | |
} | |
); | |
} | |
// Uncomment the following line for the visualizer: | |
const machine = createMachine({}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment