tldraw/state/state.ts

945 lines
26 KiB
TypeScript
Raw Normal View History

2021-05-09 12:03:39 +00:00
import { createSelectorHook, createState } from "@state-designer/react"
import {
clamp,
getBoundsCenter,
2021-05-23 13:46:04 +00:00
getChildren,
getCommonBounds,
getPage,
getSelectedBounds,
getSelectedShapes,
getShape,
2021-05-23 13:46:04 +00:00
getSiblings,
screenToWorld,
setZoomCSS,
} from "utils/utils"
2021-05-09 21:22:25 +00:00
import * as vec from "utils/vec"
import {
Data,
PointerInfo,
Shape,
ShapeType,
Corner,
Edge,
2021-05-17 10:01:11 +00:00
CodeControl,
2021-05-23 17:09:23 +00:00
MoveType,
} from "types"
2021-05-19 21:24:41 +00:00
import inputs from "./inputs"
2021-05-10 12:16:57 +00:00
import { defaultDocument } from "./data"
2021-05-20 09:49:40 +00:00
import shapeUtilityMap, { getShapeUtils } from "lib/shape-utils"
2021-05-13 08:34:56 +00:00
import history from "state/history"
2021-05-10 12:16:57 +00:00
import * as Sessions from "./sessions"
2021-05-15 13:02:13 +00:00
import commands from "./commands"
2021-05-17 21:27:18 +00:00
import { updateFromCode } from "lib/code/generate"
2021-05-09 12:03:39 +00:00
2021-05-09 21:22:25 +00:00
const initialData: Data = {
2021-05-13 06:44:52 +00:00
isReadOnly: false,
2021-05-14 22:56:41 +00:00
settings: {
fontSize: 13,
2021-05-17 21:27:18 +00:00
isDarkMode: false,
isCodeOpen: false,
2021-05-14 22:56:41 +00:00
},
2021-05-09 13:04:42 +00:00
camera: {
point: [0, 0],
zoom: 1,
},
2021-05-10 12:16:57 +00:00
brush: undefined,
2021-05-18 08:32:20 +00:00
boundsRotation: 0,
2021-05-10 12:16:57 +00:00
pointedId: null,
2021-05-14 22:56:41 +00:00
hoveredId: null,
2021-05-12 11:27:33 +00:00
selectedIds: new Set([]),
2021-05-09 21:22:25 +00:00
currentPageId: "page0",
2021-05-16 08:33:08 +00:00
currentCodeFileId: "file0",
2021-05-17 10:01:11 +00:00
codeControls: {},
2021-05-10 12:16:57 +00:00
document: defaultDocument,
2021-05-09 13:04:42 +00:00
}
const state = createState({
data: initialData,
on: {
ZOOMED_CAMERA: {
do: "zoomCamera",
},
PANNED_CAMERA: {
do: "panCamera",
},
SELECTED_SELECT_TOOL: { to: "selecting" },
2021-05-15 15:20:21 +00:00
SELECTED_DOT_TOOL: { unless: "isReadOnly", to: "dot" },
SELECTED_CIRCLE_TOOL: { unless: "isReadOnly", to: "circle" },
SELECTED_ELLIPSE_TOOL: { unless: "isReadOnly", to: "ellipse" },
SELECTED_RAY_TOOL: { unless: "isReadOnly", to: "ray" },
SELECTED_LINE_TOOL: { unless: "isReadOnly", to: "line" },
SELECTED_POLYLINE_TOOL: { unless: "isReadOnly", to: "polyline" },
SELECTED_RECTANGLE_TOOL: { unless: "isReadOnly", to: "rectangle" },
2021-05-17 21:27:18 +00:00
TOGGLED_CODE_PANEL_OPEN: "toggleCodePanel",
2021-05-17 10:01:11 +00:00
RESET_CAMERA: "resetCamera",
2021-05-25 09:09:51 +00:00
ZOOMED_TO_FIT: {
if: "hasSelection",
do: "zoomCameraToFit",
else: "resetCamera",
},
ZOOMED_TO_SELECTION: {
if: "hasSelection",
do: "zoomCameraToSelection",
else: "resetCamera",
},
ZOOMED_TO_ACTUAL: {
if: "hasSelection",
do: "zoomCameraToSelectionActual",
else: "zoomCameraToActual",
},
2021-05-09 13:04:42 +00:00
},
initial: "loading",
2021-05-10 12:16:57 +00:00
states: {
loading: {
on: {
MOUNTED: {
do: "restoreSavedData",
2021-05-17 21:27:18 +00:00
to: "ready",
},
},
},
2021-05-17 21:27:18 +00:00
ready: {
2021-05-13 08:34:56 +00:00
on: {
2021-05-17 21:27:18 +00:00
UNMOUNTED: [
{ unless: "isReadOnly", do: "forceSave" },
{ to: "loading" },
],
2021-05-13 08:34:56 +00:00
},
2021-05-17 21:27:18 +00:00
initial: "selecting",
2021-05-13 06:44:52 +00:00
states: {
2021-05-17 21:27:18 +00:00
selecting: {
2021-05-13 06:44:52 +00:00
on: {
2021-05-17 21:27:18 +00:00
SAVED: "forceSave",
UNDO: { do: "undo" },
REDO: { do: "redo" },
CANCELLED: { do: "clearSelectedIds" },
DELETED: { do: "deleteSelectedIds" },
SAVED_CODE: "saveCode",
GENERATED_FROM_CODE: ["setCodeControls", "setGeneratedShapes"],
INCREASED_CODE_FONT_SIZE: "increaseCodeFontSize",
DECREASED_CODE_FONT_SIZE: "decreaseCodeFontSize",
CHANGED_CODE_CONTROL: "updateControls",
2021-05-23 13:46:04 +00:00
MOVED_TO_FRONT: "moveSelectionToFront",
MOVED_TO_BACK: "moveSelectionToBack",
MOVED_FORWARD: "moveSelectionForward",
MOVED_BACKWARD: "moveSelectionBackward",
2021-05-17 21:27:18 +00:00
},
initial: "notPointing",
states: {
notPointing: {
on: {
SELECTED_ALL: "selectAll",
POINTED_CANVAS: { to: "brushSelecting" },
POINTED_BOUNDS: { to: "pointingBounds" },
POINTED_BOUNDS_HANDLE: {
if: "isPointingRotationHandle",
to: "rotatingSelection",
else: { to: "transformingSelection" },
},
2021-05-17 21:27:18 +00:00
MOVED_OVER_SHAPE: {
if: "pointHitsShape",
then: {
unless: "shapeIsHovered",
do: "setHoveredId",
2021-05-13 06:44:52 +00:00
},
2021-05-17 21:27:18 +00:00
else: { if: "shapeIsHovered", do: "clearHoveredId" },
2021-05-13 06:44:52 +00:00
},
2021-05-17 21:27:18 +00:00
UNHOVERED_SHAPE: "clearHoveredId",
POINTED_SHAPE: [
2021-05-13 06:44:52 +00:00
{
2021-05-20 09:25:14 +00:00
if: "isPressingMetaKey",
to: "brushSelecting",
2021-05-20 08:19:13 +00:00
},
2021-05-20 09:25:14 +00:00
"setPointedId",
2021-05-20 08:19:13 +00:00
{
unless: "isPointedShapeSelected",
2021-05-20 09:25:14 +00:00
then: {
if: "isPressingShiftKey",
do: ["pushPointedIdToSelectedIds", "clearPointedId"],
else: ["clearSelectedIds", "pushPointedIdToSelectedIds"],
},
2021-05-20 08:19:13 +00:00
},
{
to: "pointingBounds",
2021-05-13 06:44:52 +00:00
},
],
},
},
2021-05-17 21:27:18 +00:00
pointingBounds: {
2021-05-15 17:11:08 +00:00
on: {
2021-05-17 21:27:18 +00:00
STOPPED_POINTING: [
{
2021-05-20 08:19:13 +00:00
if: "isPressingShiftKey",
then: {
if: "isPointedShapeSelected",
do: "pullPointedIdFromSelectedIds",
},
else: {
2021-05-20 09:25:14 +00:00
unless: "isPointingBounds",
2021-05-20 08:19:13 +00:00
do: ["clearSelectedIds", "pushPointedIdToSelectedIds"],
},
2021-05-17 21:27:18 +00:00
},
{ to: "notPointing" },
],
2021-05-15 17:11:08 +00:00
MOVED_POINTER: {
2021-05-17 21:27:18 +00:00
unless: "isReadOnly",
2021-05-15 17:11:08 +00:00
if: "distanceImpliesDrag",
2021-05-17 21:27:18 +00:00
to: "draggingSelection",
2021-05-15 17:11:08 +00:00
},
},
},
2021-05-17 21:27:18 +00:00
rotatingSelection: {
onEnter: "startRotateSession",
onExit: "clearBoundsRotation",
2021-05-17 21:27:18 +00:00
on: {
MOVED_POINTER: "updateRotateSession",
PANNED_CAMERA: "updateRotateSession",
PRESSED_SHIFT_KEY: "keyUpdateRotateSession",
RELEASED_SHIFT_KEY: "keyUpdateRotateSession",
2021-05-17 21:27:18 +00:00
STOPPED_POINTING: { do: "completeSession", to: "selecting" },
CANCELLED: { do: "cancelSession", to: "selecting" },
},
},
transformingSelection: {
onEnter: "startTransformSession",
on: {
MOVED_POINTER: "updateTransformSession",
PANNED_CAMERA: "updateTransformSession",
2021-05-21 07:42:56 +00:00
PRESSED_SHIFT_KEY: "keyUpdateTransformSession",
RELEASED_SHIFT_KEY: "keyUpdateTransformSession",
2021-05-17 21:27:18 +00:00
STOPPED_POINTING: { do: "completeSession", to: "selecting" },
CANCELLED: { do: "cancelSession", to: "selecting" },
},
},
draggingSelection: {
2021-05-15 17:11:08 +00:00
onEnter: "startTranslateSession",
on: {
MOVED_POINTER: "updateTranslateSession",
PANNED_CAMERA: "updateTranslateSession",
2021-05-20 08:19:13 +00:00
PRESSED_SHIFT_KEY: "keyUpdateTranslateSession",
RELEASED_SHIFT_KEY: "keyUpdateTranslateSession",
PRESSED_ALT_KEY: "keyUpdateTranslateSession",
RELEASED_ALT_KEY: "keyUpdateTranslateSession",
2021-05-17 21:27:18 +00:00
STOPPED_POINTING: { do: "completeSession", to: "selecting" },
CANCELLED: { do: "cancelSession", to: "selecting" },
2021-05-15 17:11:08 +00:00
},
},
2021-05-17 21:27:18 +00:00
brushSelecting: {
onEnter: [
2021-05-20 09:25:14 +00:00
{
unless: ["isPressingMetaKey", "isPressingShiftKey"],
do: "clearSelectedIds",
},
2021-05-18 08:32:20 +00:00
"clearBoundsRotation",
2021-05-17 21:27:18 +00:00
"startBrushSession",
],
on: {
MOVED_POINTER: "updateBrushSession",
PANNED_CAMERA: "updateBrushSession",
STOPPED_POINTING: { do: "completeSession", to: "selecting" },
CANCELLED: { do: "cancelSession", to: "selecting" },
},
2021-05-15 15:20:21 +00:00
},
},
},
2021-05-17 21:27:18 +00:00
dot: {
initial: "creating",
states: {
creating: {
on: {
POINTED_CANVAS: {
2021-05-23 13:46:04 +00:00
get: "newDot",
do: "createShape",
2021-05-17 21:27:18 +00:00
to: "dot.editing",
},
},
},
editing: {
on: {
STOPPED_POINTING: { do: "completeSession", to: "selecting" },
CANCELLED: {
do: ["cancelSession", "deleteSelectedIds"],
to: "selecting",
},
},
initial: "inactive",
states: {
inactive: {
on: {
MOVED_POINTER: {
if: "distanceImpliesDrag",
to: "dot.editing.active",
},
},
},
active: {
onEnter: "startTranslateSession",
on: {
MOVED_POINTER: "updateTranslateSession",
PANNED_CAMERA: "updateTranslateSession",
},
},
},
2021-05-15 15:20:21 +00:00
},
},
2021-05-17 21:27:18 +00:00
},
circle: {
initial: "creating",
2021-05-15 17:11:08 +00:00
states: {
2021-05-17 21:27:18 +00:00
creating: {
2021-05-15 17:11:08 +00:00
on: {
2021-05-17 21:27:18 +00:00
POINTED_CANVAS: {
to: "circle.editing",
2021-05-15 17:11:08 +00:00
},
},
},
2021-05-17 21:27:18 +00:00
editing: {
2021-05-15 17:11:08 +00:00
on: {
2021-05-17 21:27:18 +00:00
STOPPED_POINTING: { to: "selecting" },
CANCELLED: { to: "selecting" },
MOVED_POINTER: {
if: "distanceImpliesDrag",
2021-05-23 13:46:04 +00:00
then: {
2021-05-23 17:09:23 +00:00
get: "newCircle",
2021-05-23 13:46:04 +00:00
do: "createShape",
to: "drawingShape.bounds",
},
2021-05-17 21:27:18 +00:00
},
2021-05-15 17:11:08 +00:00
},
},
},
},
2021-05-17 21:27:18 +00:00
ellipse: {
initial: "creating",
states: {
creating: {
on: {
2021-05-19 21:24:41 +00:00
CANCELLED: { to: "selecting" },
2021-05-17 21:27:18 +00:00
POINTED_CANVAS: {
to: "ellipse.editing",
},
},
},
editing: {
on: {
STOPPED_POINTING: { to: "selecting" },
CANCELLED: { to: "selecting" },
MOVED_POINTER: {
if: "distanceImpliesDrag",
2021-05-23 13:46:04 +00:00
then: {
get: "newEllipse",
do: "createShape",
to: "drawingShape.bounds",
},
2021-05-17 21:27:18 +00:00
},
},
2021-05-15 17:11:08 +00:00
},
},
},
2021-05-17 21:27:18 +00:00
rectangle: {
initial: "creating",
states: {
creating: {
on: {
2021-05-19 21:24:41 +00:00
CANCELLED: { to: "selecting" },
2021-05-17 21:27:18 +00:00
POINTED_CANVAS: {
to: "rectangle.editing",
},
},
},
editing: {
on: {
STOPPED_POINTING: { to: "selecting" },
CANCELLED: { to: "selecting" },
MOVED_POINTER: {
if: "distanceImpliesDrag",
2021-05-23 13:46:04 +00:00
then: {
get: "newRectangle",
do: "createShape",
to: "drawingShape.bounds",
},
2021-05-17 21:27:18 +00:00
},
},
2021-05-15 17:11:08 +00:00
},
},
2021-05-17 21:27:18 +00:00
},
ray: {
initial: "creating",
2021-05-15 17:11:08 +00:00
states: {
2021-05-17 21:27:18 +00:00
creating: {
2021-05-15 17:11:08 +00:00
on: {
2021-05-19 21:24:41 +00:00
CANCELLED: { to: "selecting" },
2021-05-17 21:27:18 +00:00
POINTED_CANVAS: {
2021-05-23 13:46:04 +00:00
get: "newRay",
do: "createShape",
2021-05-17 21:27:18 +00:00
to: "ray.editing",
},
},
},
editing: {
on: {
2021-05-19 21:24:41 +00:00
STOPPED_POINTING: { to: "selecting" },
CANCELLED: { to: "selecting" },
2021-05-15 17:11:08 +00:00
MOVED_POINTER: {
if: "distanceImpliesDrag",
2021-05-17 21:27:18 +00:00
to: "drawingShape.direction",
2021-05-15 17:11:08 +00:00
},
},
},
2021-05-17 21:27:18 +00:00
},
},
line: {
initial: "creating",
states: {
creating: {
2021-05-15 17:11:08 +00:00
on: {
2021-05-19 21:24:41 +00:00
CANCELLED: { to: "selecting" },
2021-05-17 21:27:18 +00:00
POINTED_CANVAS: {
2021-05-23 13:46:04 +00:00
get: "newLine",
do: "createShape",
2021-05-17 21:27:18 +00:00
to: "line.editing",
},
},
},
editing: {
on: {
2021-05-19 21:24:41 +00:00
STOPPED_POINTING: { to: "selecting" },
CANCELLED: { to: "selecting" },
2021-05-17 21:27:18 +00:00
MOVED_POINTER: {
if: "distanceImpliesDrag",
to: "drawingShape.direction",
},
2021-05-15 17:11:08 +00:00
},
},
},
2021-05-15 15:20:21 +00:00
},
2021-05-17 21:27:18 +00:00
polyline: {},
},
},
drawingShape: {
on: {
2021-05-19 21:24:41 +00:00
STOPPED_POINTING: {
do: "completeSession",
to: "selecting",
},
2021-05-17 21:27:18 +00:00
CANCELLED: {
do: ["cancelSession", "deleteSelectedIds"],
to: "selecting",
},
},
initial: "drawingShapeBounds",
states: {
bounds: {
onEnter: "startDrawTransformSession",
on: {
MOVED_POINTER: "updateTransformSession",
PANNED_CAMERA: "updateTransformSession",
},
},
direction: {
onEnter: "startDirectionSession",
on: {
MOVED_POINTER: "updateDirectionSession",
PANNED_CAMERA: "updateDirectionSession",
},
},
2021-05-15 15:20:21 +00:00
},
},
2021-05-10 12:16:57 +00:00
},
2021-05-23 13:46:04 +00:00
results: {
// Dot
newDot(data, payload: PointerInfo) {
return shapeUtilityMap[ShapeType.Dot].create({
point: screenToWorld(payload.point, data),
})
},
// Ray
newRay(data, payload: PointerInfo) {
return shapeUtilityMap[ShapeType.Ray].create({
point: screenToWorld(payload.point, data),
})
},
// Line
newLine(data, payload: PointerInfo) {
return shapeUtilityMap[ShapeType.Line].create({
point: screenToWorld(payload.point, data),
direction: [0, 1],
})
},
newCircle(data, payload: PointerInfo) {
return shapeUtilityMap[ShapeType.Circle].create({
point: screenToWorld(payload.point, data),
radius: 1,
})
},
newEllipse(data, payload: PointerInfo) {
return shapeUtilityMap[ShapeType.Ellipse].create({
point: screenToWorld(payload.point, data),
radiusX: 1,
radiusY: 1,
})
},
newRectangle(data, payload: PointerInfo) {
return shapeUtilityMap[ShapeType.Rectangle].create({
point: screenToWorld(payload.point, data),
size: [1, 1],
})
},
},
conditions: {
isPointingBounds(data, payload: PointerInfo) {
return payload.target === "bounds"
},
2021-05-13 06:44:52 +00:00
isReadOnly(data) {
return data.isReadOnly
},
distanceImpliesDrag(data, payload: PointerInfo) {
return vec.dist2(payload.origin, payload.point) > 8
2021-05-13 06:44:52 +00:00
},
isPointedShapeSelected(data) {
2021-05-12 11:27:33 +00:00
return data.selectedIds.has(data.pointedId)
},
2021-05-20 09:25:14 +00:00
isPressingShiftKey(data, payload: PointerInfo) {
return payload.shiftKey
},
2021-05-20 09:25:14 +00:00
isPressingMetaKey(data, payload: PointerInfo) {
return payload.metaKey
},
2021-05-14 22:56:41 +00:00
shapeIsHovered(data, payload: { target: string }) {
return data.hoveredId === payload.target
},
pointHitsShape(data, payload: { target: string; point: number[] }) {
const shape = getShape(data, payload.target)
2021-05-14 22:56:41 +00:00
return getShapeUtils(shape).hitTest(
shape,
screenToWorld(payload.point, data)
)
},
isPointingRotationHandle(
data,
payload: { target: Edge | Corner | "rotate" }
) {
return payload.target === "rotate"
},
hasSelection(data) {
return data.selectedIds.size > 0
},
},
2021-05-09 13:04:42 +00:00
actions: {
2021-05-15 15:20:21 +00:00
/* --------------------- Shapes --------------------- */
createShape(data, payload, shape: Shape) {
2021-05-23 13:46:04 +00:00
const siblings = getChildren(data, shape.parentId)
const childIndex = siblings.length
? siblings[siblings.length - 1].childIndex + 1
: 1
getShapeUtils(shape).setChildIndex(shape, childIndex)
2021-05-17 21:27:18 +00:00
getPage(data).shapes[shape.id] = shape
2021-05-19 21:24:41 +00:00
data.selectedIds.clear()
2021-05-17 21:27:18 +00:00
data.selectedIds.add(shape.id)
},
2021-05-15 15:20:21 +00:00
/* -------------------- Sessions -------------------- */
// Shared
2021-05-10 12:16:57 +00:00
cancelSession(data) {
2021-05-15 17:11:08 +00:00
session?.cancel(data)
2021-05-10 12:16:57 +00:00
session = undefined
},
completeSession(data) {
2021-05-15 17:11:08 +00:00
session?.complete(data)
2021-05-10 12:16:57 +00:00
session = undefined
},
2021-05-13 08:34:56 +00:00
2021-05-13 06:44:52 +00:00
// Brushing
startBrushSession(data, payload: PointerInfo) {
2021-05-10 20:44:17 +00:00
session = new Sessions.BrushSession(
data,
screenToWorld(payload.point, data)
)
2021-05-10 12:16:57 +00:00
},
updateBrushSession(data, payload: PointerInfo) {
2021-05-10 20:44:17 +00:00
session.update(data, screenToWorld(payload.point, data))
2021-05-10 12:16:57 +00:00
},
2021-05-14 12:44:23 +00:00
2021-05-17 21:27:18 +00:00
// Rotating
startRotateSession(data, payload: PointerInfo) {
session = new Sessions.RotateSession(
data,
screenToWorld(payload.point, data)
)
},
keyUpdateRotateSession(data, payload: PointerInfo) {
session.update(
data,
screenToWorld(inputs.pointer.point, data),
payload.shiftKey
)
},
2021-05-17 21:27:18 +00:00
updateRotateSession(data, payload: PointerInfo) {
session.update(data, screenToWorld(payload.point, data), payload.shiftKey)
2021-05-17 21:27:18 +00:00
},
2021-05-13 06:44:52 +00:00
// Dragging / Translating
startTranslateSession(data, payload: PointerInfo) {
2021-05-13 06:44:52 +00:00
session = new Sessions.TranslateSession(
data,
screenToWorld(inputs.pointer.origin, data),
2021-05-20 08:19:13 +00:00
payload.altKey
2021-05-13 06:44:52 +00:00
)
},
2021-05-20 08:19:13 +00:00
keyUpdateTranslateSession(
data,
payload: { shiftKey: boolean; altKey: boolean }
) {
2021-05-19 21:24:41 +00:00
session.update(
data,
screenToWorld(inputs.pointer.point, data),
2021-05-20 08:19:13 +00:00
payload.shiftKey,
2021-05-19 21:24:41 +00:00
payload.altKey
)
},
updateTranslateSession(data, payload: PointerInfo) {
2021-05-20 08:19:13 +00:00
session.update(
data,
screenToWorld(payload.point, data),
payload.shiftKey,
payload.altKey
)
2021-05-13 06:44:52 +00:00
},
2021-05-14 12:44:23 +00:00
// Dragging / Translating
startTransformSession(
data,
payload: PointerInfo & { target: Corner | Edge }
2021-05-14 12:44:23 +00:00
) {
const point = screenToWorld(inputs.pointer.origin, data)
2021-05-19 09:35:00 +00:00
session =
data.selectedIds.size === 1
? new Sessions.TransformSingleSession(data, payload.target, point)
: new Sessions.TransformSession(data, payload.target, point)
2021-05-14 12:44:23 +00:00
},
2021-05-17 21:27:18 +00:00
startDrawTransformSession(data, payload: PointerInfo) {
session = new Sessions.TransformSingleSession(
2021-05-17 21:27:18 +00:00
data,
Corner.BottomRight,
2021-05-19 21:24:41 +00:00
screenToWorld(payload.point, data),
true
2021-05-17 21:27:18 +00:00
)
},
2021-05-21 07:42:56 +00:00
keyUpdateTransformSession(data, payload: PointerInfo) {
session.update(
data,
screenToWorld(inputs.pointer.point, data),
payload.shiftKey,
payload.altKey
)
},
2021-05-14 12:44:23 +00:00
updateTransformSession(data, payload: PointerInfo) {
2021-05-21 07:42:56 +00:00
session.update(
data,
screenToWorld(payload.point, data),
payload.shiftKey,
payload.altKey
)
2021-05-14 12:44:23 +00:00
},
2021-05-15 15:20:21 +00:00
// Direction
startDirectionSession(data, payload: PointerInfo) {
session = new Sessions.DirectionSession(
data,
screenToWorld(inputs.pointer.origin, data)
2021-05-15 15:20:21 +00:00
)
},
updateDirectionSession(data, payload: PointerInfo) {
session.update(data, screenToWorld(payload.point, data))
},
2021-05-15 15:20:21 +00:00
/* -------------------- Selection ------------------- */
2021-05-17 21:27:18 +00:00
selectAll(data) {
const { selectedIds } = data
const page = getPage(data)
2021-05-17 21:27:18 +00:00
selectedIds.clear()
for (let id in page.shapes) {
2021-05-17 21:27:18 +00:00
selectedIds.add(id)
}
},
2021-05-14 22:56:41 +00:00
setHoveredId(data, payload: PointerInfo) {
data.hoveredId = payload.target
},
clearHoveredId(data) {
data.hoveredId = undefined
},
setPointedId(data, payload: PointerInfo) {
data.pointedId = payload.target
},
clearPointedId(data) {
data.pointedId = undefined
},
clearSelectedIds(data) {
2021-05-12 11:27:33 +00:00
data.selectedIds.clear()
},
pullPointedIdFromSelectedIds(data) {
const { selectedIds, pointedId } = data
2021-05-12 11:27:33 +00:00
selectedIds.delete(pointedId)
},
pushPointedIdToSelectedIds(data) {
2021-05-12 11:27:33 +00:00
data.selectedIds.add(data.pointedId)
},
2021-05-23 13:46:04 +00:00
moveSelectionToFront(data) {
2021-05-23 17:09:23 +00:00
commands.move(data, MoveType.ToFront)
2021-05-23 13:46:04 +00:00
},
moveSelectionToBack(data) {
2021-05-23 17:09:23 +00:00
commands.move(data, MoveType.ToBack)
2021-05-23 13:46:04 +00:00
},
moveSelectionForward(data) {
2021-05-23 17:09:23 +00:00
commands.move(data, MoveType.Forward)
2021-05-23 13:46:04 +00:00
},
moveSelectionBackward(data) {
2021-05-23 17:09:23 +00:00
commands.move(data, MoveType.Backward)
2021-05-23 13:46:04 +00:00
},
/* --------------------- Camera --------------------- */
2021-05-17 10:01:11 +00:00
resetCamera(data) {
data.camera.zoom = 1
data.camera.point = [window.innerWidth / 2, window.innerHeight / 2]
document.documentElement.style.setProperty("--camera-zoom", "1")
},
zoomCameraToSelection(data) {
const { camera } = data
const bounds = getSelectedBounds(data)
const zoom =
bounds.width > bounds.height
? (window.innerWidth - 128) / bounds.width
: (window.innerHeight - 128) / bounds.height
const mx = window.innerWidth - bounds.width * zoom
const my = window.innerHeight - bounds.height * zoom
camera.zoom = zoom
camera.point = vec.add(
[-bounds.minX, -bounds.minY],
[mx / 2 / zoom, my / 2 / zoom]
)
setZoomCSS(camera.zoom)
},
zoomCameraToSelectionActual(data) {
const { camera } = data
const bounds = getSelectedBounds(data)
const zoom = 1
const mx = window.innerWidth - 128 - bounds.width * zoom
const my = window.innerHeight - 128 - bounds.height * zoom
camera.zoom = zoom
camera.point = vec.add(
[-bounds.minX, -bounds.minY],
[mx / 2 / zoom, my / 2 / zoom]
)
setZoomCSS(camera.zoom)
},
zoomCameraToActual(data) {
const { camera } = data
const center = [window.innerWidth / 2, window.innerHeight / 2]
const p0 = screenToWorld(center, data)
camera.zoom = 1
const p1 = screenToWorld(center, data)
camera.point = vec.add(camera.point, vec.sub(p1, p0))
setZoomCSS(camera.zoom)
},
zoomCameraToFit(data) {
const { camera } = data
const { shapes } = getPage(data)
const bounds = getCommonBounds(
...Object.values(shapes).map((shape) =>
getShapeUtils(shape).getBounds(shape)
)
)
const zoom =
bounds.width > bounds.height
? (window.innerWidth - 104) / bounds.width
: (window.innerHeight - 104) / bounds.height
const mx = window.innerWidth - bounds.width * zoom
const my = window.innerHeight - bounds.height * zoom
camera.zoom = zoom
camera.point = vec.add(
[-bounds.minX, -bounds.minY],
[mx / 2 / zoom, my / 2 / zoom]
)
setZoomCSS(camera.zoom)
},
2021-05-09 13:04:42 +00:00
zoomCamera(data, payload: { delta: number; point: number[] }) {
const { camera } = data
const p0 = screenToWorld(payload.point, data)
camera.zoom = clamp(
camera.zoom - (payload.delta / 100) * camera.zoom,
0.1,
2021-05-09 13:04:42 +00:00
3
)
const p1 = screenToWorld(payload.point, data)
camera.point = vec.add(camera.point, vec.sub(p1, p0))
setZoomCSS(camera.zoom)
2021-05-09 13:04:42 +00:00
},
panCamera(data, payload: { delta: number[]; point: number[] }) {
const { camera } = data
data.camera.point = vec.sub(
camera.point,
vec.div(payload.delta, camera.zoom)
)
},
2021-05-15 15:20:21 +00:00
deleteSelectedIds(data) {
2021-05-23 17:09:23 +00:00
commands.deleteSelected(data)
2021-05-15 15:20:21 +00:00
},
2021-05-17 21:27:18 +00:00
/* ---------------------- History ---------------------- */
2021-05-15 15:20:21 +00:00
// History
2021-05-19 21:24:41 +00:00
popHistory() {
history.pop()
},
2021-05-17 21:27:18 +00:00
forceSave(data) {
history.save(data)
},
2021-05-15 15:20:21 +00:00
enableHistory() {
history.enable()
},
disableHistory() {
history.disable()
},
undo(data) {
history.undo(data)
},
redo(data) {
history.redo(data)
},
2021-05-17 21:27:18 +00:00
/* ---------------------- Code ---------------------- */
closeCodePanel(data) {
data.settings.isCodeOpen = false
},
openCodePanel(data) {
data.settings.isCodeOpen = true
},
toggleCodePanel(data) {
data.settings.isCodeOpen = !data.settings.isCodeOpen
},
2021-05-17 10:01:11 +00:00
setGeneratedShapes(
data,
payload: { shapes: Shape[]; controls: CodeControl[] }
) {
commands.generate(data, data.currentPageId, payload.shapes)
},
setCodeControls(data, payload: { controls: CodeControl[] }) {
data.codeControls = Object.fromEntries(
payload.controls.map((control) => [control.id, control])
)
2021-05-15 15:20:21 +00:00
},
increaseCodeFontSize(data) {
data.settings.fontSize++
},
decreaseCodeFontSize(data) {
data.settings.fontSize--
},
2021-05-17 10:01:11 +00:00
updateControls(data, payload: { [key: string]: any }) {
for (let key in payload) {
data.codeControls[key].value = payload[key]
}
history.disable()
data.selectedIds.clear()
try {
const { shapes } = updateFromCode(
data.document.code[data.currentCodeFileId].code,
data.codeControls
)
commands.generate(data, data.currentPageId, shapes)
} catch (e) {
console.error(e)
}
history.enable()
},
// Data
2021-05-16 08:33:08 +00:00
saveCode(data, payload: { code: string }) {
data.document.code[data.currentCodeFileId].code = payload.code
history.save(data)
},
restoreSavedData(data) {
history.load(data)
},
2021-05-18 08:32:20 +00:00
clearBoundsRotation(data) {
data.boundsRotation = 0
},
2021-05-09 13:04:42 +00:00
},
values: {
selectedIds(data) {
return new Set(data.selectedIds)
},
2021-05-12 22:08:53 +00:00
selectedBounds(data) {
const { selectedIds } = data
const page = getPage(data)
2021-05-12 22:08:53 +00:00
2021-05-17 10:01:11 +00:00
const shapes = Array.from(selectedIds.values())
.map((id) => page.shapes[id])
2021-05-17 10:01:11 +00:00
.filter(Boolean)
2021-05-15 13:02:13 +00:00
2021-05-14 12:44:23 +00:00
if (selectedIds.size === 0) return null
2021-05-18 08:32:20 +00:00
if (selectedIds.size === 1) {
2021-05-20 08:19:13 +00:00
if (!shapes[0]) {
console.error("Could not find that shape! Clearing selected IDs.")
data.selectedIds.clear()
return null
}
2021-05-18 08:32:20 +00:00
const shapeUtils = getShapeUtils(shapes[0])
if (!shapeUtils.canTransform) return null
return shapeUtils.getBounds(shapes[0])
2021-05-15 13:02:13 +00:00
}
2021-05-12 22:08:53 +00:00
return getCommonBounds(
2021-05-18 08:32:20 +00:00
...shapes.map((shape) => getShapeUtils(shape).getRotatedBounds(shape))
2021-05-12 22:08:53 +00:00
)
},
},
2021-05-09 13:04:42 +00:00
})
2021-05-09 12:03:39 +00:00
2021-05-10 12:16:57 +00:00
let session: Sessions.BaseSession
2021-05-09 12:03:39 +00:00
export default state
export const useSelector = createSelectorHook(state)