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