Skip to content

Instantly share code, notes, and snippets.

@nathanhammond
Created August 19, 2020 06:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nathanhammond/d7f960b02cbf93061dfe072f8cec751e to your computer and use it in GitHub Desktop.
Save nathanhammond/d7f960b02cbf93061dfe072f8cec751e to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
(function (xstate, constants) {
'use strict';
/**
* Leaf state condition generator.
*
* @param {String} target the target state
*/
function matchLeafState(target) {
return {
type: constants.DEFAULT_GUARD_TYPE,
name: `${target}`,
predicate: function(context, event, condMeta) {
var state = condMeta.state;
var value;
if (state) {
value = state.toStrings().pop().split(constants.STATE_DELIMITER).pop();
}
return value === target;
}
};
}
function matchThroughState(key, value) {
return {
type: constants.DEFAULT_GUARD_TYPE,
name: `${key} = ${value}`,
predicate: function(context) {
return context[key] === value;
}
};
}
function namedCondition(name, predicate) {
return {
type: constants.DEFAULT_GUARD_TYPE,
name: `${name}`,
predicate
};
}
function phases(name, resolve, reject) {
return {
id: `phases-${name}`,
initial: 'before',
states: {
before: {
invoke: {
src: `${name}-before`,
onDone: 'perform',
onError: 'reject'
}
},
perform: {
invoke: {
src: `${name}-perform`,
onDone: 'after',
onError: 'reject'
}
},
after: {
invoke: {
src: `${name}-after`,
onDone: 'resolve',
onError: 'reject'
}
},
resolve: { type: 'final' },
reject: { type: 'final' }
},
onDone: [
{ target: resolve, cond: matchLeafState('resolve') },
{ target: reject, cond: matchLeafState('reject') }
]
};
}
const WAIT_LIMIT = 3;
const RETRY_LIMIT = 3;
// TODO
// - Wire up `reject`.
function timeout(name, completeEvent, abort, retry, wait, resolve, reject) {
return {
id: `timeout-${name}`,
initial: 'reset',
states: {
reset: {
on: {
'': {
target: 'unloaded',
actions: [
xstate.assign({ timeoutFinalState: '' })
]
},
[completeEvent]: {
target: 'resolve'
}
}
},
unloaded: {
on: {
'': [
{ target: 'abort', cond: matchThroughState('timeoutFinalState', 'abort') },
{ target: 'retry', cond: matchThroughState('timeoutFinalState', 'retry') },
{ target: 'wait', cond: matchThroughState('timeoutFinalState', 'wait') },
{ target: 'resolve', cond: matchThroughState('timeoutFinalState', 'fail') }
],
'LOAD_TIMEOUT': 'loadTimeout',
[completeEvent]: {
target: 'resolve'
}
}
},
loadTimeout: {
on: {
'COMPLETE': 'loaded',
[completeEvent]: {
actions: [
xstate.assign({ timeoutFinalState: 'resolve' })
]
}
}
},
loaded: {
on: {
'ABORT': {
actions: [
(context) => { context.timeoutFinalState = context.timeoutFinalState || 'abort'; }
],
target: 'unloadTimeout'
},
'RETRY': {
cond: namedCondition('RETRY_LIMIT', (context) => { return context.retryCount < RETRY_LIMIT; }),
actions: [
(context) => { context.waitCount = 0; },
(context) => { context.retryCount = context.retryCount + 1; },
(context) => { context.timeoutFinalState = context.timeoutFinalState || 'retry'; }
],
target: 'unloadTimeout'
},
'WAIT': {
cond: namedCondition('WAIT_LIMIT', (context) => { return context.waitCount < WAIT_LIMIT; }),
actions: [
(context) => { context.waitCount = context.waitCount + 1; },
(context) => { context.timeoutFinalState = context.timeoutFinalState || 'wait'; }
],
target: 'unloadTimeout'
},
[completeEvent]: {
actions: [
xstate.assign({ timeoutFinalState: 'resolve' })
],
target: 'unloadTimeout'
}
}
},
unloadTimeout: {
on: {
'COMPLETE': 'unloaded',
[completeEvent]: {
actions: [
xstate.assign({ timeoutFinalState: 'resolve' })
]
}
}
},
abort: { type: 'final' },
retry: { type: 'final' },
wait: { type: 'final' },
resolve: { type: 'final' },
reject: { type: 'final' }
},
onDone: [
{ target: abort, cond: matchLeafState('abort') },
{ target: retry, cond: matchLeafState('retry') },
{ target: wait, cond: matchLeafState('wait') },
{ target: resolve, cond: matchLeafState('resolve') },
{ target: reject, cond: matchLeafState('reject') }
]
};
}
function loadWindow(name, abort, retry, resolve, reject ) {
return {
id: `load-window-${name}`,
initial: 'insertWindow',
states: {
insertWindow: {
on: {
'COMPLETE': 'awaitWindow'
}
},
awaitWindow: {
after: {
10000: 'timeout'
},
on: {
'TIMEOUT': 'timeout',
'LOADED': 'resolve'
}
},
timeout: timeout(name, 'WINDOW_LOADED', 'abort', 'retry', 'wait', 'resolve', 'reject'),
wait: {
on: {
'': {
target: 'awaitWindow'
}
}
},
abort: { type: 'final' },
retry: { type: 'final' },
resolve: { type: 'final' },
reject: { type: 'final' }
},
onDone: [
{ target: abort, cond: matchLeafState('abort') },
{ target: retry, cond: matchLeafState('retry') },
{ target: resolve, cond: matchLeafState('resolve') },
{ target: reject, cond: matchLeafState('reject') }
]
};
}
function unloadWindow(name, resolve, reject) {
return {
id: `unload-window-${name}`,
initial: 'removeWindow',
states: {
removeWindow: {
on: {
'COMPLETE': 'resolve'
}
},
resolve: { type: 'final' },
reject: { type: 'final' }
},
onDone: [
{ target: resolve, cond: matchLeafState('resolve') },
{ target: reject, cond: matchLeafState('reject') }
]
};
}
function windowController(name) {
return {
id: `window-controller-${name}`,
initial: 'reset',
states: {
reset: {
on: {
'': {
target: 'unloaded',
actions: [
xstate.assign({ windowRetryLoad: false })
]
}
}
},
unloaded: {
on: {
'': {
target: 'loadWindow',
cond: matchThroughState('windowRetryLoad', true)
},
'LOAD_WINDOW': 'loadWindow',
'EXIT': 'resolve'
}
},
loadWindow: loadWindow(name, 'abortLoad', 'retryLoad', 'proceed', 'reject'),
abortLoad: { type: 'final' },
retryLoad: {
on: {
'': {
target: 'unloadWindow',
actions: [
xstate.assign({ windowRetryLoad: true })
]
}
}
},
proceed: {
on: {
'': {
target: 'background',
actions: [
xstate.assign({ windowRetryLoad: false })
]
}
}
},
background: {
on: {
'FOREGROUND_WINDOW': 'foregroundWindow',
'UNLOAD_WINDOW': 'unloadWindow'
}
},
foregroundWindow: {
on: {
'COMPLETE': 'foreground'
}
},
unloadWindow: unloadWindow(name, 'unloaded', 'reject'),
foreground: {
on: {
'BACKGROUND_WINDOW': 'backgroundWindow'
}
},
backgroundWindow: {
on: {
'COMPLETE': 'background'
}
},
resolve: { type: 'final' },
reject: { type: 'final' }
}
};
}
function windowManager() {
return {
id: 'window-manager',
initial: 'windowControllers',
states: {
idle: {
on: {
'OPEN': {
target: 'opening'
},
'CLOSE': {
target: 'closing'
},
'FOCUS': {
target: 'focusing'
}
}
},
opening: {
on: {
'COMPLETE': {
target: 'idle'
}
}
},
closing: {
on: {
'COMPLETE': {
target: 'idle'
}
}
},
focusing: {
on: {
'COMPLETE': {
target: 'idle'
}
}
},
windowControllers: {
id: 'window-controllers',
type: 'parallel',
states: {
one: windowController('one')
// two: windowMachine('two')
}
}
}
};
}
function application(name) {
return {
id: `application-${name}`,
initial: 'load',
context: {
timeoutFinalState: '',
retryCount: 0,
waitCount: 0,
windowRetryLoad: false
},
states: {
load: phases('load', 'boot'),
boot: phases('boot', 'ready'),
ready: {
id: 'ready',
type: 'parallel',
states: {
windowManager: windowManager(),
inputManager: {}
}
}
}
};
}
const options = {
services: {
'load-before': () => {
return Promise.resolve().then(function() {
console.log('do load before');
});
},
'load-perform': () => {
return Promise.resolve().then(function() {
console.log('do load start');
});
},
'load-after': () => {
return Promise.resolve().then(function() {
console.log('do load after');
});
},
'boot-before': () => {
return Promise.resolve().then(function() {
console.log('do boot before');
});
},
'boot-perform': () => {
return Promise.resolve().then(function() {
console.log('do boot start');
});
},
'boot-after': () => {
return Promise.resolve().then(function() {
console.log('do boot after');
});
}
}
};
const applicationMachine = xstate.Machine(application('web-os'), options);
const applicationService = xstate.interpret(applicationMachine);
applicationService.start();
}({ Machine, interpret, assign }, { DEFAULT_GUARD_TYPE: 'xstate.guard', STATE_DELIMITER: '.' }));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment