Last active
September 28, 2019 05:48
-
-
Save enjoylife/024012d46f9749bb1227f0b93a36b843 to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
This file contains 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 resetableEditableContext = () => { | |
return { | |
pushing: { | |
updates: [], | |
error: null, | |
lastSuccess: null, | |
}, | |
pulling: { | |
error: null, | |
lastSuccess: null, | |
updates: [], | |
}, | |
}; | |
}; | |
const editableMachineError = key => { | |
return assign({ | |
[key]: (context, event) => { | |
// using an immutable pattern | |
return { | |
...context[key], | |
error: event.error, | |
}; | |
}, | |
}); | |
}; | |
const editableMachineSuccess = key => { | |
return assign({ | |
[key]: (context, event) => { | |
// using an immutable pattern | |
return { | |
...context[key], | |
lastSuccess: new Date().getTime(), | |
error: null, | |
updates: [], | |
}; | |
}, | |
}); | |
}; | |
function fakeSyncFetch(changes) { | |
return new Promise((resolve, reject) => { | |
console.log('fake sync fetch', changes); | |
if (Math.random() >= 0.8) { | |
setTimeout(resolve, 5000, {accepted: changes}); | |
} else { | |
setTimeout(reject, 2000, {auth: 'not allowed'}); | |
} | |
}); | |
} | |
const simpleSyncLoop = (callback, onEvent) => { | |
const changeHandler = changes => fakeSyncFetch(changes); | |
console.log('syncLoop started'); | |
// Receive events from parent | |
onEvent(event => { | |
console.log('syncLoop recieved event', event); | |
if (event.type === 'CHANGE') { | |
event.change = 'x'; // for playground | |
console.log('queuing change', event.change); | |
const onSynced = data => { | |
console.log('promise done!'); | |
callback({ | |
type: 'SUCCESS', | |
syncResponse: data, | |
}); | |
}; | |
const onError = e => { | |
console.log('promise failed!'); | |
callback({ | |
type: 'ERROR', | |
error: e, | |
}); | |
}; | |
// We have changes we need to sync... | |
changeHandler(event.change) | |
.then(onSynced) | |
.catch(onError); | |
} | |
}); | |
// Perform cleanup | |
return () => { | |
// TODO cancel any debounced/pending changes | |
console.log('cleaned up'); | |
}; | |
}; | |
// A editableMachine provides a state machine to manage a component which | |
// can take on a viewable or editable state. More importantly when | |
// edits do occur the process and constraints to make sure no changes are lost are provided by the state machine. | |
const editableMachine = Machine( | |
{ | |
id: 'editable', | |
type: 'parallel', | |
strict: true, | |
context: { | |
canEdit: true, | |
retryOptions: { | |
pushing: {}, // leverages defaults of retry function | |
pulling: {}, // leverages defaults of retry | |
}, | |
...resetableEditableContext(), | |
}, | |
states: { | |
editor: { | |
initial: 'viewing', | |
states: { | |
viewing: { | |
on: {EDIT: {target: 'editing', cond: 'canEdit'}}, | |
}, | |
editing: { | |
on: { | |
DONE: [ | |
{target: 'viewing', cond: 'isSaved' /*actions: 'flush', */}, | |
{target: 'prompt_force_view', cond: 'waitingOnSave'}, | |
], | |
}, | |
}, | |
printing: { | |
on: { | |
DONE: {target: 'viewing'}, | |
}, | |
}, | |
prompt_force_view: { | |
exit: ['resetContext'], | |
on: { | |
DONE: {target: 'viewing'}, | |
}, | |
}, | |
}, | |
}, | |
syncer: { | |
type: 'parallel', | |
states: { | |
// pushing is the states which deal with | |
// edits originating from the user or client side | |
// which need to be pushed out to the server | |
pushing: { | |
initial: 'sync', | |
states: { | |
idle: { | |
on: { | |
SYNC: {target: 'sync'}, | |
CHANGE: { | |
actions: ['queueChange'], | |
}, | |
}, | |
}, | |
sync: { | |
invoke: { | |
id: 'syncLoop', | |
autoForward: true, | |
src: ['syncLoopFn'], | |
// error in callback prior to any sending.. | |
onError: { | |
target: 'idle', | |
actions: [ | |
'sendChange', | |
assign({ | |
pushing: (context, event) => { | |
console.log('error in syncLoop'); | |
// using an immutable pattern | |
return { | |
...context.pushing, | |
error: event.data, | |
}; | |
}, | |
}), | |
], | |
}, | |
}, | |
on: { | |
CHANGE: { | |
target: 'sync', | |
actions: ['queueChange', 'sendChange'], | |
}, | |
PAUSE: {target: 'idle'}, | |
SUCCESS: {target: 'sync', actions: ['pushingSuccess']}, | |
FAILURE: {target: 'idle', actions: ['pushingError']}, | |
}, | |
}, | |
}, | |
}, | |
pulling: { | |
initial: 'idle', | |
states: { | |
idle: { | |
on: { | |
START: {target: 'sync'}, | |
}, | |
}, | |
sync: { | |
on: { | |
PAUSE: {target: 'idle'}, | |
SUCCESS: {target: 'sync', actions: ['pullingSuccess']}, | |
FAILURE: {target: 'idle', actions: ['pullingError']}, | |
}, | |
}, | |
}, | |
}, | |
}, | |
// invoke: { | |
// id: 'syncMachine', | |
// src: syncMachine, | |
// // Deriving child context from parent context | |
// data: { | |
// duration: (context, event) => context.customDuration | |
// } | |
// } | |
}, | |
// TODO: state waiting for confirmation that FORCE_DONE is ok | |
}, | |
}, | |
{ | |
actions: { | |
resetContext: assign({ | |
...resetableEditableContext(), | |
}), | |
pushingError: editableMachineError('pushing'), | |
pullingError: editableMachineError('pulling'), | |
pushingSuccess: editableMachineSuccess('pushing'), | |
pullingSuccess: editableMachineSuccess('pulling'), | |
queueChange: assign({ | |
pushing: (context, event) => { | |
// using an immutable pattern | |
return { | |
...context.pushing, | |
updates: [...context.pushing.updates, event.change], | |
}; | |
}, | |
}), | |
sendChange: send( | |
(context, event) => { | |
return { | |
type: 'CHANGE', | |
change: event.change, | |
}; | |
}, | |
{to: 'syncLoop'} | |
), | |
}, | |
guards: { | |
canEdit: (context, event) => context.canEdit, | |
isSaved: (context, event) => | |
context.pushing.updates.length === 0 && !context.error, | |
waitingOnSave: (context, event) => | |
context.pushing.updates.length !== 0 && | |
!context.pushing.error && | |
!context.pulling.error, | |
}, | |
services: { | |
syncLoopFn: simpleSyncLoop, | |
}, | |
} | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment