Skip to content

Instantly share code, notes, and snippets.

@jmakeig
Last active January 4, 2020 03:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jmakeig/2e6fe8293d7153fca53e2c90a142df4f to your computer and use it in GitHub Desktop.
Save jmakeig/2e6fe8293d7153fca53e2c90a142df4f to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
function clone(...rest) {
return Object.assign({}, ...rest);
}
/**
* Builder for confirmation states.
*
* @param {String} onConfirm State to transition to on confirmation
* @param {String} onCancel State to transition to on cancel
*/
const confirming = (onConfirm, onCancel) => ({
confirming: {
invoke: {
src: 'confirmCancelService',
onDone: {
target: onConfirm,
actions: [
assign({
annotation: (context, event) => clone(context.cache)
}),
assign({ cache: (context, event) => null })
]
},
onError: { target: onCancel }
}
}
});
/**
* Builder for cancel transitions that guard against losing dirty data.
*
* @param {String} name The name of the current transition
* @param {String} next The state to transition to if no confirmation is needed
*/
const cancel = (name, next) => ({
[name]: [
{
target: next,
cond: 'dirtyGuard'
},
{
target: 'confirming',
cond: 'needsConfirmation'
}
]
});
const annotation = {
id: 'ANN12345',
user: 'jmakeig',
comment: 'Here is some text',
timestamp: '2019-12-16T22:14:28.872Z',
range: {
start: { line: 3, column: 120 },
end: { line: 3, column: 500 }
},
isActive: false
};
const initialContext = {
id: null,
annotation,
errorMessage: null,
cache: null
};
const config = {
strict: true,
id: 'annotation',
context: initialContext,
initial: 'unselected',
states: {
unselected: {
entry: assign({
annotation: (context, event) => ({
...context.annotation,
isActive: false
})
}),
on: {
select: {
target: 'selected'
}
}
},
selected: {
initial: 'viewing',
entry: assign({
annotation: (context, event) => ({
...context.annotation,
isActive: true
})
}),
states: {
viewing: {
on: {
edit: 'editing'
}
},
editing: {
initial: 'clean',
on: {
...cancel('cancel', 'viewing')
},
states: {
clean: {
on: {
change: {
target: 'dirty',
actions: ['cache', 'updateAnnotation']
}
}
},
dirty: {
on: {
change: {
target: 'dirty',
actions: ['updateAnnotation']
},
save: 'saving',
...cancel('revert', 'clean')
}
},
...confirming('clean', 'dirty'),
saving: {
invoke: {
id: 'saveAnnotation',
src: 'saveAnnotationService',
onDone: {
target: '#annotation.selected.viewing',
actions: assign({
annotation: (context, event) => event.data
})
},
onError: {
actions: assign({
errorMessage: (context, event) => event.data
})
}
}
}
}
},
...confirming('viewing', 'editing.dirty')
},
on: {
...cancel('blur', 'unselected')
}
},
...confirming('unselected', 'selected.editing.dirty')
}
};
function dirtyGuard(context, event, meta) {
if (meta && meta.state) {
//console.log(m.state.value);
return meta.state.value.selected.editing !== 'dirty';
}
return true;
}
function confirmCancel(message = 'You sure?') {
return new Promise(function(resolve, reject) {
if (window.confirm(message)) {
resolve();
} else {
reject();
}
});
}
const options = {
actions: {
updateAnnotation: assign({
annotation: (context, event) =>
// This is an awkward way to update the `comment` property
Object.assign({}, context.annotation, {
comment: context.annotation.comment + 'X'
})
}),
cache: assign({
cache: (context, event) =>
context.cache === null ? clone(context.annotation) : context.cache
})
},
services: {
confirmCancelService: (context, event) => confirmCancel(),
saveAnnotationService: (context, event) => Promise.resolve({})
},
guards: {
dirtyGuard(c, e, m) {
return dirtyGuard(c, e, m);
},
needsConfirmation(c, e, m) {
return !dirtyGuard(c, e, m);
}
}
};
const machine = Machine(config, options);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment