Continues on drawing / rotation

This commit is contained in:
Steve Ruiz 2021-05-28 17:25:43 +01:00
parent 79c254a938
commit c95c54dae6
11 changed files with 311 additions and 324 deletions

View file

@ -7,7 +7,6 @@ import CenterHandle from './center-handle'
import CornerHandle from './corner-handle' import CornerHandle from './corner-handle'
import EdgeHandle from './edge-handle' import EdgeHandle from './edge-handle'
import RotateHandle from './rotate-handle' import RotateHandle from './rotate-handle'
import Selected from '../selected'
export default function Bounds() { export default function Bounds() {
const isBrushing = useSelector((s) => s.isIn('brushSelecting')) const isBrushing = useSelector((s) => s.isIn('brushSelecting'))
@ -32,7 +31,6 @@ export default function Bounds() {
${(bounds.minY + bounds.maxY) / 2}) ${(bounds.minY + bounds.maxY) / 2})
translate(${bounds.minX},${bounds.minY})`} translate(${bounds.minX},${bounds.minY})`}
> >
<Selected bounds={bounds} />
<CenterHandle bounds={bounds} /> <CenterHandle bounds={bounds} />
<EdgeHandle size={size} bounds={bounds} edge={Edge.Top} /> <EdgeHandle size={size} bounds={bounds} edge={Edge.Top} />
<EdgeHandle size={size} bounds={bounds} edge={Edge.Right} /> <EdgeHandle size={size} bounds={bounds} edge={Edge.Right} />

View file

@ -1,5 +1,5 @@
import styled from 'styles' import styled from 'styles'
import state from 'state' import state, { useSelector } from 'state'
import inputs from 'state/inputs' import inputs from 'state/inputs'
import React, { useCallback, useRef } from 'react' import React, { useCallback, useRef } from 'react'
import useZoomEvents from 'hooks/useZoomEvents' import useZoomEvents from 'hooks/useZoomEvents'
@ -9,6 +9,7 @@ import Page from './page'
import Brush from './brush' import Brush from './brush'
import Bounds from './bounds/bounding-box' import Bounds from './bounds/bounding-box'
import BoundsBg from './bounds/bounds-bg' import BoundsBg from './bounds/bounds-bg'
import Selected from './selected'
export default function Canvas() { export default function Canvas() {
const rCanvas = useRef<SVGSVGElement>(null) const rCanvas = useRef<SVGSVGElement>(null)
@ -17,6 +18,8 @@ export default function Canvas() {
useCamera(rGroup) useCamera(rGroup)
const isReady = useSelector((s) => s.isIn('ready'))
const handlePointerDown = useCallback((e: React.PointerEvent) => { const handlePointerDown = useCallback((e: React.PointerEvent) => {
rCanvas.current.setPointerCapture(e.pointerId) rCanvas.current.setPointerCapture(e.pointerId)
state.send('POINTED_CANVAS', inputs.pointerDown(e, 'canvas')) state.send('POINTED_CANVAS', inputs.pointerDown(e, 'canvas'))
@ -40,12 +43,15 @@ export default function Canvas() {
onPointerUp={handlePointerUp} onPointerUp={handlePointerUp}
> >
<Defs /> <Defs />
<MainGroup ref={rGroup}> {isReady && (
<g ref={rGroup}>
<BoundsBg /> <BoundsBg />
<Page /> <Page />
<Bounds /> <Bounds />
<Selected />
<Brush /> <Brush />
</MainGroup> </g>
)}
</MainSVG> </MainSVG>
) )
} }
@ -63,5 +69,3 @@ const MainSVG = styled('svg', {
userSelect: 'none', userSelect: 'none',
}, },
}) })
const MainGroup = styled('g', {})

View file

@ -1,6 +1,6 @@
import { getShapeUtils } from "lib/shape-utils" import { getShapeUtils } from 'lib/shape-utils'
import { useSelector } from "state" import { useSelector } from 'state'
import { deepCompareArrays, getPage } from "utils/utils" import { deepCompareArrays, getPage } from 'utils/utils'
export default function Defs() { export default function Defs() {
const currentPageShapeIds = useSelector(({ data }) => { const currentPageShapeIds = useSelector(({ data }) => {
@ -20,6 +20,6 @@ export default function Defs() {
export function Def({ id }: { id: string }) { export function Def({ id }: { id: string }) {
const shape = useSelector(({ data }) => getPage(data).shapes[id]) const shape = useSelector(({ data }) => getPage(data).shapes[id])
if (!shape) return null
return getShapeUtils(shape).render(shape) return getShapeUtils(shape).render(shape)
} }

View file

@ -1,48 +1,48 @@
import styled from 'styles' import styled from 'styles'
import { useSelector } from 'state' import { useSelector } from 'state'
import { import { deepCompareArrays, getPage } from 'utils/utils'
deepCompareArrays,
getBoundsCenter,
getPage,
getSelectedShapes,
} from 'utils/utils'
import * as vec from 'utils/vec'
import { getShapeUtils } from 'lib/shape-utils' import { getShapeUtils } from 'lib/shape-utils'
import { Bounds } from 'types'
import useShapeEvents from 'hooks/useShapeEvents' import useShapeEvents from 'hooks/useShapeEvents'
import { useRef } from 'react' import { useRef } from 'react'
export default function Selected({ bounds }: { bounds: Bounds }) { export default function Selected() {
const currentPageShapeIds = useSelector(({ data }) => { const currentPageShapeIds = useSelector(({ data }) => {
return Array.from(data.selectedIds.values()) return Array.from(data.selectedIds.values())
}, deepCompareArrays) }, deepCompareArrays)
const isSelecting = useSelector((s) => s.isIn('selecting'))
if (!isSelecting) return null
return ( return (
<g> <g>
{currentPageShapeIds.map((id) => ( {currentPageShapeIds.map((id) => (
<ShapeOutline key={id} id={id} bounds={bounds} /> <ShapeOutline key={id} id={id} />
))} ))}
</g> </g>
) )
} }
export function ShapeOutline({ id, bounds }: { id: string; bounds: Bounds }) { export function ShapeOutline({ id }: { id: string }) {
const rIndicator = useRef<SVGUseElement>(null) const rIndicator = useRef<SVGUseElement>(null)
const shape = useSelector(({ data }) => getPage(data).shapes[id]) const shape = useSelector(({ data }) => getPage(data).shapes[id])
const shapeBounds = getShapeUtils(shape).getBounds(shape)
const events = useShapeEvents(id, rIndicator) const events = useShapeEvents(id, rIndicator)
if (!shape) return null
const transform = `
rotate(${shape.rotation * (180 / Math.PI)},
${getShapeUtils(shape).getCenter(shape)})
translate(${shape.point})`
return ( return (
<Indicator <Indicator
ref={rIndicator} ref={rIndicator}
as="use" as="use"
href={'#' + id} href={'#' + id}
transform={`rotate(${shape.rotation * (180 / Math.PI)},${getBoundsCenter( transform={transform}
shapeBounds
)}) translate(${vec.sub(shape.point, [bounds.minX, bounds.minY])})`}
{...events} {...events}
/> />
) )

View file

@ -1,10 +1,10 @@
import React, { useCallback, useRef, memo } from 'react' import React, { useRef, memo } from 'react'
import state, { useSelector } from 'state' import { useSelector } from 'state'
import inputs from 'state/inputs'
import styled from 'styles' import styled from 'styles'
import { getShapeUtils } from 'lib/shape-utils' import { getShapeUtils } from 'lib/shape-utils'
import { getPage } from 'utils/utils' import { getPage } from 'utils/utils'
import { ShapeStyles } from 'types' import { ShapeStyles } from 'types'
import useShapeEvents from 'hooks/useShapeEvents'
function Shape({ id, isSelecting }: { id: string; isSelecting: boolean }) { function Shape({ id, isSelecting }: { id: string; isSelecting: boolean }) {
const isHovered = useSelector((state) => state.data.hoveredId === id) const isHovered = useSelector((state) => state.data.hoveredId === id)
@ -15,42 +15,7 @@ function Shape({ id, isSelecting }: { id: string; isSelecting: boolean }) {
const rGroup = useRef<SVGGElement>(null) const rGroup = useRef<SVGGElement>(null)
const handlePointerDown = useCallback( const events = useShapeEvents(id, rGroup)
(e: React.PointerEvent) => {
e.stopPropagation()
rGroup.current.setPointerCapture(e.pointerId)
state.send('POINTED_SHAPE', inputs.pointerDown(e, id))
},
[id]
)
const handlePointerUp = useCallback(
(e: React.PointerEvent) => {
e.stopPropagation()
rGroup.current.releasePointerCapture(e.pointerId)
state.send('STOPPED_POINTING', inputs.pointerUp(e))
},
[id]
)
const handlePointerEnter = useCallback(
(e: React.PointerEvent) => {
state.send('HOVERED_SHAPE', inputs.pointerEnter(e, id))
},
[id, shape]
)
const handlePointerMove = useCallback(
(e: React.PointerEvent) => {
state.send('MOVED_OVER_SHAPE', inputs.pointerEnter(e, id))
},
[id, shape]
)
const handlePointerLeave = useCallback(
() => state.send('UNHOVERED_SHAPE', { target: id }),
[id]
)
// This is a problem with deleted shapes. The hooks in this component // This is a problem with deleted shapes. The hooks in this component
// may sometimes run before the hook in the Page component, which means // may sometimes run before the hook in the Page component, which means
@ -58,19 +23,18 @@ function Shape({ id, isSelecting }: { id: string; isSelecting: boolean }) {
// detects the change and pulls this component. // detects the change and pulls this component.
if (!shape) return null if (!shape) return null
const transform = `
rotate(${shape.rotation * (180 / Math.PI)},
${getShapeUtils(shape).getCenter(shape)})
translate(${shape.point})`
return ( return (
<StyledGroup <StyledGroup
ref={rGroup} ref={rGroup}
isHovered={isHovered} isHovered={isHovered}
isSelected={isSelected} isSelected={isSelected}
transform={`rotate(${shape.rotation * (180 / Math.PI)},${getShapeUtils( transform={transform}
shape {...events}
).getCenter(shape)}) translate(${shape.point})`}
onPointerDown={handlePointerDown}
onPointerUp={handlePointerUp}
onPointerEnter={handlePointerEnter}
onPointerLeave={handlePointerLeave}
onPointerMove={handlePointerMove}
> >
{isSelecting && <HoverIndicator as="use" href={'#' + id} />} {isSelecting && <HoverIndicator as="use" href={'#' + id} />}
<StyledShape id={id} style={shape.style} /> <StyledShape id={id} style={shape.style} />

View file

@ -1,8 +1,8 @@
import React, { useEffect, useRef } from "react" import React, { useEffect, useRef } from 'react'
import state from "state" import state from 'state'
import inputs from "state/inputs" import inputs from 'state/inputs'
import * as vec from "utils/vec" import * as vec from 'utils/vec'
import { usePinch } from "react-use-gesture" import { usePinch } from 'react-use-gesture'
/** /**
* Capture zoom gestures (pinches, wheels and pans) and send to the state. * Capture zoom gestures (pinches, wheels and pans) and send to the state.
@ -21,16 +21,17 @@ export default function useZoomEvents(
function handleWheel(e: WheelEvent) { function handleWheel(e: WheelEvent) {
e.preventDefault() e.preventDefault()
e.stopPropagation()
if (e.ctrlKey) { if (e.ctrlKey) {
state.send("ZOOMED_CAMERA", { state.send('ZOOMED_CAMERA', {
delta: e.deltaY, delta: e.deltaY,
...inputs.wheel(e), ...inputs.wheel(e),
}) })
return return
} }
state.send("PANNED_CAMERA", { state.send('PANNED_CAMERA', {
delta: [e.deltaX, e.deltaY], delta: [e.deltaX, e.deltaY],
...inputs.wheel(e), ...inputs.wheel(e),
}) })
@ -38,6 +39,7 @@ export default function useZoomEvents(
function handleTouchMove(e: TouchEvent) { function handleTouchMove(e: TouchEvent) {
e.preventDefault() e.preventDefault()
e.stopPropagation()
if (e.touches.length === 2) { if (e.touches.length === 2) {
const { clientX: x0, clientY: y0 } = e.touches[0] const { clientX: x0, clientY: y0 } = e.touches[0]
@ -46,7 +48,7 @@ export default function useZoomEvents(
const dist = vec.dist([x0, y0], [x1, y1]) const dist = vec.dist([x0, y0], [x1, y1])
const point = vec.med([x0, y0], [x1, y1]) const point = vec.med([x0, y0], [x1, y1])
state.send("WHEELED", { state.send('WHEELED', {
delta: dist - rTouchDist.current, delta: dist - rTouchDist.current,
point, point,
}) })
@ -55,38 +57,37 @@ export default function useZoomEvents(
} }
} }
element.addEventListener("wheel", handleWheel) element.addEventListener('wheel', handleWheel, { passive: false })
element.addEventListener("touchstart", handleTouchMove) element.addEventListener('touchstart', handleTouchMove, { passive: false })
element.addEventListener("touchmove", handleTouchMove) element.addEventListener('touchmove', handleTouchMove, { passive: false })
return () => { return () => {
element.removeEventListener("wheel", handleWheel) element.removeEventListener('wheel', handleWheel)
element.removeEventListener("touchstart", handleTouchMove) element.removeEventListener('touchstart', handleTouchMove)
element.removeEventListener("touchmove", handleTouchMove) element.removeEventListener('touchmove', handleTouchMove)
} }
}, [ref]) }, [ref])
const rPinchDa = useRef<number[] | undefined>(undefined) const rPinchDa = useRef<number[] | undefined>(undefined)
const rPinchAngle = useRef<number>(undefined)
const rPinchPoint = useRef<number[] | undefined>(undefined) const rPinchPoint = useRef<number[] | undefined>(undefined)
const bind = usePinch(({ pinching, da, origin }) => { const bind = usePinch(({ pinching, da, origin }) => {
if (!pinching) { if (!pinching) {
state.send("STOPPED_PINCHING") state.send('STOPPED_PINCHING')
rPinchDa.current = undefined rPinchDa.current = undefined
rPinchPoint.current = undefined rPinchPoint.current = undefined
return return
} }
if (rPinchPoint.current === undefined) { if (rPinchPoint.current === undefined) {
state.send("STARTED_PINCHING") state.send('STARTED_PINCHING')
rPinchDa.current = da rPinchDa.current = da
rPinchPoint.current = origin rPinchPoint.current = origin
} }
const [distanceDelta, angleDelta] = vec.sub(rPinchDa.current, da) const [distanceDelta, angleDelta] = vec.sub(rPinchDa.current, da)
state.send("PINCHED", { state.send('PINCHED', {
delta: vec.sub(rPinchPoint.current, origin), delta: vec.sub(rPinchPoint.current, origin),
point: origin, point: origin,
distanceDelta, distanceDelta,

View file

@ -7,11 +7,10 @@ import { boundsContainPolygon } from 'utils/bounds'
import getStroke from 'perfect-freehand' import getStroke from 'perfect-freehand'
import { import {
getBoundsFromPoints, getBoundsFromPoints,
getRotatedCorners,
getSvgPathFromStroke, getSvgPathFromStroke,
translateBounds, translateBounds,
} from 'utils/utils' } from 'utils/utils'
import { DotCircle } from 'components/canvas/misc'
import { shades } from 'lib/colors'
const pathCache = new WeakMap<number[][], string>([]) const pathCache = new WeakMap<number[][], string>([])
@ -43,13 +42,18 @@ const draw = registerShapeUtils<DrawShape>({
render(shape) { render(shape) {
const { id, point, points } = shape const { id, point, points } = shape
if (points.length < 2) {
return <DotCircle id={id} cx={point[0]} cy={point[1]} r={3} />
}
if (!pathCache.has(points)) { if (!pathCache.has(points)) {
if (points.length < 2) {
const left = vec.add(point, [6, 0])
let d: number[][] = []
for (let i = 0; i < 10; i++) {
d.push(vec.rotWith(left, point, i * ((Math.PI * 2) / 8)))
}
pathCache.set(points, getSvgPathFromStroke(d))
} else {
pathCache.set(points, getSvgPathFromStroke(getStroke(points))) pathCache.set(points, getSvgPathFromStroke(getStroke(points)))
} }
}
return <path id={id} d={pathCache.get(points)} /> return <path id={id} d={pathCache.get(points)} />
}, },
@ -69,11 +73,13 @@ const draw = registerShapeUtils<DrawShape>({
}, },
getRotatedBounds(shape) { getRotatedBounds(shape) {
return this.getBounds(shape) return getBoundsFromPoints(
getRotatedCorners(this.getBounds(shape), shape.rotation)
)
}, },
getCenter(shape) { getCenter(shape) {
const bounds = this.getBounds(shape) const bounds = this.getRotatedBounds(shape)
return [bounds.minX + bounds.width / 2, bounds.minY + bounds.height / 2] return [bounds.minX + bounds.width / 2, bounds.minY + bounds.height / 2]
}, },
@ -114,6 +120,10 @@ const draw = registerShapeUtils<DrawShape>({
rotateTo(shape, rotation) { rotateTo(shape, rotation) {
shape.rotation = rotation shape.rotation = rotation
// console.log(shape.points.map(([x, y]) => [x, y]))
// const bounds = this.getBounds(shape)
// const center = [bounds.width / 2, bounds.height / 2]
// shape.points = shape.points.map((pt) => vec.rotWith(pt, center, rotation))
return this return this
}, },

View file

@ -16,7 +16,7 @@
"framer-motion": "^4.1.16", "framer-motion": "^4.1.16",
"ismobilejs": "^1.1.1", "ismobilejs": "^1.1.1",
"next": "10.2.0", "next": "10.2.0",
"perfect-freehand": "^0.4.71", "perfect-freehand": "^0.4.8",
"prettier": "^2.3.0", "prettier": "^2.3.0",
"react": "17.0.2", "react": "17.0.2",
"react-dom": "17.0.2", "react-dom": "17.0.2",

View file

@ -1,8 +1,8 @@
import { Data } from "types" import { Data } from 'types'
import * as vec from "utils/vec" import * as vec from 'utils/vec'
import BaseSession from "./base-session" import BaseSession from './base-session'
import commands from "state/commands" import commands from 'state/commands'
import { current } from "immer" import { current } from 'immer'
import { import {
clampToRotationToSegments, clampToRotationToSegments,
getBoundsCenter, getBoundsCenter,
@ -10,8 +10,8 @@ import {
getPage, getPage,
getSelectedShapes, getSelectedShapes,
getShapeBounds, getShapeBounds,
} from "utils/utils" } from 'utils/utils'
import { getShapeUtils } from "lib/shape-utils" import { getShapeUtils } from 'lib/shape-utils'
const PI2 = Math.PI * 2 const PI2 = Math.PI * 2

View file

@ -1,13 +1,13 @@
import { createSelectorHook, createState } from "@state-designer/react" import { createSelectorHook, createState } from '@state-designer/react'
import * as vec from "utils/vec" import * as vec from 'utils/vec'
import inputs from "./inputs" import inputs from './inputs'
import { defaultDocument } from "./data" import { defaultDocument } from './data'
import { shades } from "lib/colors" import { shades } from 'lib/colors'
import { createShape, getShapeUtils } from "lib/shape-utils" import { createShape, getShapeUtils } from 'lib/shape-utils'
import history from "state/history" import history from 'state/history'
import * as Sessions from "./sessions" import * as Sessions from './sessions'
import commands from "./commands" import commands from './commands'
import { updateFromCode } from "lib/code/generate" import { updateFromCode } from 'lib/code/generate'
import { import {
clamp, clamp,
getChildren, getChildren,
@ -18,7 +18,7 @@ import {
getShape, getShape,
screenToWorld, screenToWorld,
setZoomCSS, setZoomCSS,
} from "utils/utils" } from 'utils/utils'
import { import {
Data, Data,
PointerInfo, PointerInfo,
@ -32,7 +32,7 @@ import {
DistributeType, DistributeType,
AlignType, AlignType,
StretchType, StretchType,
} from "types" } from 'types'
const initialData: Data = { const initialData: Data = {
isReadOnly: false, isReadOnly: false,
@ -55,8 +55,8 @@ const initialData: Data = {
pointedId: null, pointedId: null,
hoveredId: null, hoveredId: null,
selectedIds: new Set([]), selectedIds: new Set([]),
currentPageId: "page0", currentPageId: 'page0',
currentCodeFileId: "file0", currentCodeFileId: 'file0',
codeControls: {}, codeControls: {},
document: defaultDocument, document: defaultDocument,
} }
@ -65,109 +65,116 @@ const state = createState({
data: initialData, data: initialData,
on: { on: {
ZOOMED_CAMERA: { ZOOMED_CAMERA: {
do: "zoomCamera", do: 'zoomCamera',
}, },
PANNED_CAMERA: { PANNED_CAMERA: {
do: "panCamera", do: 'panCamera',
}, },
SELECTED_SELECT_TOOL: { to: "selecting" }, SELECTED_SELECT_TOOL: { to: 'selecting' },
SELECTED_DRAW_TOOL: { unless: "isReadOnly", to: "draw" }, SELECTED_DRAW_TOOL: { unless: 'isReadOnly', to: 'draw' },
SELECTED_DOT_TOOL: { unless: "isReadOnly", to: "dot" }, SELECTED_DOT_TOOL: { unless: 'isReadOnly', to: 'dot' },
SELECTED_CIRCLE_TOOL: { unless: "isReadOnly", to: "circle" }, SELECTED_CIRCLE_TOOL: { unless: 'isReadOnly', to: 'circle' },
SELECTED_ELLIPSE_TOOL: { unless: "isReadOnly", to: "ellipse" }, SELECTED_ELLIPSE_TOOL: { unless: 'isReadOnly', to: 'ellipse' },
SELECTED_RAY_TOOL: { unless: "isReadOnly", to: "ray" }, SELECTED_RAY_TOOL: { unless: 'isReadOnly', to: 'ray' },
SELECTED_LINE_TOOL: { unless: "isReadOnly", to: "line" }, SELECTED_LINE_TOOL: { unless: 'isReadOnly', to: 'line' },
SELECTED_POLYLINE_TOOL: { unless: "isReadOnly", to: "polyline" }, SELECTED_POLYLINE_TOOL: { unless: 'isReadOnly', to: 'polyline' },
SELECTED_RECTANGLE_TOOL: { unless: "isReadOnly", to: "rectangle" }, SELECTED_RECTANGLE_TOOL: { unless: 'isReadOnly', to: 'rectangle' },
TOGGLED_CODE_PANEL_OPEN: "toggleCodePanel", TOGGLED_CODE_PANEL_OPEN: 'toggleCodePanel',
TOGGLED_STYLE_PANEL_OPEN: "toggleStylePanel", TOGGLED_STYLE_PANEL_OPEN: 'toggleStylePanel',
CHANGED_STYLE: ["updateStyles", "applyStylesToSelection"], CHANGED_STYLE: ['updateStyles', 'applyStylesToSelection'],
RESET_CAMERA: "resetCamera", RESET_CAMERA: 'resetCamera',
ZOOMED_TO_FIT: "zoomCameraToFit", ZOOMED_TO_FIT: 'zoomCameraToFit',
ZOOMED_TO_SELECTION: { ZOOMED_TO_SELECTION: {
if: "hasSelection", if: 'hasSelection',
do: "zoomCameraToSelection", do: 'zoomCameraToSelection',
}, },
ZOOMED_TO_ACTUAL: { ZOOMED_TO_ACTUAL: {
if: "hasSelection", if: 'hasSelection',
do: "zoomCameraToSelectionActual", do: 'zoomCameraToSelectionActual',
else: "zoomCameraToActual", else: 'zoomCameraToActual',
}, },
SELECTED_ALL: { to: "selecting", do: "selectAll" }, SELECTED_ALL: { to: 'selecting', do: 'selectAll' },
}, },
initial: "loading", initial: 'loading',
states: { states: {
loading: { loading: {
on: { on: {
MOUNTED: { MOUNTED: [
do: ["restoreSavedData", "zoomCameraToFit"], 'restoreSavedData',
to: "ready", {
if: 'hasSelection',
do: 'zoomCameraToSelectionActual',
else: ['zoomCameraToFit', 'zoomCameraToActual'],
}, },
{
to: 'ready',
},
],
}, },
}, },
ready: { ready: {
on: { on: {
UNMOUNTED: [ UNMOUNTED: [
{ unless: "isReadOnly", do: "forceSave" }, { unless: 'isReadOnly', do: 'forceSave' },
{ to: "loading" }, { to: 'loading' },
], ],
}, },
initial: "selecting", initial: 'selecting',
states: { states: {
selecting: { selecting: {
on: { on: {
SAVED: "forceSave", SAVED: 'forceSave',
UNDO: { do: "undo" }, UNDO: { do: 'undo' },
REDO: { do: "redo" }, REDO: { do: 'redo' },
CANCELLED: { do: "clearSelectedIds" }, CANCELLED: { do: 'clearSelectedIds' },
DELETED: { do: "deleteSelectedIds" }, DELETED: { do: 'deleteSelectedIds' },
SAVED_CODE: "saveCode", SAVED_CODE: 'saveCode',
GENERATED_FROM_CODE: ["setCodeControls", "setGeneratedShapes"], GENERATED_FROM_CODE: ['setCodeControls', 'setGeneratedShapes'],
INCREASED_CODE_FONT_SIZE: "increaseCodeFontSize", INCREASED_CODE_FONT_SIZE: 'increaseCodeFontSize',
DECREASED_CODE_FONT_SIZE: "decreaseCodeFontSize", DECREASED_CODE_FONT_SIZE: 'decreaseCodeFontSize',
CHANGED_CODE_CONTROL: "updateControls", CHANGED_CODE_CONTROL: 'updateControls',
ALIGNED: "alignSelection", ALIGNED: 'alignSelection',
STRETCHED: "stretchSelection", STRETCHED: 'stretchSelection',
DISTRIBUTED: "distributeSelection", DISTRIBUTED: 'distributeSelection',
MOVED: "moveSelection", MOVED: 'moveSelection',
STARTED_PINCHING: { to: "pinching" }, STARTED_PINCHING: { to: 'pinching' },
}, },
initial: "notPointing", initial: 'notPointing',
states: { states: {
notPointing: { notPointing: {
on: { on: {
POINTED_CANVAS: { to: "brushSelecting" }, POINTED_CANVAS: { to: 'brushSelecting' },
POINTED_BOUNDS: { to: "pointingBounds" }, POINTED_BOUNDS: { to: 'pointingBounds' },
POINTED_BOUNDS_HANDLE: { POINTED_BOUNDS_HANDLE: {
if: "isPointingRotationHandle", if: 'isPointingRotationHandle',
to: "rotatingSelection", to: 'rotatingSelection',
else: { to: "transformingSelection" }, else: { to: 'transformingSelection' },
}, },
MOVED_OVER_SHAPE: { MOVED_OVER_SHAPE: {
if: "pointHitsShape", if: 'pointHitsShape',
then: { then: {
unless: "shapeIsHovered", unless: 'shapeIsHovered',
do: "setHoveredId", do: 'setHoveredId',
}, },
else: { if: "shapeIsHovered", do: "clearHoveredId" }, else: { if: 'shapeIsHovered', do: 'clearHoveredId' },
}, },
UNHOVERED_SHAPE: "clearHoveredId", UNHOVERED_SHAPE: 'clearHoveredId',
POINTED_SHAPE: [ POINTED_SHAPE: [
{ {
if: "isPressingMetaKey", if: 'isPressingMetaKey',
to: "brushSelecting", to: 'brushSelecting',
}, },
"setPointedId", 'setPointedId',
{ {
unless: "isPointedShapeSelected", unless: 'isPointedShapeSelected',
then: { then: {
if: "isPressingShiftKey", if: 'isPressingShiftKey',
do: ["pushPointedIdToSelectedIds", "clearPointedId"], do: ['pushPointedIdToSelectedIds', 'clearPointedId'],
else: ["clearSelectedIds", "pushPointedIdToSelectedIds"], else: ['clearSelectedIds', 'pushPointedIdToSelectedIds'],
}, },
}, },
{ {
to: "pointingBounds", to: 'pointingBounds',
}, },
], ],
}, },
@ -176,151 +183,153 @@ const state = createState({
on: { on: {
STOPPED_POINTING: [ STOPPED_POINTING: [
{ {
if: "isPressingShiftKey", if: 'isPressingShiftKey',
then: { then: {
if: "isPointedShapeSelected", if: 'isPointedShapeSelected',
do: "pullPointedIdFromSelectedIds", do: 'pullPointedIdFromSelectedIds',
}, },
else: { else: {
unless: "isPointingBounds", unless: 'isPointingBounds',
do: ["clearSelectedIds", "pushPointedIdToSelectedIds"], do: ['clearSelectedIds', 'pushPointedIdToSelectedIds'],
}, },
}, },
{ to: "notPointing" }, { to: 'notPointing' },
], ],
MOVED_POINTER: { MOVED_POINTER: {
unless: "isReadOnly", unless: 'isReadOnly',
if: "distanceImpliesDrag", if: 'distanceImpliesDrag',
to: "draggingSelection", to: 'draggingSelection',
}, },
}, },
}, },
rotatingSelection: { rotatingSelection: {
onEnter: "startRotateSession", onEnter: 'startRotateSession',
onExit: "clearBoundsRotation", onExit: 'clearBoundsRotation',
on: { on: {
MOVED_POINTER: "updateRotateSession", MOVED_POINTER: 'updateRotateSession',
PANNED_CAMERA: "updateRotateSession", PANNED_CAMERA: 'updateRotateSession',
PRESSED_SHIFT_KEY: "keyUpdateRotateSession", PRESSED_SHIFT_KEY: 'keyUpdateRotateSession',
RELEASED_SHIFT_KEY: "keyUpdateRotateSession", RELEASED_SHIFT_KEY: 'keyUpdateRotateSession',
STOPPED_POINTING: { do: "completeSession", to: "selecting" }, STOPPED_POINTING: { do: 'completeSession', to: 'selecting' },
CANCELLED: { do: "cancelSession", to: "selecting" }, CANCELLED: { do: 'cancelSession', to: 'selecting' },
}, },
}, },
transformingSelection: { transformingSelection: {
onEnter: "startTransformSession", onEnter: 'startTransformSession',
on: { on: {
MOVED_POINTER: "updateTransformSession", MOVED_POINTER: 'updateTransformSession',
PANNED_CAMERA: "updateTransformSession", PANNED_CAMERA: 'updateTransformSession',
PRESSED_SHIFT_KEY: "keyUpdateTransformSession", PRESSED_SHIFT_KEY: 'keyUpdateTransformSession',
RELEASED_SHIFT_KEY: "keyUpdateTransformSession", RELEASED_SHIFT_KEY: 'keyUpdateTransformSession',
STOPPED_POINTING: { do: "completeSession", to: "selecting" }, STOPPED_POINTING: { do: 'completeSession', to: 'selecting' },
CANCELLED: { do: "cancelSession", to: "selecting" }, CANCELLED: { do: 'cancelSession', to: 'selecting' },
}, },
}, },
draggingSelection: { draggingSelection: {
onEnter: "startTranslateSession", onEnter: 'startTranslateSession',
on: { on: {
MOVED_POINTER: "updateTranslateSession", MOVED_POINTER: 'updateTranslateSession',
PANNED_CAMERA: "updateTranslateSession", PANNED_CAMERA: 'updateTranslateSession',
PRESSED_SHIFT_KEY: "keyUpdateTranslateSession", PRESSED_SHIFT_KEY: 'keyUpdateTranslateSession',
RELEASED_SHIFT_KEY: "keyUpdateTranslateSession", RELEASED_SHIFT_KEY: 'keyUpdateTranslateSession',
PRESSED_ALT_KEY: "keyUpdateTranslateSession", PRESSED_ALT_KEY: 'keyUpdateTranslateSession',
RELEASED_ALT_KEY: "keyUpdateTranslateSession", RELEASED_ALT_KEY: 'keyUpdateTranslateSession',
STOPPED_POINTING: { do: "completeSession", to: "selecting" }, STOPPED_POINTING: { do: 'completeSession', to: 'selecting' },
CANCELLED: { do: "cancelSession", to: "selecting" }, CANCELLED: { do: 'cancelSession', to: 'selecting' },
}, },
}, },
brushSelecting: { brushSelecting: {
onEnter: [ onEnter: [
{ {
unless: ["isPressingMetaKey", "isPressingShiftKey"], unless: ['isPressingMetaKey', 'isPressingShiftKey'],
do: "clearSelectedIds", do: 'clearSelectedIds',
}, },
"clearBoundsRotation", 'clearBoundsRotation',
"startBrushSession", 'startBrushSession',
], ],
on: { on: {
MOVED_POINTER: "updateBrushSession", MOVED_POINTER: 'updateBrushSession',
PANNED_CAMERA: "updateBrushSession", PANNED_CAMERA: 'updateBrushSession',
STOPPED_POINTING: { do: "completeSession", to: "selecting" }, STOPPED_POINTING: { do: 'completeSession', to: 'selecting' },
CANCELLED: { do: "cancelSession", to: "selecting" }, CANCELLED: { do: 'cancelSession', to: 'selecting' },
}, },
}, },
}, },
}, },
pinching: { pinching: {
on: { on: {
STOPPED_PINCHING: { to: "selecting" }, STOPPED_PINCHING: { to: 'selecting' },
PINCHED: { do: "pinchCamera" }, PINCHED: { do: 'pinchCamera' },
}, },
}, },
draw: { draw: {
initial: "creating", initial: 'creating',
states: { states: {
creating: { creating: {
on: { on: {
CANCELLED: { to: 'selecting' },
POINTED_CANVAS: { POINTED_CANVAS: {
get: "newDraw", get: 'newDraw',
do: "createShape", do: 'createShape',
to: "draw.editing", to: 'draw.editing',
}, },
UNDO: { do: "undo" }, UNDO: { do: 'undo' },
REDO: { do: "redo" }, REDO: { do: 'redo' },
}, },
}, },
editing: { editing: {
onEnter: "startDrawSession", onEnter: 'startDrawSession',
on: { on: {
STOPPED_POINTING: { STOPPED_POINTING: {
do: "completeSession", do: 'completeSession',
to: "draw.creating", to: 'draw.creating',
}, },
CANCELLED: { CANCELLED: {
do: ["cancelSession", "deleteSelectedIds"], do: ['cancelSession', 'deleteSelectedIds'],
to: "selecting", to: 'selecting',
}, },
MOVED_POINTER: "updateDrawSession", MOVED_POINTER: 'updateDrawSession',
PANNED_CAMERA: "updateDrawSession", PANNED_CAMERA: 'updateDrawSession',
}, },
}, },
}, },
}, },
dot: { dot: {
initial: "creating", initial: 'creating',
states: { states: {
creating: { creating: {
on: { on: {
CANCELLED: { to: 'selecting' },
POINTED_CANVAS: { POINTED_CANVAS: {
get: "newDot", get: 'newDot',
do: "createShape", do: 'createShape',
to: "dot.editing", to: 'dot.editing',
}, },
}, },
}, },
editing: { editing: {
on: { on: {
STOPPED_POINTING: { do: "completeSession", to: "selecting" }, STOPPED_POINTING: { do: 'completeSession', to: 'selecting' },
CANCELLED: { CANCELLED: {
do: ["cancelSession", "deleteSelectedIds"], do: ['cancelSession', 'deleteSelectedIds'],
to: "selecting", to: 'selecting',
}, },
}, },
initial: "inactive", initial: 'inactive',
states: { states: {
inactive: { inactive: {
on: { on: {
MOVED_POINTER: { MOVED_POINTER: {
if: "distanceImpliesDrag", if: 'distanceImpliesDrag',
to: "dot.editing.active", to: 'dot.editing.active',
}, },
}, },
}, },
active: { active: {
onEnter: "startTranslateSession", onEnter: 'startTranslateSession',
on: { on: {
MOVED_POINTER: "updateTranslateSession", MOVED_POINTER: 'updateTranslateSession',
PANNED_CAMERA: "updateTranslateSession", PANNED_CAMERA: 'updateTranslateSession',
}, },
}, },
}, },
@ -328,25 +337,26 @@ const state = createState({
}, },
}, },
circle: { circle: {
initial: "creating", initial: 'creating',
states: { states: {
creating: { creating: {
on: { on: {
CANCELLED: { to: 'selecting' },
POINTED_CANVAS: { POINTED_CANVAS: {
to: "circle.editing", to: 'circle.editing',
}, },
}, },
}, },
editing: { editing: {
on: { on: {
STOPPED_POINTING: { to: "selecting" }, STOPPED_POINTING: { to: 'selecting' },
CANCELLED: { to: "selecting" }, CANCELLED: { to: 'selecting' },
MOVED_POINTER: { MOVED_POINTER: {
if: "distanceImpliesDrag", if: 'distanceImpliesDrag',
then: { then: {
get: "newCircle", get: 'newCircle',
do: "createShape", do: 'createShape',
to: "drawingShape.bounds", to: 'drawingShape.bounds',
}, },
}, },
}, },
@ -354,26 +364,26 @@ const state = createState({
}, },
}, },
ellipse: { ellipse: {
initial: "creating", initial: 'creating',
states: { states: {
creating: { creating: {
on: { on: {
CANCELLED: { to: "selecting" }, CANCELLED: { to: 'selecting' },
POINTED_CANVAS: { POINTED_CANVAS: {
to: "ellipse.editing", to: 'ellipse.editing',
}, },
}, },
}, },
editing: { editing: {
on: { on: {
STOPPED_POINTING: { to: "selecting" }, STOPPED_POINTING: { to: 'selecting' },
CANCELLED: { to: "selecting" }, CANCELLED: { to: 'selecting' },
MOVED_POINTER: { MOVED_POINTER: {
if: "distanceImpliesDrag", if: 'distanceImpliesDrag',
then: { then: {
get: "newEllipse", get: 'newEllipse',
do: "createShape", do: 'createShape',
to: "drawingShape.bounds", to: 'drawingShape.bounds',
}, },
}, },
}, },
@ -381,26 +391,26 @@ const state = createState({
}, },
}, },
rectangle: { rectangle: {
initial: "creating", initial: 'creating',
states: { states: {
creating: { creating: {
on: { on: {
CANCELLED: { to: "selecting" }, CANCELLED: { to: 'selecting' },
POINTED_CANVAS: { POINTED_CANVAS: {
to: "rectangle.editing", to: 'rectangle.editing',
}, },
}, },
}, },
editing: { editing: {
on: { on: {
STOPPED_POINTING: { to: "selecting" }, STOPPED_POINTING: { to: 'selecting' },
CANCELLED: { to: "selecting" }, CANCELLED: { to: 'selecting' },
MOVED_POINTER: { MOVED_POINTER: {
if: "distanceImpliesDrag", if: 'distanceImpliesDrag',
then: { then: {
get: "newRectangle", get: 'newRectangle',
do: "createShape", do: 'createShape',
to: "drawingShape.bounds", to: 'drawingShape.bounds',
}, },
}, },
}, },
@ -408,50 +418,50 @@ const state = createState({
}, },
}, },
ray: { ray: {
initial: "creating", initial: 'creating',
states: { states: {
creating: { creating: {
on: { on: {
CANCELLED: { to: "selecting" }, CANCELLED: { to: 'selecting' },
POINTED_CANVAS: { POINTED_CANVAS: {
get: "newRay", get: 'newRay',
do: "createShape", do: 'createShape',
to: "ray.editing", to: 'ray.editing',
}, },
}, },
}, },
editing: { editing: {
on: { on: {
STOPPED_POINTING: { to: "selecting" }, STOPPED_POINTING: { to: 'selecting' },
CANCELLED: { to: "selecting" }, CANCELLED: { to: 'selecting' },
MOVED_POINTER: { MOVED_POINTER: {
if: "distanceImpliesDrag", if: 'distanceImpliesDrag',
to: "drawingShape.direction", to: 'drawingShape.direction',
}, },
}, },
}, },
}, },
}, },
line: { line: {
initial: "creating", initial: 'creating',
states: { states: {
creating: { creating: {
on: { on: {
CANCELLED: { to: "selecting" }, CANCELLED: { to: 'selecting' },
POINTED_CANVAS: { POINTED_CANVAS: {
get: "newLine", get: 'newLine',
do: "createShape", do: 'createShape',
to: "line.editing", to: 'line.editing',
}, },
}, },
}, },
editing: { editing: {
on: { on: {
STOPPED_POINTING: { to: "selecting" }, STOPPED_POINTING: { to: 'selecting' },
CANCELLED: { to: "selecting" }, CANCELLED: { to: 'selecting' },
MOVED_POINTER: { MOVED_POINTER: {
if: "distanceImpliesDrag", if: 'distanceImpliesDrag',
to: "drawingShape.direction", to: 'drawingShape.direction',
}, },
}, },
}, },
@ -463,28 +473,28 @@ const state = createState({
drawingShape: { drawingShape: {
on: { on: {
STOPPED_POINTING: { STOPPED_POINTING: {
do: "completeSession", do: 'completeSession',
to: "selecting", to: 'selecting',
}, },
CANCELLED: { CANCELLED: {
do: ["cancelSession", "deleteSelectedIds"], do: ['cancelSession', 'deleteSelectedIds'],
to: "selecting", to: 'selecting',
}, },
}, },
initial: "drawingShapeBounds", initial: 'drawingShapeBounds',
states: { states: {
bounds: { bounds: {
onEnter: "startDrawTransformSession", onEnter: 'startDrawTransformSession',
on: { on: {
MOVED_POINTER: "updateTransformSession", MOVED_POINTER: 'updateTransformSession',
PANNED_CAMERA: "updateTransformSession", PANNED_CAMERA: 'updateTransformSession',
}, },
}, },
direction: { direction: {
onEnter: "startDirectionSession", onEnter: 'startDirectionSession',
on: { on: {
MOVED_POINTER: "updateDirectionSession", MOVED_POINTER: 'updateDirectionSession',
PANNED_CAMERA: "updateDirectionSession", PANNED_CAMERA: 'updateDirectionSession',
}, },
}, },
}, },
@ -515,7 +525,7 @@ const state = createState({
}, },
conditions: { conditions: {
isPointingBounds(data, payload: PointerInfo) { isPointingBounds(data, payload: PointerInfo) {
return payload.target === "bounds" return payload.target === 'bounds'
}, },
isReadOnly(data) { isReadOnly(data) {
return data.isReadOnly return data.isReadOnly
@ -545,9 +555,9 @@ const state = createState({
}, },
isPointingRotationHandle( isPointingRotationHandle(
data, data,
payload: { target: Edge | Corner | "rotate" } payload: { target: Edge | Corner | 'rotate' }
) { ) {
return payload.target === "rotate" return payload.target === 'rotate'
}, },
hasSelection(data) { hasSelection(data) {
return data.selectedIds.size > 0 return data.selectedIds.size > 0
@ -753,7 +763,7 @@ const state = createState({
data.camera.zoom = 1 data.camera.zoom = 1
data.camera.point = [window.innerWidth / 2, window.innerHeight / 2] data.camera.point = [window.innerWidth / 2, window.innerHeight / 2]
document.documentElement.style.setProperty("--camera-zoom", "1") document.documentElement.style.setProperty('--camera-zoom', '1')
}, },
zoomCameraToActual(data) { zoomCameraToActual(data) {
const { camera } = data const { camera } = data
@ -983,7 +993,7 @@ const state = createState({
if (selectedIds.size === 1) { if (selectedIds.size === 1) {
if (!shapes[0]) { if (!shapes[0]) {
console.error("Could not find that shape! Clearing selected IDs.") console.error('Could not find that shape! Clearing selected IDs.')
data.selectedIds.clear() data.selectedIds.clear()
return null return null
} }

View file

@ -6392,10 +6392,10 @@ pend@~1.2.0:
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
perfect-freehand@^0.4.71: perfect-freehand@^0.4.8:
version "0.4.71" version "0.4.8"
resolved "https://registry.yarnpkg.com/perfect-freehand/-/perfect-freehand-0.4.71.tgz#b98ffc3cbc4e3cd930528e8d74a8849ee77475fb" resolved "https://registry.yarnpkg.com/perfect-freehand/-/perfect-freehand-0.4.8.tgz#0054995322fdd9939c0c38c260d96a9d0f22eb4f"
integrity sha512-bJ3w2E6WcUfZJTXWPlS7DI6FIT9rRIYSCXgDYjvST8sAe/c+zNnJnlfJp3q8Hk1uPt9dH7bFuj8Sico6CKZw7A== integrity sha512-zU0hvTh0ctjb/h5+nwFhb+/5ZnqS8Z16mn7lY2tYy3NwTkjrEKZ8CJvQ2phlOV5kk+BnCDFGVz7tkKrl9+Mr9w==
performance-now@^2.1.0: performance-now@^2.1.0:
version "2.1.0" version "2.1.0"