tldraw/state/state.ts

1767 lines
52 KiB
TypeScript
Raw Normal View History

2021-05-28 16:25:43 +00:00
import { createSelectorHook, createState } from '@state-designer/react'
import { updateFromCode } from 'lib/code/generate'
import { createShape, getShapeUtils } from 'lib/shape-utils'
2021-05-28 16:25:43 +00:00
import * as vec from 'utils/vec'
import inputs from './inputs'
import { defaultDocument } from './data'
import history from './history'
import storage from './storage'
2021-05-28 16:25:43 +00:00
import * as Sessions from './sessions'
import commands from './commands'
import {
2021-05-23 13:46:04 +00:00
getChildren,
getCommonBounds,
2021-05-26 10:34:10 +00:00
getCurrent,
2021-06-03 12:06:39 +00:00
getCurrentCamera,
getPage,
getSelectedBounds,
2021-06-04 16:08:43 +00:00
getSelectedShapes,
getShape,
screenToWorld,
setZoomCSS,
2021-06-04 16:08:43 +00:00
rotateBounds,
getBoundsCenter,
2021-06-04 21:21:03 +00:00
getDocumentBranch,
getCameraZoom,
getSelectedIds,
setSelectedIds,
getPageState,
2021-05-28 16:25:43 +00:00
} from 'utils/utils'
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,
2021-05-26 10:34:10 +00:00
ShapeStyles,
2021-05-26 19:20:52 +00:00
DistributeType,
AlignType,
StretchType,
2021-06-01 21:49:32 +00:00
DashStyle,
SizeStyle,
ColorStyle,
FontSize,
2021-05-28 16:25:43 +00:00
} from 'types'
import session from './session'
2021-06-08 11:27:47 +00:00
import { pointInBounds } from 'utils/bounds'
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-26 10:34:10 +00:00
isStyleOpen: false,
2021-05-28 20:30:27 +00:00
isToolLocked: false,
isPenLocked: false,
nudgeDistanceLarge: 10,
nudgeDistanceSmall: 1,
2021-05-26 10:34:10 +00:00
},
currentStyle: {
size: SizeStyle.Medium,
color: ColorStyle.Black,
2021-06-01 21:49:32 +00:00
dash: DashStyle.Solid,
fontSize: FontSize.Medium,
isFilled: false,
2021-05-14 22:56:41 +00:00
},
2021-06-02 21:17:38 +00:00
activeTool: 'select',
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,
editingId: null,
currentPageId: 'page1',
2021-06-04 16:08:43 +00:00
currentParentId: 'page1',
2021-05-28 16:25:43 +00:00
currentCodeFileId: 'file0',
2021-05-17 10:01:11 +00:00
codeControls: {},
2021-05-10 12:16:57 +00:00
document: defaultDocument,
2021-06-03 12:06:39 +00:00
pageStates: {
page1: {
selectedIds: new Set([]),
2021-06-03 12:06:39 +00:00
camera: {
point: [0, 0],
zoom: 1,
},
},
page2: {
selectedIds: new Set([]),
2021-06-03 12:06:39 +00:00
camera: {
point: [0, 0],
zoom: 1,
},
},
},
2021-05-09 13:04:42 +00:00
}
const state = createState({
data: initialData,
on: {
UNMOUNTED: [{ unless: 'isReadOnly', do: 'forceSave' }, { to: 'loading' }],
2021-05-09 13:04:42 +00:00
},
2021-05-28 16:25:43 +00:00
initial: 'loading',
2021-05-10 12:16:57 +00:00
states: {
loading: {
on: {
2021-05-28 16:25:43 +00:00
MOUNTED: [
'restoreSavedData',
{
to: 'ready',
},
],
},
},
2021-05-17 21:27:18 +00:00
ready: {
2021-05-29 13:59:11 +00:00
onEnter: {
wait: 0.01,
if: 'hasSelection',
do: 'zoomCameraToSelectionActual',
else: ['zoomCameraToFit', 'zoomCameraToActual'],
},
2021-05-13 08:34:56 +00:00
on: {
TOGGLED_SHAPE_LOCK: { if: 'hasSelection', do: 'lockSelection' },
TOGGLED_SHAPE_HIDE: { if: 'hasSelection', do: 'hideSelection' },
TOGGLED_SHAPE_ASPECT_LOCK: {
if: 'hasSelection',
do: 'aspectLockSelection',
},
TOGGLED_CODE_PANEL_OPEN: 'toggleCodePanel',
TOGGLED_STYLE_PANEL_OPEN: 'toggleStylePanel',
2021-06-04 16:08:43 +00:00
POINTED_CANVAS: ['closeStylePanel', 'clearCurrentParentId'],
CHANGED_STYLE: ['updateStyles', 'applyStylesToSelection'],
USED_PEN_DEVICE: 'enablePenLock',
DISABLED_PEN_LOCK: 'disablePenLock',
2021-06-06 19:42:04 +00:00
CLEARED_PAGE: {
if: 'hasSelection',
do: 'deleteSelection',
else: ['selectAll', 'deleteSelection'],
},
CHANGED_PAGE: 'changePage',
2021-06-03 13:10:54 +00:00
CREATED_PAGE: ['clearSelectedIds', 'createPage'],
DELETED_PAGE: { unless: 'hasOnlyOnePage', do: 'deletePage' },
LOADED_FROM_FILE: 'loadDocumentFromJson',
PANNED_CAMERA: {
do: 'panCamera',
},
2021-05-13 08:34:56 +00:00
},
2021-05-28 16:25:43 +00:00
initial: 'selecting',
2021-05-13 06:44:52 +00:00
states: {
2021-05-17 21:27:18 +00:00
selecting: {
2021-06-02 21:17:38 +00:00
onEnter: 'setActiveToolSelect',
2021-05-13 06:44:52 +00:00
on: {
2021-05-28 20:30:27 +00:00
UNDO: 'undo',
REDO: 'redo',
SAVED: 'forceSave',
LOADED_FROM_FILE_STSTEM: 'loadFromFileSystem',
SAVED_TO_FILESYSTEM: 'saveToFileSystem',
SAVED_AS_TO_FILESYSTEM: 'saveAsToFileSystem',
2021-05-28 16:25:43 +00:00
SAVED_CODE: 'saveCode',
2021-05-29 13:59:11 +00:00
DELETED: 'deleteSelection',
2021-05-28 16:25:43 +00:00
INCREASED_CODE_FONT_SIZE: 'increaseCodeFontSize',
DECREASED_CODE_FONT_SIZE: 'decreaseCodeFontSize',
CHANGED_CODE_CONTROL: 'updateControls',
2021-05-28 20:30:27 +00:00
GENERATED_FROM_CODE: ['setCodeControls', 'setGeneratedShapes'],
TOGGLED_TOOL_LOCK: 'toggleToolLock',
2021-06-10 09:49:16 +00:00
MOVED_TO_PAGE: {
if: 'hasSelection',
do: ['moveSelectionToPage', 'zoomCameraToSelectionActual'],
},
2021-05-28 20:30:27 +00:00
MOVED: { if: 'hasSelection', do: 'moveSelection' },
DUPLICATED: { if: 'hasSelection', do: 'duplicateSelection' },
2021-05-29 22:27:19 +00:00
ROTATED_CCW: { if: 'hasSelection', do: 'rotateSelectionCcw' },
2021-06-04 16:08:43 +00:00
ALIGNED: { if: 'hasMultipleSelection', do: 'alignSelection' },
STRETCHED: { if: 'hasMultipleSelection', do: 'stretchSelection' },
DISTRIBUTED: {
if: 'hasMultipleSelection',
do: 'distributeSelection',
},
GROUPED: { if: 'hasMultipleSelection', do: 'groupSelection' },
UNGROUPED: {
if: ['hasSelection', 'selectionIncludesGroups'],
do: 'ungroupSelection',
},
SELECTED_ALL: { to: 'selecting', do: 'selectAll' },
NUDGED: { do: 'nudgeSelection' },
SELECTED_SELECT_TOOL: { to: 'selecting' },
SELECTED_DRAW_TOOL: { unless: 'isReadOnly', to: 'draw' },
SELECTED_ARROW_TOOL: { unless: 'isReadOnly', to: 'arrow' },
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' },
ZOOMED_CAMERA: {
do: 'zoomCamera',
},
ZOOMED_TO_ACTUAL: {
if: 'hasSelection',
do: 'zoomCameraToSelectionActual',
else: 'zoomCameraToActual',
},
ZOOMED_TO_SELECTION: {
if: 'hasSelection',
do: 'zoomCameraToSelection',
},
ZOOMED_TO_FIT: ['zoomCameraToFit', 'zoomCameraToActual'],
ZOOMED_IN: 'zoomIn',
ZOOMED_OUT: 'zoomOut',
RESET_CAMERA: 'resetCamera',
2021-05-17 21:27:18 +00:00
},
2021-05-28 16:25:43 +00:00
initial: 'notPointing',
2021-05-17 21:27:18 +00:00
states: {
notPointing: {
on: {
2021-05-29 12:40:41 +00:00
CANCELLED: 'clearSelectedIds',
STARTED_PINCHING: { to: 'pinching' },
2021-05-28 16:25:43 +00:00
POINTED_CANVAS: { to: 'brushSelecting' },
POINTED_BOUNDS: [
{
if: 'isPressingMetaKey',
to: 'brushSelecting',
},
{ to: 'pointingBounds' },
],
POINTED_BOUNDS_HANDLE: {
2021-05-28 16:25:43 +00:00
if: 'isPointingRotationHandle',
to: 'rotatingSelection',
else: { to: 'transformingSelection' },
},
STARTED_EDITING_SHAPE: {
get: 'firstSelectedShape',
if: ['hasSingleSelection', 'canEditSelectedShape'],
do: 'setEditingId',
to: 'editingShape',
},
DOUBLE_POINTED_BOUNDS_HANDLE: {
if: 'hasSingleSelection',
do: 'resetShapeBounds',
},
2021-05-31 19:13:43 +00:00
POINTED_HANDLE: { to: 'translatingHandles' },
2021-05-17 21:27:18 +00:00
MOVED_OVER_SHAPE: {
2021-05-28 16:25:43 +00:00
if: 'pointHitsShape',
2021-05-17 21:27:18 +00:00
then: {
2021-05-28 16:25:43 +00:00
unless: 'shapeIsHovered',
do: 'setHoveredId',
2021-05-13 06:44:52 +00:00
},
else: {
if: 'shapeIsHovered',
do: 'clearHoveredId',
},
2021-05-13 06:44:52 +00:00
},
2021-05-28 16:25:43 +00:00
UNHOVERED_SHAPE: 'clearHoveredId',
2021-06-04 16:08:43 +00:00
DOUBLE_POINTED_SHAPE: [
'setPointedId',
{
if: 'isPointedShapeSelected',
then: {
get: 'firstSelectedShape',
if: 'canEditSelectedShape',
do: 'setEditingId',
to: 'editingShape',
},
},
2021-06-04 16:08:43 +00:00
{
unless: 'isPressingShiftKey',
do: [
'setDrilledPointedId',
'clearSelectedIds',
'pushPointedIdToSelectedIds',
],
2021-06-04 16:08:43 +00:00
to: 'pointingBounds',
},
],
2021-05-17 21:27:18 +00:00
POINTED_SHAPE: [
2021-05-13 06:44:52 +00:00
{
2021-05-28 16:25:43 +00:00
if: 'isPressingMetaKey',
to: 'brushSelecting',
2021-05-20 08:19:13 +00:00
},
2021-05-28 16:25:43 +00:00
'setPointedId',
{
if: 'pointInSelectionBounds',
to: 'pointingBounds',
},
2021-05-20 08:19:13 +00:00
{
2021-05-28 16:25:43 +00:00
unless: 'isPointedShapeSelected',
2021-05-20 09:25:14 +00:00
then: {
2021-05-28 16:25:43 +00:00
if: 'isPressingShiftKey',
do: ['pushPointedIdToSelectedIds', 'clearPointedId'],
else: ['clearSelectedIds', 'pushPointedIdToSelectedIds'],
2021-05-20 09:25:14 +00:00
},
2021-05-20 08:19:13 +00:00
},
{
2021-05-28 16:25:43 +00:00
to: 'pointingBounds',
2021-05-13 06:44:52 +00:00
},
],
2021-06-10 09:49:16 +00:00
RIGHT_POINTED: [
{
if: 'isPointingCanvas',
do: 'clearSelectedIds',
else: {
if: 'isPointingShape',
then: [
'setPointedId',
{
unless: 'isPointedShapeSelected',
do: [
'clearSelectedIds',
'pushPointedIdToSelectedIds',
],
},
],
},
},
],
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-06-15 12:20:22 +00:00
STOPPED_POINTING_BOUNDS: [],
2021-05-17 21:27:18 +00:00
STOPPED_POINTING: [
2021-06-15 12:20:22 +00:00
{
if: 'isPointingBounds',
do: 'clearSelectedIds',
},
2021-05-17 21:27:18 +00:00
{
2021-05-28 16:25:43 +00:00
if: 'isPressingShiftKey',
then: [
{
if: 'isPointedShapeSelected',
do: 'pullPointedIdFromSelectedIds',
},
],
2021-05-20 08:19:13 +00:00
else: {
2021-05-28 16:25:43 +00:00
unless: 'isPointingBounds',
do: ['clearSelectedIds', 'pushPointedIdToSelectedIds'],
2021-05-20 08:19:13 +00:00
},
2021-05-17 21:27:18 +00:00
},
2021-05-28 16:25:43 +00:00
{ to: 'notPointing' },
2021-05-17 21:27:18 +00:00
],
2021-05-15 17:11:08 +00:00
MOVED_POINTER: {
2021-05-28 16:25:43 +00:00
unless: 'isReadOnly',
if: 'distanceImpliesDrag',
2021-05-31 19:13:43 +00:00
to: 'translatingSelection',
2021-05-15 17:11:08 +00:00
},
},
},
2021-05-17 21:27:18 +00:00
rotatingSelection: {
2021-05-28 16:25:43 +00:00
onEnter: 'startRotateSession',
onExit: 'clearBoundsRotation',
2021-05-17 21:27:18 +00:00
on: {
2021-05-28 16:25:43 +00:00
MOVED_POINTER: 'updateRotateSession',
PANNED_CAMERA: 'updateRotateSession',
PRESSED_SHIFT_KEY: 'keyUpdateRotateSession',
RELEASED_SHIFT_KEY: 'keyUpdateRotateSession',
STOPPED_POINTING: { do: 'completeSession', to: 'selecting' },
CANCELLED: { do: 'cancelSession', to: 'selecting' },
2021-05-17 21:27:18 +00:00
},
},
transformingSelection: {
2021-05-28 16:25:43 +00:00
onEnter: 'startTransformSession',
2021-05-17 21:27:18 +00:00
on: {
2021-05-28 16:25:43 +00:00
MOVED_POINTER: 'updateTransformSession',
PANNED_CAMERA: 'updateTransformSession',
PRESSED_SHIFT_KEY: 'keyUpdateTransformSession',
RELEASED_SHIFT_KEY: 'keyUpdateTransformSession',
STOPPED_POINTING: { do: 'completeSession', to: 'selecting' },
CANCELLED: { do: 'cancelSession', to: 'selecting' },
2021-05-17 21:27:18 +00:00
},
},
2021-05-31 19:13:43 +00:00
translatingSelection: {
2021-05-28 16:25:43 +00:00
onEnter: 'startTranslateSession',
2021-05-15 17:11:08 +00:00
on: {
2021-05-28 16:25:43 +00:00
MOVED_POINTER: 'updateTranslateSession',
PANNED_CAMERA: 'updateTranslateSession',
PRESSED_SHIFT_KEY: 'keyUpdateTranslateSession',
RELEASED_SHIFT_KEY: 'keyUpdateTranslateSession',
PRESSED_ALT_KEY: 'keyUpdateTranslateSession',
RELEASED_ALT_KEY: 'keyUpdateTranslateSession',
STOPPED_POINTING: { do: 'completeSession', to: 'selecting' },
CANCELLED: { do: 'cancelSession', to: 'selecting' },
2021-05-15 17:11:08 +00:00
},
},
2021-05-31 19:13:43 +00:00
translatingHandles: {
onEnter: 'startHandleSession',
on: {
MOVED_POINTER: 'updateHandleSession',
PANNED_CAMERA: 'updateHandleSession',
PRESSED_SHIFT_KEY: 'keyUpdateHandleSession',
RELEASED_SHIFT_KEY: 'keyUpdateHandleSession',
STOPPED_POINTING: { do: 'completeSession', to: 'selecting' },
CANCELLED: { do: 'cancelSession', to: 'selecting' },
},
},
2021-05-17 21:27:18 +00:00
brushSelecting: {
onEnter: [
2021-05-20 09:25:14 +00:00
{
2021-05-28 16:25:43 +00:00
unless: ['isPressingMetaKey', 'isPressingShiftKey'],
do: 'clearSelectedIds',
2021-05-20 09:25:14 +00:00
},
2021-05-28 16:25:43 +00:00
'clearBoundsRotation',
'startBrushSession',
2021-05-17 21:27:18 +00:00
],
on: {
STARTED_PINCHING: { do: 'completeSession', to: 'pinching' },
// Currently using hacks.fastBrushSelect
// MOVED_POINTER: 'updateBrushSession',
2021-05-28 16:25:43 +00:00
PANNED_CAMERA: 'updateBrushSession',
STOPPED_POINTING: { do: 'completeSession', to: 'selecting' },
CANCELLED: { do: 'cancelSession', to: 'selecting' },
2021-05-17 21:27:18 +00:00
},
2021-05-15 15:20:21 +00:00
},
},
},
editingShape: {
onEnter: 'startEditSession',
onExit: 'clearEditingId',
on: {
EDITED_SHAPE: { do: 'updateEditSession' },
BLURRED_SHAPE: { do: 'completeSession', to: 'selecting' },
CANCELLED: { do: 'cancelSession', to: 'selecting' },
},
},
pinching: {
on: {
// Pinching uses hacks.fastPinchCamera
// PINCHED: { do: 'pinchCamera' },
},
initial: 'selectPinching',
onExit: { secretlyDo: 'updateZoomCSS' },
states: {
selectPinching: {
on: {
STOPPED_PINCHING: { to: 'selecting' },
},
},
toolPinching: {
on: {
STOPPED_PINCHING: { to: 'usingTool.previous' },
},
},
},
2021-05-28 13:08:51 +00:00
},
2021-05-28 20:30:27 +00:00
usingTool: {
initial: 'draw',
2021-05-28 21:05:40 +00:00
onEnter: 'clearSelectedIds',
2021-05-29 13:59:11 +00:00
on: {
STARTED_PINCHING: {
do: 'breakSession',
to: 'pinching.toolPinching',
},
2021-05-29 13:59:11 +00:00
TOGGLED_TOOL_LOCK: 'toggleToolLock',
SELECTED_SELECT_TOOL: { to: 'selecting' },
SELECTED_DRAW_TOOL: { unless: 'isReadOnly', to: 'draw' },
SELECTED_ARROW_TOOL: { unless: 'isReadOnly', to: 'arrow' },
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' },
ZOOMED_CAMERA: {
do: 'zoomCamera',
},
PANNED_CAMERA: {
do: 'panCamera',
},
ZOOMED_TO_ACTUAL: {
if: 'hasSelection',
do: 'zoomCameraToSelectionActual',
else: 'zoomCameraToActual',
},
ZOOMED_TO_SELECTION: {
if: 'hasSelection',
do: 'zoomCameraToSelection',
},
ZOOMED_TO_FIT: ['zoomCameraToFit', 'zoomCameraToActual'],
ZOOMED_IN: 'zoomIn',
ZOOMED_OUT: 'zoomOut',
RESET_CAMERA: 'resetCamera',
2021-05-29 13:59:11 +00:00
},
2021-05-27 17:59:40 +00:00
states: {
2021-05-28 20:30:27 +00:00
draw: {
2021-06-02 21:17:38 +00:00
onEnter: 'setActiveToolDraw',
2021-05-28 20:30:27 +00:00
initial: 'creating',
states: {
creating: {
on: {
CANCELLED: { to: 'selecting' },
2021-05-28 21:05:40 +00:00
POINTED_SHAPE: {
get: 'newDraw',
do: 'createShape',
to: 'draw.editing',
},
2021-05-28 20:30:27 +00:00
POINTED_CANVAS: {
get: 'newDraw',
do: 'createShape',
to: 'draw.editing',
},
UNDO: 'undo',
REDO: 'redo',
SAVED: 'forceSave',
LOADED_FROM_FILE_STSTEM: 'loadFromFileSystem',
SAVED_TO_FILESYSTEM: 'saveToFileSystem',
SAVED_AS_TO_FILESYSTEM: 'saveAsToFileSystem',
2021-05-28 20:30:27 +00:00
},
},
editing: {
onEnter: 'startDrawSession',
on: {
CANCELLED: {
do: 'breakSession',
2021-05-28 20:30:27 +00:00
to: 'selecting',
},
STOPPED_POINTING: {
do: 'completeSession',
to: 'draw.creating',
},
PRESSED_SHIFT: 'keyUpdateDrawSession',
RELEASED_SHIFT: 'keyUpdateDrawSession',
// MOVED_POINTER: 'updateDrawSession',
2021-05-28 20:30:27 +00:00
PANNED_CAMERA: 'updateDrawSession',
},
2021-05-27 17:59:40 +00:00
},
},
},
2021-05-28 20:30:27 +00:00
dot: {
2021-06-02 21:17:38 +00:00
onEnter: 'setActiveToolDot',
2021-05-28 20:30:27 +00:00
initial: 'creating',
states: {
creating: {
on: {
CANCELLED: { to: 'selecting' },
2021-05-28 21:05:40 +00:00
POINTED_SHAPE: {
get: 'newDot',
do: 'createShape',
to: 'dot.editing',
},
2021-05-28 20:30:27 +00:00
POINTED_CANVAS: {
get: 'newDot',
do: 'createShape',
to: 'dot.editing',
},
},
2021-05-27 22:07:27 +00:00
},
2021-05-28 20:30:27 +00:00
editing: {
on: {
STOPPED_POINTING: [
'completeSession',
{
if: 'isToolLocked',
to: 'dot.creating',
else: {
to: 'selecting',
},
},
],
CANCELLED: {
do: 'breakSession',
2021-05-28 20:30:27 +00:00
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-27 17:59:40 +00:00
},
},
},
2021-05-31 19:13:43 +00:00
arrow: {
2021-06-02 21:17:38 +00:00
onEnter: 'setActiveToolArrow',
2021-05-31 19:13:43 +00:00
initial: 'creating',
states: {
creating: {
on: {
CANCELLED: { to: 'selecting' },
POINTED_SHAPE: {
get: 'newArrow',
do: 'createShape',
to: 'arrow.editing',
},
POINTED_CANVAS: {
get: 'newArrow',
do: 'createShape',
to: 'arrow.editing',
},
UNDO: { do: 'undo' },
REDO: { do: 'redo' },
},
},
editing: {
onEnter: 'startArrowSession',
on: {
STOPPED_POINTING: [
'completeSession',
{
if: 'isToolLocked',
to: 'arrow.creating',
else: { to: 'selecting' },
},
],
CANCELLED: {
do: 'breakSession',
if: 'isToolLocked',
to: 'arrow.creating',
else: { to: 'selecting' },
},
PRESSED_SHIFT: 'keyUpdateArrowSession',
RELEASED_SHIFT: 'keyUpdateArrowSession',
MOVED_POINTER: 'updateArrowSession',
PANNED_CAMERA: 'updateArrowSession',
},
},
},
},
2021-05-28 20:30:27 +00:00
circle: {
2021-06-02 21:17:38 +00:00
onEnter: 'setActiveToolCircle',
2021-05-28 20:30:27 +00:00
initial: 'creating',
states: {
creating: {
on: {
CANCELLED: { to: 'selecting' },
2021-05-28 21:05:40 +00:00
POINTED_SHAPE: {
to: 'circle.editing',
},
2021-05-28 20:30:27 +00:00
POINTED_CANVAS: {
to: 'circle.editing',
},
},
2021-05-17 21:27:18 +00:00
},
2021-05-28 20:30:27 +00:00
editing: {
on: {
STOPPED_POINTING: { to: 'selecting' },
CANCELLED: { to: 'selecting' },
MOVED_POINTER: {
if: 'distanceImpliesDrag',
then: {
get: 'newCircle',
do: 'createShape',
to: 'drawingShape.bounds',
},
},
},
2021-05-17 21:27:18 +00:00
},
},
2021-05-28 20:30:27 +00:00
},
ellipse: {
2021-06-02 21:17:38 +00:00
onEnter: 'setActiveToolEllipse',
2021-05-28 20:30:27 +00:00
initial: 'creating',
2021-05-17 21:27:18 +00:00
states: {
2021-05-28 20:30:27 +00:00
creating: {
2021-05-17 21:27:18 +00:00
on: {
2021-05-28 20:30:27 +00:00
CANCELLED: { to: 'selecting' },
POINTED_CANVAS: {
to: 'ellipse.editing',
2021-05-17 21:27:18 +00:00
},
},
},
2021-05-28 20:30:27 +00:00
editing: {
2021-05-17 21:27:18 +00:00
on: {
2021-05-28 20:30:27 +00:00
STOPPED_POINTING: { to: 'selecting' },
CANCELLED: { to: 'selecting' },
MOVED_POINTER: {
if: 'distanceImpliesDrag',
then: {
get: 'newEllipse',
do: 'createShape',
to: 'drawingShape.bounds',
},
},
2021-05-17 21:27:18 +00:00
},
},
},
2021-05-15 15:20:21 +00:00
},
2021-05-28 20:30:27 +00:00
rectangle: {
2021-06-02 21:17:38 +00:00
onEnter: 'setActiveToolRectangle',
2021-05-28 20:30:27 +00:00
initial: 'creating',
states: {
creating: {
on: {
CANCELLED: { to: 'selecting' },
2021-05-28 21:05:40 +00:00
POINTED_SHAPE: {
to: 'rectangle.editing',
},
2021-05-28 20:30:27 +00:00
POINTED_CANVAS: {
to: 'rectangle.editing',
},
},
2021-05-15 17:11:08 +00:00
},
2021-05-28 20:30:27 +00:00
editing: {
on: {
STOPPED_POINTING: { to: 'selecting' },
CANCELLED: { to: 'selecting' },
MOVED_POINTER: {
if: 'distanceImpliesDrag',
then: {
get: 'newRectangle',
do: 'createShape',
to: 'drawingShape.bounds',
},
},
2021-05-23 13:46:04 +00:00
},
2021-05-17 21:27:18 +00:00
},
2021-05-15 17:11:08 +00:00
},
},
2021-05-28 20:30:27 +00:00
ray: {
2021-06-02 21:17:38 +00:00
onEnter: 'setActiveToolRay',
2021-05-28 20:30:27 +00:00
initial: 'creating',
states: {
creating: {
on: {
CANCELLED: { to: 'selecting' },
2021-05-28 21:05:40 +00:00
POINTED_SHAPE: {
get: 'newRay',
do: 'createShape',
to: 'ray.editing',
},
2021-05-28 20:30:27 +00:00
POINTED_CANVAS: {
get: 'newRay',
do: 'createShape',
to: 'ray.editing',
},
},
2021-05-17 21:27:18 +00:00
},
2021-05-28 20:30:27 +00:00
editing: {
on: {
STOPPED_POINTING: { to: 'selecting' },
CANCELLED: { to: 'selecting' },
MOVED_POINTER: {
if: 'distanceImpliesDrag',
to: 'drawingShape.direction',
},
2021-05-23 13:46:04 +00:00
},
2021-05-17 21:27:18 +00:00
},
},
2021-05-15 17:11:08 +00:00
},
2021-05-28 20:30:27 +00:00
line: {
2021-06-02 21:17:38 +00:00
onEnter: 'setActiveToolLine',
2021-05-28 20:30:27 +00:00
initial: 'creating',
states: {
creating: {
on: {
CANCELLED: { to: 'selecting' },
2021-05-28 21:05:40 +00:00
POINTED_SHAPE: {
get: 'newLine',
do: 'createShape',
to: 'line.editing',
},
2021-05-28 20:30:27 +00:00
POINTED_CANVAS: {
get: 'newLine',
do: 'createShape',
to: 'line.editing',
},
},
2021-05-17 21:27:18 +00:00
},
2021-05-28 20:30:27 +00:00
editing: {
on: {
STOPPED_POINTING: { to: 'selecting' },
CANCELLED: { to: 'selecting' },
MOVED_POINTER: {
if: 'distanceImpliesDrag',
to: 'drawingShape.direction',
},
2021-05-23 13:46:04 +00:00
},
2021-05-17 21:27:18 +00:00
},
},
2021-05-15 17:11:08 +00:00
},
2021-06-02 21:17:38 +00:00
polyline: {
onEnter: 'setActiveToolPolyline',
},
2021-05-15 17:11:08 +00:00
},
2021-05-17 21:27:18 +00:00
},
2021-05-28 20:30:27 +00:00
drawingShape: {
on: {
STOPPED_POINTING: [
'completeSession',
{
if: 'isToolLocked',
to: 'usingTool.previous',
else: { to: 'selecting' },
2021-05-15 17:11:08 +00:00
},
2021-05-28 20:30:27 +00:00
],
CANCELLED: {
do: 'breakSession',
2021-05-28 20:30:27 +00:00
to: 'selecting',
2021-05-15 17:11:08 +00:00
},
2021-05-17 21:27:18 +00:00
},
2021-05-28 20:30:27 +00:00
initial: 'drawingShapeBounds',
2021-05-17 21:27:18 +00:00
states: {
2021-05-28 20:30:27 +00:00
bounds: {
onEnter: 'startDrawTransformSession',
2021-05-15 17:11:08 +00:00
on: {
2021-05-28 20:30:27 +00:00
MOVED_POINTER: 'updateTransformSession',
PANNED_CAMERA: 'updateTransformSession',
2021-05-17 21:27:18 +00:00
},
},
2021-05-28 20:30:27 +00:00
direction: {
onEnter: 'startDirectionSession',
2021-05-17 21:27:18 +00:00
on: {
2021-05-28 20:30:27 +00:00
MOVED_POINTER: 'updateDirectionSession',
PANNED_CAMERA: 'updateDirectionSession',
2021-05-15 17:11:08 +00:00
},
},
},
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: {
2021-05-31 19:13:43 +00:00
newArrow() {
return ShapeType.Arrow
},
2021-05-27 17:59:40 +00:00
newDraw() {
return ShapeType.Draw
},
2021-05-26 10:34:10 +00:00
newDot() {
return ShapeType.Dot
2021-05-23 13:46:04 +00:00
},
2021-05-26 10:34:10 +00:00
newRay() {
return ShapeType.Ray
2021-05-23 13:46:04 +00:00
},
2021-05-26 10:34:10 +00:00
newLine() {
return ShapeType.Line
2021-05-23 13:46:04 +00:00
},
2021-05-26 10:34:10 +00:00
newCircle() {
return ShapeType.Circle
2021-05-23 13:46:04 +00:00
},
2021-05-26 10:34:10 +00:00
newEllipse() {
return ShapeType.Ellipse
2021-05-23 13:46:04 +00:00
},
2021-05-26 10:34:10 +00:00
newRectangle() {
return ShapeType.Rectangle
2021-05-23 13:46:04 +00:00
},
firstSelectedShape(data) {
return getSelectedShapes(data)[0]
},
2021-05-23 13:46:04 +00:00
},
conditions: {
2021-06-10 09:49:16 +00:00
isPointingCanvas(data, payload: PointerInfo) {
return payload.target === 'canvas'
},
isPointingBounds(data, payload: PointerInfo) {
return getSelectedIds(data).size > 0 && payload.target === 'bounds'
},
2021-06-10 09:49:16 +00:00
isPointingShape(data, payload: PointerInfo) {
return (
payload.target &&
payload.target !== 'canvas' &&
payload.target !== 'bounds'
)
},
2021-05-13 06:44:52 +00:00
isReadOnly(data) {
return data.isReadOnly
},
canEditSelectedShape(data, payload, result: Shape) {
return getShapeUtils(result).canEdit
},
2021-05-13 06:44:52 +00:00
distanceImpliesDrag(data, payload: PointerInfo) {
return vec.dist2(payload.origin, payload.point) > 8
2021-05-13 06:44:52 +00:00
},
2021-06-10 09:49:16 +00:00
hasPointedTarget(data, payload: PointerInfo) {
return payload.target !== undefined
},
isPointedShapeSelected(data) {
return getSelectedIds(data).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
},
2021-06-08 11:27:47 +00:00
pointInSelectionBounds(data, payload: PointerInfo) {
if (getSelectedIds(data).size === 0) return false
return pointInBounds(
screenToWorld(payload.point, data),
getSelectionBounds(data)
2021-06-08 11:27:47 +00:00
)
},
pointHitsShape(data, payload: PointerInfo) {
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,
2021-05-28 16:25:43 +00:00
payload: { target: Edge | Corner | 'rotate' }
) {
2021-05-28 16:25:43 +00:00
return payload.target === 'rotate'
},
hasSelection(data) {
return getSelectedIds(data).size > 0
},
hasSingleSelection(data) {
return getSelectedIds(data).size === 1
},
2021-06-04 16:08:43 +00:00
hasMultipleSelection(data) {
return getSelectedIds(data).size > 1
2021-06-04 16:08:43 +00:00
},
2021-05-28 20:30:27 +00:00
isToolLocked(data) {
return data.settings.isToolLocked
},
isPenLocked(data) {
return data.settings.isPenLocked
},
2021-06-03 13:10:54 +00:00
hasOnlyOnePage(data) {
return Object.keys(data.document.pages).length === 1
},
2021-06-04 16:08:43 +00:00
selectionIncludesGroups(data) {
return getSelectedShapes(data).some(
(shape) => shape.type === ShapeType.Group
)
},
},
2021-05-09 13:04:42 +00:00
actions: {
2021-06-03 12:06:39 +00:00
/* ---------------------- Pages --------------------- */
changePage(data, payload: { id: string }) {
2021-06-03 12:06:39 +00:00
commands.changePage(data, payload.id)
},
2021-06-03 13:10:54 +00:00
createPage(data) {
commands.createPage(data)
},
deletePage(data, payload: { id: string }) {
commands.deletePage(data, payload.id)
},
2021-06-03 12:06:39 +00:00
2021-05-15 15:20:21 +00:00
/* --------------------- Shapes --------------------- */
2021-05-26 10:34:10 +00:00
createShape(data, payload, type: ShapeType) {
const shape = createShape(type, {
2021-06-04 16:08:43 +00:00
parentId: data.currentPageId,
2021-06-13 13:55:37 +00:00
point: vec.round(screenToWorld(payload.point, data)),
2021-05-26 10:34:10 +00:00
style: getCurrent(data.currentStyle),
})
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).setProperty(shape, 'childIndex', childIndex)
2021-05-17 21:27:18 +00:00
getPage(data).shapes[shape.id] = shape
setSelectedIds(data, [shape.id])
2021-05-17 21:27:18 +00:00
},
2021-05-15 15:20:21 +00:00
/* -------------------- Sessions -------------------- */
// Shared
breakSession(data) {
session.current?.cancel(data)
session.clear()
history.disable()
commands.deleteSelected(data)
history.enable()
},
2021-05-10 12:16:57 +00:00
cancelSession(data) {
session.current?.cancel(data)
session.clear()
2021-05-10 12:16:57 +00:00
},
completeSession(data) {
session.current?.complete(data)
session.clear()
2021-05-10 12:16:57 +00:00
},
2021-05-13 08:34:56 +00:00
// Editing
startEditSession(data) {
session.current = new Sessions.EditSession(data)
},
updateEditSession(data, payload: { change: Partial<Shape> }) {
session.current.update(data, payload.change)
},
2021-05-13 06:44:52 +00:00
// Brushing
startBrushSession(data, payload: PointerInfo) {
session.current = new Sessions.BrushSession(
2021-05-10 20:44:17 +00:00
data,
screenToWorld(payload.point, data)
)
2021-05-10 12:16:57 +00:00
},
updateBrushSession(data, payload: PointerInfo) {
session.current.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.current = new Sessions.RotateSession(
2021-05-17 21:27:18 +00:00
data,
screenToWorld(payload.point, data)
)
},
keyUpdateRotateSession(data, payload: PointerInfo) {
session.current.update(
data,
screenToWorld(inputs.pointer.point, data),
payload.shiftKey
)
},
2021-05-17 21:27:18 +00:00
updateRotateSession(data, payload: PointerInfo) {
session.current.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) {
session.current = new Sessions.TranslateSession(
2021-05-13 06:44:52 +00:00
data,
screenToWorld(inputs.pointer.origin, data)
2021-05-13 06:44:52 +00:00
)
},
2021-05-20 08:19:13 +00:00
keyUpdateTranslateSession(
data,
payload: { shiftKey: boolean; altKey: boolean }
) {
session.current.update(
2021-05-19 21:24:41 +00:00
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) {
session.current.update(
2021-05-20 08:19:13 +00:00
data,
screenToWorld(payload.point, data),
payload.shiftKey,
payload.altKey
)
2021-05-13 06:44:52 +00:00
},
2021-05-31 19:13:43 +00:00
// Dragging Handle
startHandleSession(data, payload: PointerInfo) {
const shapeId = Array.from(getSelectedIds(data).values())[0]
2021-05-31 19:13:43 +00:00
const handleId = payload.target
session.current = new Sessions.HandleSession(
2021-05-31 19:13:43 +00:00
data,
shapeId,
handleId,
screenToWorld(inputs.pointer.origin, data)
)
},
keyUpdateHandleSession(
data,
payload: { shiftKey: boolean; altKey: boolean }
) {
session.current.update(
2021-05-31 19:13:43 +00:00
data,
screenToWorld(inputs.pointer.point, data),
payload.shiftKey,
payload.altKey
)
},
updateHandleSession(data, payload: PointerInfo) {
session.current.update(
2021-05-31 19:13:43 +00:00
data,
screenToWorld(payload.point, data),
payload.shiftKey,
payload.altKey
)
},
// Transforming
2021-05-14 12:44:23 +00:00
startTransformSession(
data,
payload: PointerInfo & { target: Corner | Edge }
2021-05-14 12:44:23 +00:00
) {
const point = screenToWorld(inputs.pointer.origin, data)
session.current =
getSelectedIds(data).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.current = 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.current.update(
2021-05-21 07:42:56 +00:00
data,
screenToWorld(inputs.pointer.point, data),
payload.shiftKey,
payload.altKey
)
},
2021-05-14 12:44:23 +00:00
updateTransformSession(data, payload: PointerInfo) {
session.current.update(
2021-05-21 07:42:56 +00:00
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.current = new Sessions.DirectionSession(
2021-05-15 15:20:21 +00:00
data,
screenToWorld(inputs.pointer.origin, data)
2021-05-15 15:20:21 +00:00
)
},
updateDirectionSession(data, payload: PointerInfo) {
session.current.update(data, screenToWorld(payload.point, data))
2021-05-15 15:20:21 +00:00
},
2021-05-27 17:59:40 +00:00
// Drawing
startDrawSession(data, payload: PointerInfo) {
const id = Array.from(getSelectedIds(data).values())[0]
session.current = new Sessions.DrawSession(
2021-05-27 17:59:40 +00:00
data,
id,
screenToWorld(inputs.pointer.origin, data),
payload.shiftKey
)
},
keyUpdateDrawSession(data, payload: PointerInfo) {
session.current.update(
data,
screenToWorld(inputs.pointer.point, data),
2021-06-06 07:33:30 +00:00
payload.pressure,
payload.shiftKey
2021-05-27 17:59:40 +00:00
)
},
updateDrawSession(data, payload: PointerInfo) {
session.current.update(
2021-06-06 07:33:30 +00:00
data,
screenToWorld(payload.point, data),
payload.pressure,
payload.shiftKey
)
2021-05-27 17:59:40 +00:00
},
2021-05-31 19:13:43 +00:00
// Arrow
startArrowSession(data, payload: PointerInfo) {
const id = Array.from(getSelectedIds(data).values())[0]
session.current = new Sessions.ArrowSession(
2021-05-31 19:13:43 +00:00
data,
id,
screenToWorld(inputs.pointer.origin, data),
payload.shiftKey
)
},
keyUpdateArrowSession(data, payload: PointerInfo) {
session.current.update(
2021-05-31 19:13:43 +00:00
data,
screenToWorld(inputs.pointer.point, data),
payload.shiftKey
)
},
updateArrowSession(data, payload: PointerInfo) {
session.current.update(
data,
screenToWorld(payload.point, data),
payload.shiftKey
)
2021-05-31 19:13:43 +00:00
},
2021-05-28 20:30:27 +00:00
// Nudges
nudgeSelection(data, payload: { delta: number[]; shiftKey: boolean }) {
commands.nudge(
data,
vec.mul(
payload.delta,
payload.shiftKey
? data.settings.nudgeDistanceLarge
: data.settings.nudgeDistanceSmall
)
)
},
2021-05-15 15:20:21 +00:00
/* -------------------- Selection ------------------- */
2021-05-17 21:27:18 +00:00
selectAll(data) {
const selectedIds = getSelectedIds(data)
const page = getPage(data)
2021-05-17 21:27:18 +00:00
selectedIds.clear()
for (let id in page.shapes) {
2021-06-04 17:56:46 +00:00
if (page.shapes[id].parentId === data.currentPageId) {
selectedIds.add(id)
}
2021-05-17 21:27:18 +00:00
}
},
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) {
2021-06-04 16:08:43 +00:00
data.pointedId = getPointedId(data, payload.target)
data.currentParentId = getParentId(data, data.pointedId)
},
setDrilledPointedId(data, payload: PointerInfo) {
data.pointedId = getDrilledPointedId(data, payload.target)
data.currentParentId = getParentId(data, data.pointedId)
},
clearCurrentParentId(data) {
data.currentParentId = data.currentPageId
data.pointedId = undefined
},
clearPointedId(data) {
data.pointedId = undefined
},
clearSelectedIds(data) {
setSelectedIds(data, [])
},
pullPointedIdFromSelectedIds(data) {
const { pointedId } = data
const selectedIds = getSelectedIds(data)
2021-05-12 11:27:33 +00:00
selectedIds.delete(pointedId)
},
pushPointedIdToSelectedIds(data) {
getSelectedIds(data).add(data.pointedId)
},
2021-05-26 19:20:52 +00:00
moveSelection(data, payload: { type: MoveType }) {
commands.move(data, payload.type)
2021-05-23 13:46:04 +00:00
},
2021-06-10 09:49:16 +00:00
moveSelectionToPage(data, payload: { id: string }) {
commands.moveToPage(data, payload.id)
},
2021-05-26 19:20:52 +00:00
alignSelection(data, payload: { type: AlignType }) {
commands.align(data, payload.type)
2021-05-23 13:46:04 +00:00
},
2021-05-26 19:20:52 +00:00
stretchSelection(data, payload: { type: StretchType }) {
commands.stretch(data, payload.type)
2021-05-23 13:46:04 +00:00
},
2021-05-26 19:20:52 +00:00
distributeSelection(data, payload: { type: DistributeType }) {
commands.distribute(data, payload.type)
2021-05-23 13:46:04 +00:00
},
2021-05-28 20:30:27 +00:00
duplicateSelection(data) {
commands.duplicate(data)
},
lockSelection(data) {
commands.toggle(data, 'isLocked')
},
hideSelection(data) {
commands.toggle(data, 'isHidden')
},
aspectLockSelection(data) {
commands.toggle(data, 'isAspectRatioLocked')
},
2021-05-29 13:59:11 +00:00
deleteSelection(data) {
commands.deleteSelected(data)
},
2021-05-29 22:27:19 +00:00
rotateSelectionCcw(data) {
commands.rotateCcw(data)
},
2021-06-04 16:08:43 +00:00
groupSelection(data) {
commands.group(data)
},
ungroupSelection(data) {
commands.ungroup(data)
},
resetShapeBounds(data) {
commands.resetBounds(data)
},
/* --------------------- Editing -------------------- */
setEditingId(data) {
const selectedShape = getSelectedShapes(data)[0]
if (getShapeUtils(selectedShape).canEdit) {
data.editingId = selectedShape.id
}
getPageState(data).selectedIds = new Set([selectedShape.id])
},
clearEditingId(data) {
data.editingId = null
},
2021-05-23 13:46:04 +00:00
2021-06-02 21:17:38 +00:00
/* ---------------------- Tool ---------------------- */
setActiveTool(data, payload: { tool: ShapeType | 'select' }) {
data.activeTool = payload.tool
},
setActiveToolSelect(data) {
data.activeTool = 'select'
},
setActiveToolDraw(data) {
data.activeTool = ShapeType.Draw
},
setActiveToolRectangle(data) {
data.activeTool = ShapeType.Rectangle
},
setActiveToolEllipse(data) {
data.activeTool = ShapeType.Ellipse
},
setActiveToolArrow(data) {
data.activeTool = ShapeType.Arrow
},
setActiveToolDot(data) {
data.activeTool = ShapeType.Dot
},
setActiveToolPolyline(data) {
data.activeTool = ShapeType.Polyline
},
setActiveToolRay(data) {
data.activeTool = ShapeType.Ray
},
setActiveToolCircle(data) {
data.activeTool = ShapeType.Circle
},
setActiveToolLine(data) {
data.activeTool = ShapeType.Line
},
2021-05-23 13:46:04 +00:00
/* --------------------- Camera --------------------- */
2021-05-29 13:59:11 +00:00
zoomIn(data) {
2021-06-03 12:06:39 +00:00
const camera = getCurrentCamera(data)
2021-05-29 13:59:11 +00:00
const i = Math.round((camera.zoom * 100) / 25)
const center = [window.innerWidth / 2, window.innerHeight / 2]
2021-05-17 10:01:11 +00:00
2021-05-29 13:59:11 +00:00
const p0 = screenToWorld(center, data)
2021-06-02 21:17:38 +00:00
camera.zoom = getCameraZoom((i + 1) * 0.25)
2021-05-29 13:59:11 +00:00
const p1 = screenToWorld(center, data)
camera.point = vec.add(camera.point, vec.sub(p1, p0))
setZoomCSS(camera.zoom)
},
zoomOut(data) {
2021-06-03 12:06:39 +00:00
const camera = getCurrentCamera(data)
2021-05-29 13:59:11 +00:00
const i = Math.round((camera.zoom * 100) / 25)
const center = [window.innerWidth / 2, window.innerHeight / 2]
const p0 = screenToWorld(center, data)
2021-06-02 21:17:38 +00:00
camera.zoom = getCameraZoom((i - 1) * 0.25)
2021-05-29 13:59:11 +00:00
const p1 = screenToWorld(center, data)
camera.point = vec.add(camera.point, vec.sub(p1, p0))
setZoomCSS(camera.zoom)
2021-05-17 10:01:11 +00:00
},
2021-05-26 10:34:10 +00:00
zoomCameraToActual(data) {
2021-06-03 12:06:39 +00:00
const camera = getCurrentCamera(data)
2021-05-26 10:34:10 +00:00
const center = [window.innerWidth / 2, window.innerHeight / 2]
2021-05-26 10:34:10 +00:00
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)
},
zoomCameraToSelectionActual(data) {
2021-06-03 12:06:39 +00:00
const camera = getCurrentCamera(data)
const bounds = getSelectedBounds(data)
2021-05-26 10:34:10 +00:00
const mx = (window.innerWidth - bounds.width) / 2
const my = (window.innerHeight - bounds.height) / 2
2021-05-25 11:38:21 +00:00
camera.zoom = 1
2021-05-26 10:34:10 +00:00
camera.point = vec.add([-bounds.minX, -bounds.minY], [mx, my])
setZoomCSS(camera.zoom)
},
2021-05-26 10:34:10 +00:00
zoomCameraToSelection(data) {
2021-06-03 12:06:39 +00:00
const camera = getCurrentCamera(data)
2021-05-26 10:34:10 +00:00
const bounds = getSelectedBounds(data)
2021-06-02 21:17:38 +00:00
const zoom = getCameraZoom(
2021-05-26 10:34:10 +00:00
bounds.width > bounds.height
? (window.innerWidth - 128) / bounds.width
: (window.innerHeight - 128) / bounds.height
2021-06-02 21:17:38 +00:00
)
2021-05-26 10:34:10 +00:00
const mx = (window.innerWidth - bounds.width * zoom) / 2 / zoom
const my = (window.innerHeight - bounds.height * zoom) / 2 / zoom
camera.zoom = zoom
camera.point = vec.add([-bounds.minX, -bounds.minY], [mx, my])
setZoomCSS(camera.zoom)
},
zoomCameraToFit(data) {
2021-06-03 12:06:39 +00:00
const camera = getCurrentCamera(data)
2021-05-25 11:38:21 +00:00
const page = getPage(data)
const shapes = Object.values(page.shapes)
if (shapes.length === 0) {
return
}
const bounds = getCommonBounds(
...Object.values(shapes).map((shape) =>
getShapeUtils(shape).getBounds(shape)
)
)
2021-06-02 21:17:38 +00:00
const zoom = getCameraZoom(
2021-05-26 10:34:10 +00:00
bounds.width > bounds.height
2021-05-25 11:38:21 +00:00
? (window.innerWidth - 128) / bounds.width
: (window.innerHeight - 128) / bounds.height
2021-06-02 21:17:38 +00:00
)
2021-05-25 11:38:21 +00:00
const mx = (window.innerWidth - bounds.width * zoom) / 2 / zoom
const my = (window.innerHeight - bounds.height * zoom) / 2 / zoom
camera.zoom = zoom
2021-05-25 11:38:21 +00:00
camera.point = vec.add([-bounds.minX, -bounds.minY], [mx, my])
setZoomCSS(camera.zoom)
},
2021-05-09 13:04:42 +00:00
zoomCamera(data, payload: { delta: number; point: number[] }) {
2021-06-03 12:06:39 +00:00
const camera = getCurrentCamera(data)
2021-05-26 10:34:10 +00:00
const next = camera.zoom - (payload.delta / 100) * camera.zoom
2021-05-09 13:04:42 +00:00
const p0 = screenToWorld(payload.point, data)
2021-06-02 21:17:38 +00:00
camera.zoom = getCameraZoom(next)
2021-05-09 13:04:42 +00:00
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
},
2021-05-28 13:08:51 +00:00
panCamera(data, payload: { delta: number[] }) {
2021-06-03 12:06:39 +00:00
const camera = getCurrentCamera(data)
2021-05-28 13:08:51 +00:00
camera.point = vec.sub(camera.point, vec.div(payload.delta, camera.zoom))
},
updateZoomCSS(data) {
const camera = getCurrentCamera(data)
setZoomCSS(camera.zoom)
},
2021-05-28 13:08:51 +00:00
pinchCamera(
data,
payload: {
delta: number[]
distanceDelta: number
angleDelta: number
point: number[]
}
) {
2021-06-03 12:06:39 +00:00
const camera = getCurrentCamera(data)
2021-05-28 13:08:51 +00:00
camera.point = vec.sub(camera.point, vec.div(payload.delta, camera.zoom))
const next = camera.zoom - (payload.distanceDelta / 300) * camera.zoom
const p0 = screenToWorld(payload.point, data)
2021-06-02 21:17:38 +00:00
camera.zoom = getCameraZoom(next)
2021-05-28 13:08:51 +00:00
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
},
2021-05-29 13:59:11 +00:00
resetCamera(data) {
2021-06-03 12:06:39 +00:00
const camera = getCurrentCamera(data)
camera.zoom = 1
camera.point = [window.innerWidth / 2, window.innerHeight / 2]
2021-05-29 13:59:11 +00:00
document.documentElement.style.setProperty('--camera-zoom', '1')
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-15 15:20:21 +00:00
enableHistory() {
history.enable()
},
disableHistory() {
history.disable()
},
undo(data) {
history.undo(data)
},
redo(data) {
history.redo(data)
},
2021-05-26 10:34:10 +00:00
/* --------------------- Styles --------------------- */
toggleStylePanel(data) {
data.settings.isStyleOpen = !data.settings.isStyleOpen
},
2021-06-01 08:56:41 +00:00
closeStylePanel(data) {
data.settings.isStyleOpen = false
},
2021-05-26 10:34:10 +00:00
updateStyles(data, payload: Partial<ShapeStyles>) {
Object.assign(data.currentStyle, payload)
},
applyStylesToSelection(data, payload: Partial<ShapeStyles>) {
commands.style(data, payload)
},
2021-05-17 21:27:18 +00:00
/* ---------------------- Code ---------------------- */
2021-05-28 20:30:27 +00:00
2021-05-17 21:27:18 +00:00
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()
setSelectedIds(data, [])
2021-05-17 10:01:11 +00:00
try {
const { shapes } = updateFromCode(
2021-06-08 10:32:20 +00:00
data,
data.document.code[data.currentCodeFileId].code
2021-05-17 10:01:11 +00:00
)
commands.generate(data, data.currentPageId, shapes)
} catch (e) {
console.error(e)
}
history.enable()
},
2021-05-28 20:30:27 +00:00
/* -------------------- Settings -------------------- */
enablePenLock(data) {
data.settings.isPenLocked = true
},
disablePenLock(data) {
data.settings.isPenLocked = false
},
toggleToolLock(data) {
data.settings.isToolLocked = !data.settings.isToolLocked
},
/* ---------------------- Data ---------------------- */
restoreSavedData(data) {
storage.firstLoad(data)
},
saveToFileSystem(data) {
storage.saveToFileSystem(data)
},
saveAsToFileSystem(data) {
storage.saveAsToFileSystem(data)
},
loadFromFileSystem() {
storage.loadDocumentFromFilesystem()
},
loadDocumentFromJson(data, payload: { restoredData: any }) {
2021-06-11 22:06:09 +00:00
storage.loadDocumentFromJson(data, payload.restoredData)
},
forceSave(data) {
2021-06-11 10:31:31 +00:00
storage.saveToFileSystem(data)
},
savePage(data) {
storage.savePage(data)
},
loadPage(data) {
storage.loadPage(data)
},
2021-05-16 08:33:08 +00:00
saveCode(data, payload: { code: string }) {
data.document.code[data.currentCodeFileId].code = payload.code
storage.saveToLocalStorage(data)
2021-05-16 08:33:08 +00:00
},
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(getSelectedIds(data))
},
2021-05-12 22:08:53 +00:00
selectedBounds(data) {
return getSelectionBounds(data)
2021-05-12 22:08:53 +00:00
},
2021-06-01 21:49:32 +00:00
selectedStyle(data) {
const selectedIds = Array.from(getSelectedIds(data).values())
2021-06-01 21:49:32 +00:00
const { currentStyle } = data
if (selectedIds.length === 0) {
return currentStyle
}
const page = getPage(data)
2021-06-04 21:21:03 +00:00
2021-06-01 21:49:32 +00:00
const shapeStyles = selectedIds.map((id) => page.shapes[id].style)
const commonStyle: ShapeStyles = {} as ShapeStyles
2021-06-01 21:49:32 +00:00
const overrides = new Set<string>([])
for (const shapeStyle of shapeStyles) {
for (let key in currentStyle) {
if (overrides.has(key)) continue
if (commonStyle[key] === undefined) {
commonStyle[key] = shapeStyle[key]
} else {
if (commonStyle[key] === shapeStyle[key]) continue
commonStyle[key] = currentStyle[key]
overrides.add(key)
}
}
}
return commonStyle
},
},
2021-05-09 13:04:42 +00:00
})
2021-05-09 12:03:39 +00:00
export default state
export const useSelector = createSelectorHook(state)
2021-06-02 21:17:38 +00:00
2021-06-04 16:08:43 +00:00
function getParentId(data: Data, id: string) {
const shape = getPage(data).shapes[id]
return shape.parentId
}
function getPointedId(data: Data, id: string) {
const shape = getPage(data).shapes[id]
return shape.parentId === data.currentParentId ||
shape.parentId === data.currentPageId
? id
: getPointedId(data, shape.parentId)
}
function getDrilledPointedId(data: Data, id: string) {
const shape = getPage(data).shapes[id]
return shape.parentId === data.currentPageId ||
shape.parentId === data.pointedId ||
shape.parentId === data.currentParentId
? id
: getDrilledPointedId(data, shape.parentId)
}
function hasPointedIdInChildren(data: Data, id: string, pointedId: string) {
const shape = getPage(data).shapes[id]
if (shape.type !== ShapeType.Group) {
return false
}
if (shape.children.includes(pointedId)) {
return true
}
return shape.children.some((childId) =>
hasPointedIdInChildren(data, childId, pointedId)
)
}
function getSelectionBounds(data: Data) {
const selectedIds = getSelectedIds(data)
const page = getPage(data)
const shapes = Array.from(selectedIds.values())
.map((id) => page.shapes[id])
.filter(Boolean)
if (selectedIds.size === 0) return null
if (selectedIds.size === 1) {
if (!shapes[0]) {
console.error('Could not find that shape! Clearing selected IDs.')
setSelectedIds(data, [])
return null
}
const shape = shapes[0]
const shapeUtils = getShapeUtils(shape)
if (!shapeUtils.canTransform) return null
let bounds = shapeUtils.getBounds(shape)
let parentId = shape.parentId
while (parentId !== data.currentPageId) {
const parent = page.shapes[parentId]
bounds = rotateBounds(
bounds,
getBoundsCenter(getShapeUtils(parent).getBounds(parent)),
parent.rotation
)
bounds.rotation = parent.rotation
parentId = parent.parentId
}
return bounds
}
const uniqueSelectedShapeIds: string[] = Array.from(
new Set(
Array.from(selectedIds.values()).flatMap((id) =>
getDocumentBranch(data, id)
)
).values()
)
const commonBounds = getCommonBounds(
...uniqueSelectedShapeIds
.map((id) => page.shapes[id])
.filter((shape) => shape.type !== ShapeType.Group)
.map((shape) => getShapeUtils(shape).getRotatedBounds(shape))
)
return commonBounds
}