Skip to content

Instantly share code, notes, and snippets.

@yllan
Last active November 5, 2021 06:28
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 yllan/42d296f72796cbebb9d9529cee452d57 to your computer and use it in GitHub Desktop.
Save yllan/42d296f72796cbebb9d9529cee452d57 to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
const IDX = {
VHS: { P2: 6, P3: 7, P4: 8, P5: 9, P6: 10 }
}
const nudgePoints = (elementIndexes) => assign((ctx, evt) => {
const [x, y] = [evt.clientX, evt.clientY]
const [sx, sy] = ctx.lastClientPoint
const [dx, dy] = [x - sx, y - sy]
const paths = elementIndexes.map(idx => [0, ctx.groupIndex, idx])
ctx.editorMethods.nudgeElementsAtPath(dx, dy, paths)
return { ...ctx, lastClientPoint: [x, y] }
})
const setPoints = (elementIndexes) => assign((ctx, evt) => {
const [vx, vy] = [evt.offsetX, evt.offsetY] // viewport
const [x, y] = multiply(ctx.invMatrix, [vx, vy, 1]) // dataspace
const paths = elementIndexes.map(idx => [0, ctx.groupIndex, idx])
ctx.editorMethods.moveCirclesAtPathTo(x, y, paths)
return { ...ctx, lastClientPoint: [evt.clientX, evt.clientY] }
})
/* Finite State Machine definition */
// https://xstate.js.org/viz/?gist=42d296f72796cbebb9d9529cee452d57
const editMachine = Machine({
id: 'roiEdit',
initial: 'idle',
context: {
// dependencies
mode: 'readonly',
invMatrix: [],
editorMethods: {},
groupsBehaviors: {},
onUpdateCallback: () => { },
// variables
lastClientPoint: [0, 0],
groupIndex: 0,
colorIndex: 0,
colors: [
'#01f601', // green
'#ffb80a', // yellow
'#f35252', // red
'#0086ff' // blue
]
},
states: {
idle: {
on: {
mousemove: [
{
actions: 'highlightElement',
cond: 'hitElement'
},
{
actions: 'clearHighlights'
}
],
mousedown: [
{
actions: ['selectElement', 'setLastClientPoint'],
target: 'selection',
cond: 'hitElement'
},
{
actions: ['createSimpleShape', 'setLastClientPoint'],
target: 'simple_shape_creation',
cond: 'isSimpleShape'
},
{
actions: ['createAngle', 'setLastClientPoint'],
target: 'angle_creation',
cond: 'isAngle'
},
{
actions: ['createVHS', 'setLastClientPoint'],
target: 'vhs_creation',
cond: 'isVHS'
},
{
actions: ['createText', 'setLastClientPoint'],
target: 'decide_text_position',
cond: 'isText'
}
],
dblclick: [{
actions: ['selectElement', 'setGroupIndex', 'prepareTextInput'],
target: 'edit_text',
cond: 'targetIsText'
}],
delete: {
actions: 'deleteSelectedROIs'
}
}
},
selection: {
on: {
mousemove: { // nudge element
actions: 'nudgeSelection',
target: 'selection_dragged'
},
mouseup: { // just click, not dragged
target: 'idle'
}
}
},
selection_dragged: {
on: {
mousemove: {
actions: 'nudgeSelection'
},
mouseup: {
actions: 'saveROI',
target: 'idle'
}
}
},
vhs_creation: {
initial: 'p1_commited',
states: {
p1_commited: { // 第一個點決定好位置
on: {
mousemove: {
actions: 'nudgeVHSP2',
target: 'p2_moving'
}
}
},
p2_moving: { // 移動第二個點
on: {
mousemove: { actions: 'nudgeVHSP2' },
mouseup: { target: 'p2_commited' }
}
},
p2_commited: {
on: {
mousedown: {
actions: 'setVHSP3P4',
target: 'p3_commited'
}
}
},
p3_commited: {
on: {
mousemove: {
actions: 'nudgeVHSP4',
target: 'p4_moving'
}
}
},
p4_moving: {
on: {
mousemove: { actions: 'nudgeVHSP4' },
mouseup: { target: 'p4_commited' }
}
},
p4_commited: {
on: {
mousedown: {
actions: 'setVHSP5P6',
target: 'p5_commited'
}
}
},
p5_commited: {
on: {
mousemove: {
actions: 'nudgeVHSP6',
target: 'p6_moving'
}
}
},
p6_moving: {
on: {
mousemove: { actions: 'nudgeVHSP6' },
mouseup: { // 結束編輯, 儲存
actions: ['saveROI', 'clearSelection', send('saved')]
}
}
}
},
on: {
saved: { target: 'idle' }
}
},
angle_creation: {
initial: 'p1_commited',
states: {
p1_commited: {
on: {
mousemove: {
actions: 'nudgeAngleP2P3',
target: 'p2_moving'
}
}
},
p2_moving: {
on: {
mousemove: { actions: 'nudgeAngleP2P3' },
mouseup: { target: 'p2_commited' }
}
},
p2_commited: {
on: {
mousemove: { actions: 'nudgeAngleP3' },
mouseup: { actions: ['saveROI', 'clearSelection', send('saved')] }
}
}
},
on: {
saved: { target: 'idle' }
}
},
simple_shape_creation: {
initial: 'p1_commited',
states: {
p1_commited: {
on: {
mousemove: {
actions: 'nudgeSelection',
target: 'p2_moving'
},
mouseup: {
actions: ['saveROI', 'clearSelection', send('saved')],
cond: 'isLabel'
}
}
},
p2_moving: {
on: {
mousemove: { actions: 'nudgeSelection' },
mouseup: { actions: ['saveROI', 'clearSelection', send('saved')] }
}
}
},
on: {
saved: { target: 'idle' }
}
},
decide_text_position: {
on: {
mousemove: { actions: 'nudgeSelection' },
mouseup: { actions: 'prepareTextInput', target: 'edit_text' }
}
},
edit_text: {
initial: 'normal_mode',
states: {
normal_mode: {
on: {
input: { actions: 'handleInput' },
close: [
{
actions: ['discardROI', 'clearSelection', send('discard')],
cond: 'emptyText'
},
{
actions: ['saveROI', 'clearSelection', send('saved')]
}
],
begin_ime: { target: 'ime_mode' }
}
},
ime_mode: {
on: {
input: { actions: 'handleInput' },
end_ime: { target: 'normal_mode' }
}
}
},
on: {
saved: { target: 'idle' },
discard: { target: 'idle' }
}
}
},
on: {
// 更新 props 時傳進來 machine
UPDATE: { actions: assign((ctx, evt) => ({ ...ctx, ...evt.content })) }
}
}, {
actions: {
setLastClientPoint: (ctx, evt) => { /*STUB*/ },
selectElement: (ctx, evt) => { /*STUB*/ },
setGroupIndex: (ctx, evt) => { /*STUB*/ },
clearHighlights: (ctx) => { /*STUB*/ },
highlightElement: (ctx, evt) => { /*STUB*/ },
nudgeSelection: (ctx, evt) => { /*STUB*/ },
createSimpleShape: (ctx, evt) => { /*STUB*/ },
createText: (ctx, evt) => { /*STUB*/ },
prepareTextInput: (ctx) => { /*STUB*/ },
handleInput: (ctx, evt) => { /*STUB*/ },
clearSelection: (ctx) => { /*STUB*/ },
deleteSelectedROIs: (ctx) => { /*STUB*/ },
saveROI: (ctx) => { /*STUB*/ },
discardROI: (ctx) => { /*STUB*/ },
createAngle: (ctx, evt) => { /*STUB*/ },
nudgeVHSP2: nudgePoints([IDX.VHS.P2]),
nudgeVHSP4: nudgePoints([IDX.VHS.P4]),
nudgeVHSP6: nudgePoints([IDX.VHS.P6]),
setVHSP3P4: setPoints([IDX.VHS.P3, IDX.VHS.P4]),
setVHSP5P6: setPoints([IDX.VHS.P5, IDX.VHS.P6]),
nudgeAngleP2P3: nudgePoints([3, 4]),
nudgeAngleP3: nudgePoints([4])
},
guards: {
hitElement: (ctx, evt) => true,
isVHS: (ctx) => ctx.mode === 'vhs',
isAngle: (ctx) => ctx.mode === 'angle',
isText: (ctx) => ctx.mode === 'text',
isLabel: (ctx) => ctx.mode === 'label',
isSimpleShape: (ctx) => new Set(['line', 'arrow', 'ellipse', 'label']).has(ctx.mode),
emptyText: (ctx) => ctx.textValue === '',
targetIsText: (ctx, evt) => false
}
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment