Skip to content

Instantly share code, notes, and snippets.

@hnordt
Last active July 20, 2022 19:49
Show Gist options
  • Save hnordt/aa62a863e07ae050806ddc6372379f17 to your computer and use it in GitHub Desktop.
Save hnordt/aa62a863e07ae050806ddc6372379f17 to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
Machine({
id: "widget",
context: {
id: null,
settings: {
name: null,
refreshInterval: 30,
waitDelay: 5, // TODO: make editable
dataSource: null,
series: null,
timeframe: null,
interval: null,
visualizationType: null,
legendPosition: null,
defaultName: "Untitled",
defaultRefreshInterval: 30,
defaultSeries: {},
defaultTimeframe: "today",
defaultIntervalByTimeframe: {
today: "15m",
yesterday: "15m",
thisWeek: "1d",
thisMonth: "1d",
last7Days: "1d",
last30Days: "1d",
last60Days: "1d"
},
defaultVisualizationType: "barChart",
defaultLegendPosition: "bottom",
enableIntervalFor: ["thisWeek"],
enableLegendFor: ["pieChart"]
},
data: [],
error: null,
nextRefresh: 0
},
initial: "initializing",
states: {
initializing: {
entry: "initializeSettings",
on: {
"": "ready"
}
},
ready: {
type: "parallel",
states: {
settings: {
type: "parallel",
states: {
general: {
entry: ["resetName", "resetRefreshInterval"],
on: {
CHANGE_NAME: {
actions: "updateName"
},
CHANGE_REFRESH_INTERVAL: {
actions: "updateRefreshInterval"
},
BLUR_NAME: {
actions: "resetName"
}
}
},
dataSource: {
initial: "empty",
states: {
empty: {
on: {
// FIXME: https://github.com/davidkpiano/xstate/issues/673
// CHANGE_DATA_SOURCE: "selected"
CHANGE_DATA_SOURCE: {
target: "selected",
actions: "updateDataSource"
}
}
},
selected: {
// FIXME: https://github.com/davidkpiano/xstate/issues/673
// entry: "updateDataSource",
exit: "cleanupDataSource",
type: "parallel",
states: {
series: {
exit: "cleanupSeries",
initial: "pristine",
states: {
pristine: {
entry: "resetSeries"
},
dirty: {
// FIXME: https://github.com/davidkpiano/xstate/issues/673
// entry: "toggleSerie"
}
},
on: {
// FIXME: https://github.com/davidkpiano/xstate/issues/673
// TOGGLE_SERIE: "series.dirty"
TOGGLE_SERIE: {
target: ".dirty",
actions: "toggleSerie"
}
}
},
timeframe: {
exit: "cleanupTimeframe",
initial: "pristine",
states: {
pristine: {
entry: "resetTimeframe"
},
dirty: {
// FIXME: https://github.com/davidkpiano/xstate/issues/673
// entry: "updateTimeframe"
}
},
on: {
// FIXME: https://github.com/davidkpiano/xstate/issues/673
// CHANGE_TIMEFRAME: "timeframe.dirty"
CHANGE_TIMEFRAME: {
target: ".dirty",
actions: "updateTimeframe"
}
}
},
interval: {
exit: "cleanupInterval",
initial: "init",
states: {
init: {
on: {
"": [
{
target: "pristine",
cond: "canChangeInterval"
},
{
target: "disabled"
}
]
}
},
disabled: {
entry: "resetInterval"
},
pristine: {
entry: "resetInterval"
},
dirty: {
// entry: "updateInterval"
}
},
on: {
CHANGE_TIMEFRAME: ".init",
// FIXME: https://github.com/davidkpiano/xstate/issues/673
// CHANGE_INTERVAL: "interval.dirty"
CHANGE_INTERVAL: {
target: ".dirty",
actions: "updateInterval"
}
}
},
visualizationType: {
exit: "cleanupVisualizationType",
initial: "pristine",
states: {
pristine: {
entry: "resetVisualizationType"
},
dirty: {
// entry: "updateVisualizationType"
}
},
on: {
// FIXME: https://github.com/davidkpiano/xstate/issues/673
// CHANGE_VISUALIZATION_TYPE: "visualizationType.dirty"
CHANGE_VISUALIZATION_TYPE: {
target: ".dirty",
actions: "updateVisualizationType"
}
}
},
legendPosition: {
exit: "cleanupLegendPosition",
initial: "init",
states: {
init: {
on: {
"": [
{
target: "pristine",
cond: "canChangeLegendPosition"
},
{
target: "hidden"
}
]
}
},
hidden: {
entry: "cleanupLegendPosition"
},
pristine: {
entry: "resetLegendPosition"
},
dirty: {
// FIXME: https://github.com/davidkpiano/xstate/issues/673
// entry: "updateLegendPosition"
}
},
on: {
CHANGE_VISUALIZATION_TYPE: ".init",
// FIXME: https://github.com/davidkpiano/xstate/issues/673
// CHANGE_LEGEND_POSITION: "legendPosition.dirty"
CHANGE_LEGEND_POSITION: {
target: ".dirty",
actions: "updateLegendPosition"
}
}
}
},
on: {
CHANGE_DATA_SOURCE: {
actions: "updateDataSource"
},
CLEANUP_DATA_SOURCE: "empty"
}
}
}
}
}
},
data: {
initial: "loading",
states: {
loading: {
invoke: {
src: "getData",
onDone: "success",
onError: "failure"
}
},
success: {
invoke: {
src: "tick"
},
initial: "wait",
states: {
wait: {
entry: ["setData", "resetNextRefresh"],
after: {
WAIT_DELAY: "idle"
},
initial: "waiting",
states: {
waiting: {
on: {
REFRESH: "blocked"
}
},
blocked: {
after: {
2000: "waiting"
},
on: {
REFRESH: "blocked"
}
}
}
},
idle: {
after: {
REFRESH_DELAY: "refreshing"
},
on: {
REFRESH: "refreshing"
}
},
refreshing: {
invoke: {
src: "getData",
onDone: "wait",
onError: "failure"
}
},
failure: {
on: {
RETRY: "refreshing"
}
}
},
on: {
TICK: {
actions: "updateNextRefresh"
}
}
},
failure: {
entry: "setError",
exit: "resetError",
on: {
RETRY: "loading"
}
}
}
}
}
}
},
on: {
UPDATE_SETTINGS: {
target: "ready.data.loading",
actions: ["updateSettings", "storeSettings"]
},
REMOVE: {
actions: "cleanupSettings"
}
}
},
{
guards: {
canChangeInterval: ctx => {
return ctx.settings.enableIntervalFor.includes(ctx.settings.timeframe)
},
canChangeLegendPosition: ctx => {
return ctx.settings.enableLegendFor.includes(
ctx.settings.visualizationType
)
}
},
delays: {
REFRESH_DELAY: ctx => {
return (ctx.settings.refreshInterval - ctx.settings.waitDelay) * 1000
},
WAIT_DELAY: ctx => {
return ctx.settings.waitDelay * 1000
}
},
actions: {
initializeSettings: assign({
settings: ctx => {
try {
return (
JSON.parse(localStorage.getItem(`widget-${ctx.id}`)) ||
ctx.settings
)
} catch {
return ctx.settings
}
}
}),
updateSettings: assign({
settings: (_, e) => e.payload.settings
}),
storeSettings: ctx => {
localStorage.setItem(`widget-${ctx.id}`, JSON.stringify(ctx.settings))
},
cleanupSettings: ctx => {
localStorage.removeItem(`widget-${ctx.id}`)
},
updateName: assign({
settings: (ctx, e) => ({
...ctx.settings,
name: e.payload
})
}),
updateRefreshInterval: assign({
settings: (ctx, e) => ({
...ctx.settings,
refreshInterval: e.payload
})
}),
updateDataSource: assign({
settings: (ctx, e) => ({
...ctx.settings,
dataSource: e.payload
})
}),
toggleSerie: assign({
settings: (ctx, e) => ({
...ctx.settings,
series: {
...ctx.series,
[e.payload]: !ctx.series[e.payload]
}
})
}),
updateTimeframe: assign({
settings: (ctx, e) => ({
...ctx.settings,
timeframe: e.payload
})
}),
updateInterval: assign({
settings: (ctx, e) => ({
...ctx.settings,
interval: e.payload
})
}),
updateVisualizationType: assign({
settings: (ctx, e) => ({
...ctx.settings,
visualizationType: e.payload
})
}),
updateLegendPosition: assign({
settings: (ctx, e) => ({
...ctx.settings,
legendPosition: e.payload
})
}),
// TODO: rename to ensureName?
resetName: assign({
settings: ctx => ({
...ctx.settings,
name: ctx.name || ctx.defaultName
})
}),
// TODO: rename to ensureRefreshInterval?
resetRefreshInterval: assign({
settings: ctx => ({
...ctx.settings,
refreshInterval: ctx.refreshInterval || ctx.defaultRefreshInterval
})
}),
resetSeries: assign({
settings: ctx => ({
...ctx.settings,
series: ctx.defaultSeries
})
}),
resetTimeframe: assign({
settings: ctx => ({
...ctx.settings,
timeframe: ctx.defaultTimeframe
})
}),
resetInterval: assign({
settings: ctx => ({
...ctx.settings,
interval: ctx.defaultIntervalByTimeframe[ctx.timeframe]
})
}),
resetVisualizationType: assign({
settings: ctx => ({
...ctx.settings,
visualizationType: ctx.defaultVisualizationType
})
}),
resetLegendPosition: assign({
settings: ctx => ({
...ctx.settings,
legendPosition: ctx.defaultLegendPosition
})
}),
cleanupDataSource: assign({
settings: ctx => ({
...ctx.settings,
dataSource: null
})
}),
cleanupSeries: assign({
settings: ctx => ({
...ctx.settings,
series: null
})
}),
cleanupTimeframe: assign({
settings: ctx => ({
...ctx.settings,
timeframe: null
})
}),
cleanupInterval: assign({
settings: ctx => ({
...ctx.settings,
interval: null
})
}),
cleanupVisualizationType: assign({
settings: ctx => ({
...ctx.settings,
visualizationType: null
})
}),
cleanupLegendPosition: assign({
settings: ctx => ({
...ctx.settings,
legendPosition: null
})
}),
setData: assign({
data: (_, e) => e.data
}),
setError: assign({
error: (_, e) => e.data
}),
resetError: assign({
error: null
}),
updateNextRefresh: assign({
nextRefresh: ctx => ctx.nextRefresh - 1
}),
resetNextRefresh: assign({
nextRefresh: ctx => ctx.settings.refreshInterval
})
},
services: {
tick: () => callback => {
let id = setInterval(() => callback("TICK"), 1000)
return () => clearInterval(id)
},
getData: ctx => new Promise(r => setTimeout(r, 4000))
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment