Created
May 12, 2020 11:36
-
-
Save mrsln/17ca3865b2e461b55afe506a45d39315 to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
This file contains hidden or 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 AppMachine = Machine( | |
{ | |
id: "appmachine", | |
type: "parallel", | |
context: { | |
template: {}, | |
param: {}, | |
rect: {}, | |
}, | |
states: { | |
rect: { | |
idle: { | |
on: { | |
RESIZE: "resizing", | |
MOVE: "moving", | |
}, | |
states: { | |
resizing: {}, | |
moving: {}, | |
}, | |
}, | |
}, | |
app: { | |
states: { | |
loading: { | |
after: { | |
LOADING_TIMEOUT: "failure.timeout", | |
}, | |
on: { | |
RESOLVE: "loaded", | |
REJECT: "failure", | |
}, | |
}, | |
loaded: { | |
on: { | |
UPDATE_SETTINGS: { | |
internal: true, | |
actions: assign({ | |
param: (event) => event.param, | |
}), | |
}, | |
}, | |
}, | |
failure: { | |
initial: "rejection", | |
states: { | |
rejection: { | |
meta: { | |
errorMessage: "Loading the app has failed.", | |
}, | |
}, | |
timeout: { | |
meta: { | |
errorMessage: "Loading the app has timed out.", | |
}, | |
}, | |
}, | |
meta: { | |
error: true, | |
}, | |
}, | |
}, | |
}, | |
}, | |
}, | |
{ | |
delays: { | |
LOADING_TIMEOUT: 10000, | |
}, | |
} | |
); | |
const DraggableMachine = Machine({ | |
id: "draggable", | |
initial: "dragging", | |
context: { | |
dx: 0, | |
dy: 0, | |
startX: 0, | |
startY: 0, | |
}, | |
states: { | |
dragging: { | |
on: { | |
mousemove: { | |
actions: sendParent((context, event) => ({ | |
type: "UPDATE_SELECTION", | |
dx: context.startX - event.x, | |
dy: context.startY - event.y, | |
})), | |
}, | |
mouseup: { | |
target: "finish", | |
}, | |
}, | |
}, | |
finish: { | |
type: "final", | |
}, | |
}, | |
}); | |
const SettingsStates = { | |
id: "settings", | |
initial: "loading", | |
states: { | |
loading: { | |
after: { | |
LOADING_TIMEOUT: "failure.timeout", | |
}, | |
on: { | |
RESOLVE: "loaded", | |
REJECT: "failure", | |
}, | |
}, | |
loaded: { | |
on: { | |
UPDATE: { | |
actions: ["updateSelectedAppSettings"], | |
}, | |
}, | |
}, | |
failure: { | |
initial: "rejection", | |
on: { | |
RETRY: "loading", | |
}, | |
states: { | |
rejection: { | |
meta: { | |
errorMessage: "Loading settings has failed.", | |
}, | |
}, | |
timeout: { | |
meta: { | |
errorMessage: "Loading settings has timed out.", | |
}, | |
}, | |
}, | |
meta: { | |
error: true, | |
}, | |
}, | |
}, | |
}; | |
const MIN_SIZE = 0.01; | |
const StateMachine = Machine( | |
{ | |
id: "studio", | |
initial: "idle", | |
context: { | |
selection: { | |
x: 0.01, | |
y: 0.01, | |
startX: 0, | |
startY: 0, | |
width: 0.4, | |
height: 0.4, | |
resizingPart: "bottomRightCorner", | |
appId: "", | |
}, | |
draggingItem: { | |
offsetX: 0, | |
offsetY: 0, | |
startX: 0, | |
startY: 0, | |
x: 0.01, | |
y: 0.01, | |
app: {}, | |
}, | |
appOverlay: { | |
x: 0, | |
y: 0, | |
width: 0, | |
height: 0, | |
}, | |
apps: [], | |
}, | |
states: { | |
idle: { | |
on: { | |
mousedown: { | |
target: "resizing", | |
actions: "startResizing", | |
}, | |
mousedownOnMedia: { | |
target: "draggingMedia", | |
actions: "startDraggingMedia", | |
}, | |
canvasMouseDown: [ | |
{ | |
cond: "isWithinApp", | |
actions: ["selectApp", "startResizing"], | |
target: "resizing", | |
}, | |
{ | |
cond: "isNotWithinApp", | |
target: ".idle", | |
}, | |
], | |
mousemove: [ | |
{ cond: "isWithinApp", actions: "displayAppOverlay" }, | |
{ cond: "isNotWithinApp", actions: "hideAppOverlay" }, | |
], | |
deleteApp: { | |
actions: ["deleteApp", "hideAppOverlay"], | |
target: ".idle", | |
}, | |
}, | |
initial: "idle", | |
states: { | |
idle: { | |
entry: "reset", | |
}, | |
selected: { | |
internal: true, | |
...SettingsStates, | |
}, | |
}, | |
}, | |
resizing: { | |
invoke: { | |
id: "drag", | |
src: DraggableMachine, | |
autoForward: true, | |
onDone: { target: "idle.selected", actions: "updateAppOverlay" }, | |
data: { | |
startX: (_context, event) => event.x, | |
startY: (_context, event) => event.y, | |
}, | |
}, | |
on: { | |
UPDATE_SELECTION: { | |
actions: ["resize", "updateSelectedApp", "hideAppOverlay"], | |
}, | |
}, | |
}, | |
draggingMedia: { | |
invoke: { | |
id: "drag", | |
src: DraggableMachine, | |
autoForward: true, | |
onDone: { | |
target: "idle.selected", | |
actions: ["addApp", "selectLatestApp"], | |
}, | |
data: { | |
startX: (context) => context.draggingItem.startX, | |
startY: (context) => context.draggingItem.startY, | |
}, | |
}, | |
on: { | |
UPDATE_SELECTION: { | |
actions: "draggingMedia", | |
}, | |
}, | |
}, | |
}, | |
}, | |
{ | |
delays: { LOADING_TIMEOUT: 10000 }, | |
guards: { | |
isWithinApp: (context, event) => | |
!!getAppUnderMouse(context.apps, event.x, event.y), | |
isNotWithinApp: (context, event) => | |
!getAppUnderMouse(context.apps, event.x, event.y), | |
}, | |
actions: { | |
startResizing: assign((context, event) => { | |
return { | |
...context, | |
selection: { | |
...context.selection, | |
resizingPart: event.resizingPart, | |
startX: context.selection.x, | |
startY: context.selection.y, | |
startWidth: context.selection.width, | |
startHeight: context.selection.height, | |
}, | |
}; | |
}), | |
startDraggingMedia: assign((context, event) => { | |
return { | |
...context, | |
draggingItem: { | |
...context.draggingItem, | |
startX: event.x, | |
startY: event.y, | |
x: event.x - event.offsetX, | |
y: event.y - event.offsetY, | |
offsetX: event.offsetX, | |
offsetY: event.offsetY, | |
app: event.app, | |
}, | |
}; | |
}), | |
reset: assign((context) => ({ | |
...context, | |
selection: { | |
x: 0.02, | |
y: 0.02, | |
startX: 0, | |
startY: 0, | |
width: 0.4, | |
height: 0.4, | |
resizingPart: "bottomRightCorner", | |
}, | |
draggingItem: { | |
offsetX: 0, | |
offsetY: 0, | |
startX: 0, | |
startY: 0, | |
x: -100, | |
y: -100, | |
}, | |
})), | |
resize: assign((context, { dx, dy }) => { | |
if (context.selection.resizingPart === "bottomRightCorner") { | |
let newWidth = context.selection.startWidth - dx; | |
if (newWidth < MIN_SIZE) { | |
newWidth = MIN_SIZE; | |
} | |
if (context.selection.startX + newWidth > 1) { | |
newWidth = 1 - context.selection.startX; | |
} | |
let newHeight = context.selection.startHeight - dy; | |
if (newHeight < MIN_SIZE) { | |
newHeight = MIN_SIZE; | |
} | |
if (context.selection.startY + newHeight > 1) { | |
newHeight = 1 - context.selection.startY; | |
} | |
return { | |
...context, | |
selection: { | |
...context.selection, | |
width: newWidth, | |
height: newHeight, | |
}, | |
}; | |
} else if (context.selection.resizingPart === "topLeftCorner") { | |
let newWidth = context.selection.startWidth + dx; | |
let newHeight = context.selection.startHeight + dy; | |
let newX = context.selection.startX - dx; | |
let newY = context.selection.startY - dy; | |
if (newWidth < MIN_SIZE) { | |
newWidth = MIN_SIZE; | |
newX = | |
context.selection.startX + | |
context.selection.startWidth - | |
newWidth; | |
} | |
if (newHeight < MIN_SIZE) { | |
newHeight = MIN_SIZE; | |
newY = | |
context.selection.startY + | |
context.selection.startHeight - | |
newHeight; | |
} | |
if (newX < 0) { | |
newX = 0; | |
newWidth = context.selection.startX + context.selection.startWidth; | |
} | |
if (newY < 0) { | |
newY = 0; | |
newHeight = | |
context.selection.startY + context.selection.startHeight; | |
} | |
return { | |
...context, | |
selection: { | |
...context.selection, | |
x: newX, | |
y: newY, | |
width: newWidth, | |
height: newHeight, | |
}, | |
}; | |
} else if (context.selection.resizingPart === "outline") { | |
let newX = context.selection.startX - dx; | |
if (newX < 0) { | |
newX = 0; | |
} | |
if (newX + context.selection.width > 1) { | |
newX = 1 - context.selection.width; | |
} | |
let newY = context.selection.startY - dy; | |
if (newY < 0) { | |
newY = 0; | |
} | |
if (newY + context.selection.height > 1) { | |
newY = 1 - context.selection.height; | |
} | |
return { | |
...context, | |
selection: { | |
...context.selection, | |
x: newX, | |
y: newY, | |
}, | |
}; | |
} else if (context.selection.resizingPart === "topRightCorner") { | |
let newWidth = context.selection.startWidth - dx; | |
if (newWidth < MIN_SIZE) { | |
newWidth = MIN_SIZE; | |
} | |
if (context.selection.startX + newWidth > 1) { | |
newWidth = 1 - context.selection.startX; | |
} | |
let newY = context.selection.startY - dy; | |
let newHeight = context.selection.startHeight + dy; | |
if (newHeight < MIN_SIZE) { | |
newHeight = MIN_SIZE; | |
newY = | |
context.selection.startY + | |
context.selection.startHeight - | |
newHeight; | |
} | |
if (newY < 0) { | |
newY = 0; | |
newHeight = | |
context.selection.startY + context.selection.startHeight; | |
} | |
return { | |
...context, | |
selection: { | |
...context.selection, | |
width: newWidth, | |
height: newHeight, | |
y: newY, | |
}, | |
}; | |
} else if (context.selection.resizingPart === "bottomLeftCorner") { | |
let newWidth = context.selection.startWidth + dx; | |
let newHeight = context.selection.startHeight - dy; | |
let newX = context.selection.startX - dx; | |
if (newHeight < MIN_SIZE) { | |
newHeight = MIN_SIZE; | |
} | |
if (newWidth < MIN_SIZE) { | |
newWidth = MIN_SIZE; | |
newX = | |
context.selection.startX + | |
context.selection.startWidth - | |
newWidth; | |
} | |
if (newX < 0) { | |
newX = 0; | |
newWidth = context.selection.startX + context.selection.startWidth; | |
} | |
if (context.selection.startY + newHeight > 1) { | |
newHeight = 1 - context.selection.startY; | |
} | |
return { | |
...context, | |
selection: { | |
...context.selection, | |
x: newX, | |
width: newWidth, | |
height: newHeight, | |
}, | |
}; | |
} else if (context.selection.resizingPart === "bottomOutline") { | |
let newHeight = context.selection.startHeight - dy; | |
if (newHeight < MIN_SIZE) { | |
newHeight = MIN_SIZE; | |
} | |
if (context.selection.startY + newHeight > 1) { | |
newHeight = 1 - context.selection.startY; | |
} | |
return { | |
...context, | |
selection: { | |
...context.selection, | |
height: newHeight, | |
}, | |
}; | |
} else if (context.selection.resizingPart === "leftOutline") { | |
let newWidth = context.selection.startWidth + dx; | |
let newX = context.selection.startX - dx; | |
if (newWidth < MIN_SIZE) { | |
newWidth = MIN_SIZE; | |
newX = | |
context.selection.startX + | |
context.selection.startWidth - | |
newWidth; | |
} | |
if (newX < 0) { | |
newX = 0; | |
newWidth = context.selection.startX + context.selection.startWidth; | |
} | |
return { | |
...context, | |
selection: { | |
...context.selection, | |
x: newX, | |
width: newWidth, | |
}, | |
}; | |
} else if (context.selection.resizingPart === "rightOutline") { | |
let newWidth = context.selection.startWidth - dx; | |
if (newWidth < MIN_SIZE) { | |
newWidth = MIN_SIZE; | |
} | |
if (context.selection.startX + newWidth > 1) { | |
newWidth = 1 - context.selection.startX; | |
} | |
return { | |
...context, | |
selection: { | |
...context.selection, | |
width: newWidth, | |
}, | |
}; | |
} else if (context.selection.resizingPart === "topOutline") { | |
let newHeight = context.selection.startHeight + dy; | |
let newY = context.selection.startY - dy; | |
if (newHeight < MIN_SIZE) { | |
newHeight = MIN_SIZE; | |
newY = | |
context.selection.startY + | |
context.selection.startHeight - | |
newHeight; | |
} | |
if (newY < 0) { | |
newY = 0; | |
newHeight = | |
context.selection.startY + context.selection.startHeight; | |
} | |
return { | |
...context, | |
selection: { | |
...context.selection, | |
y: newY, | |
height: newHeight, | |
}, | |
}; | |
} | |
console.error( | |
`${context.selection.resizingPart} is unknown resizing part` | |
); | |
}), | |
updateSelectedApp: assign({ | |
apps: (context) => { | |
const newApps = context.apps.map((app) => { | |
if (app.id !== context.selection.appId) return app; | |
const newApp = { ...app }; | |
newApp.rect = { | |
x: context.selection.x, | |
y: context.selection.y, | |
width: context.selection.width, | |
height: context.selection.height, | |
}; | |
return newApp; | |
}); | |
return newApps; | |
}, | |
}), | |
selectApp: assign({ | |
selection: (context, event) => { | |
const selectedApp = getAppUnderMouse(context.apps, event.x, event.y); | |
const { rect } = selectedApp; | |
return { | |
...context.selection, | |
x: rect.x, | |
y: rect.y, | |
width: rect.width, | |
height: rect.height, | |
appId: selectedApp.id, | |
}; | |
}, | |
}), | |
selectLatestApp: assign({ | |
selection: (context) => { | |
const selectedApp = context.apps[context.apps.length - 1]; | |
const { rect } = selectedApp; | |
return { | |
...context.selection, | |
x: rect.x, | |
y: rect.y, | |
width: rect.width, | |
height: rect.height, | |
appId: selectedApp.id, | |
}; | |
}, | |
}), | |
addApp: assign({ | |
apps: (context) => [ | |
...context.apps, | |
{ | |
id: uuidv4(), | |
template: context.draggingItem.app, | |
param: context.draggingItem.app.param, | |
rect: { | |
x: context.draggingItem.x, | |
y: context.draggingItem.y, | |
width: 0.2, | |
height: 0.2, | |
}, | |
ref: spawn(AppMachine), | |
}, | |
], | |
}), | |
deleteApp: assign({ | |
apps: (context, event) => { | |
const app = getAppUnderMouse(context.apps, event.x, event.y); | |
return context.apps.filter((a) => a !== app); | |
}, | |
}), | |
draggingMedia: assign((context, { dx, dy }) => { | |
let newX = | |
context.draggingItem.startX - dx - context.draggingItem.offsetX; | |
let newY = | |
context.draggingItem.startY - dy - context.draggingItem.offsetY; | |
return { | |
...context, | |
draggingItem: { | |
...context.draggingItem, | |
x: newX, | |
y: newY, | |
}, | |
}; | |
}), | |
displayAppOverlay: assign({ | |
appOverlay: (context, event) => { | |
const { rect } = getAppUnderMouse(context.apps, event.x, event.y); | |
return { | |
...context.appOverlay, | |
...rect, | |
}; | |
}, | |
}), | |
updateAppOverlay: assign({ | |
appOverlay: (context) => { | |
return { | |
...context.appOverlay, | |
x: context.selection.x, | |
y: context.selection.y, | |
width: context.selection.width, | |
height: context.selection.height, | |
}; | |
}, | |
}), | |
hideAppOverlay: assign({ | |
appOverlay: { x: 0, y: 0, width: 0, height: 0 }, | |
}), | |
updateSelectedAppSettings: assign({ | |
apps: (context, event) => { | |
debugger; | |
const newApps = context.apps.map((app) => { | |
if (app.id !== context.selection.appId) return app; | |
const newApp = { ...app, param: event.param }; | |
return newApp; | |
}); | |
return newApps; | |
}, | |
}), | |
}, | |
} | |
); | |
function getAppUnderMouse(apps, x, y) { | |
return apps | |
.slice() | |
.reverse() | |
.find( | |
({ rect }) => | |
x >= rect.x && | |
y >= rect.y && | |
x <= rect.x + rect.width && | |
y <= rect.y + rect.height | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment