Improves undo/redo, fixes pinching and multitouch
This commit is contained in:
parent
bc6f5cf5b7
commit
76a4ccdfcb
15 changed files with 366 additions and 288 deletions
|
@ -1,27 +1,29 @@
|
||||||
import { useCallback, useRef } from "react"
|
import { useCallback, useRef } from 'react'
|
||||||
import state, { useSelector } from "state"
|
import state, { useSelector } from 'state'
|
||||||
import inputs from "state/inputs"
|
import inputs from 'state/inputs'
|
||||||
import styled from "styles"
|
import styled from 'styles'
|
||||||
import { getPage } from "utils/utils"
|
import { getPage } from 'utils/utils'
|
||||||
|
|
||||||
function handlePointerDown(e: React.PointerEvent<SVGRectElement>) {
|
function handlePointerDown(e: React.PointerEvent<SVGRectElement>) {
|
||||||
if (e.buttons !== 1) return
|
if (e.buttons !== 1) return
|
||||||
|
if (!inputs.canAccept(e.pointerId)) return
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
e.currentTarget.setPointerCapture(e.pointerId)
|
e.currentTarget.setPointerCapture(e.pointerId)
|
||||||
state.send("POINTED_BOUNDS", inputs.pointerDown(e, "bounds"))
|
state.send('POINTED_BOUNDS', inputs.pointerDown(e, 'bounds'))
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePointerUp(e: React.PointerEvent<SVGRectElement>) {
|
function handlePointerUp(e: React.PointerEvent<SVGRectElement>) {
|
||||||
if (e.buttons !== 1) return
|
if (e.buttons !== 1) return
|
||||||
|
if (!inputs.canAccept(e.pointerId)) return
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
e.currentTarget.releasePointerCapture(e.pointerId)
|
e.currentTarget.releasePointerCapture(e.pointerId)
|
||||||
state.send("STOPPED_POINTING", inputs.pointerUp(e))
|
state.send('STOPPED_POINTING', inputs.pointerUp(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BoundsBg() {
|
export default function BoundsBg() {
|
||||||
const rBounds = useRef<SVGRectElement>(null)
|
const rBounds = useRef<SVGRectElement>(null)
|
||||||
const bounds = useSelector((state) => state.values.selectedBounds)
|
const bounds = useSelector((state) => state.values.selectedBounds)
|
||||||
const isSelecting = useSelector((s) => s.isIn("selecting"))
|
const isSelecting = useSelector((s) => s.isIn('selecting'))
|
||||||
const rotation = useSelector((s) => {
|
const rotation = useSelector((s) => {
|
||||||
if (s.data.selectedIds.size === 1) {
|
if (s.data.selectedIds.size === 1) {
|
||||||
const { shapes } = getPage(s.data)
|
const { shapes } = getPage(s.data)
|
||||||
|
@ -53,6 +55,6 @@ export default function BoundsBg() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledBoundsBg = styled("rect", {
|
const StyledBoundsBg = styled('rect', {
|
||||||
fill: "$boundsBg",
|
fill: '$boundsBg',
|
||||||
})
|
})
|
||||||
|
|
|
@ -21,15 +21,26 @@ export default function Canvas() {
|
||||||
const isReady = useSelector((s) => s.isIn('ready'))
|
const isReady = useSelector((s) => s.isIn('ready'))
|
||||||
|
|
||||||
const handlePointerDown = useCallback((e: React.PointerEvent) => {
|
const handlePointerDown = useCallback((e: React.PointerEvent) => {
|
||||||
|
if (!inputs.canAccept(e.pointerId)) return
|
||||||
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'))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const handleTouchStart = useCallback((e: React.TouchEvent) => {
|
||||||
|
if (e.touches.length === 2) {
|
||||||
|
state.send('TOUCH_UNDO')
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
const handlePointerMove = useCallback((e: React.PointerEvent) => {
|
const handlePointerMove = useCallback((e: React.PointerEvent) => {
|
||||||
|
if (!inputs.canAccept(e.pointerId)) return
|
||||||
|
if (inputs.canAccept(e.pointerId)) {
|
||||||
state.send('MOVED_POINTER', inputs.pointerMove(e))
|
state.send('MOVED_POINTER', inputs.pointerMove(e))
|
||||||
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handlePointerUp = useCallback((e: React.PointerEvent) => {
|
const handlePointerUp = useCallback((e: React.PointerEvent) => {
|
||||||
|
if (!inputs.canAccept(e.pointerId)) return
|
||||||
rCanvas.current.releasePointerCapture(e.pointerId)
|
rCanvas.current.releasePointerCapture(e.pointerId)
|
||||||
state.send('STOPPED_POINTING', { id: 'canvas', ...inputs.pointerUp(e) })
|
state.send('STOPPED_POINTING', { id: 'canvas', ...inputs.pointerUp(e) })
|
||||||
}, [])
|
}, [])
|
||||||
|
@ -41,14 +52,15 @@ export default function Canvas() {
|
||||||
onPointerDown={handlePointerDown}
|
onPointerDown={handlePointerDown}
|
||||||
onPointerMove={handlePointerMove}
|
onPointerMove={handlePointerMove}
|
||||||
onPointerUp={handlePointerUp}
|
onPointerUp={handlePointerUp}
|
||||||
|
onTouchStart={handleTouchStart}
|
||||||
>
|
>
|
||||||
<Defs />
|
<Defs />
|
||||||
{isReady && (
|
{isReady && (
|
||||||
<g ref={rGroup}>
|
<g ref={rGroup}>
|
||||||
<BoundsBg />
|
<BoundsBg />
|
||||||
<Page />
|
<Page />
|
||||||
<Bounds />
|
|
||||||
<Selected />
|
<Selected />
|
||||||
|
<Bounds />
|
||||||
<Brush />
|
<Brush />
|
||||||
</g>
|
</g>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -26,12 +26,11 @@ export const IconButton = styled('button', {
|
||||||
'& > svg': {
|
'& > svg': {
|
||||||
height: '16px',
|
height: '16px',
|
||||||
width: '16px',
|
width: '16px',
|
||||||
// strokeWidth: '2px',
|
|
||||||
// stroke: '$text',
|
|
||||||
},
|
},
|
||||||
|
|
||||||
variants: {
|
variants: {
|
||||||
size: {
|
size: {
|
||||||
|
small: {},
|
||||||
medium: {
|
medium: {
|
||||||
height: 44,
|
height: 44,
|
||||||
width: 44,
|
width: 44,
|
||||||
|
|
|
@ -1,48 +1,62 @@
|
||||||
import { useStateDesigner } from "@state-designer/react"
|
import { useStateDesigner } from '@state-designer/react'
|
||||||
import state from "state"
|
import state from 'state'
|
||||||
import styled from "styles"
|
import styled from 'styles'
|
||||||
import { useRef } from "react"
|
import { useRef } from 'react'
|
||||||
|
|
||||||
export default function StatusBar() {
|
export default function StatusBar() {
|
||||||
const local = useStateDesigner(state)
|
const local = useStateDesigner(state)
|
||||||
const { count, time } = useRenderCount()
|
const { count, time } = useRenderCount()
|
||||||
|
|
||||||
const active = local.active.slice(1).map((s) => s.split("root.")[1])
|
const active = local.active.slice(1).map((s) => s.split('root.')[1])
|
||||||
const log = local.log[0]
|
const log = local.log[0]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StatusBarContainer>
|
<StatusBarContainer
|
||||||
<Section>{active.join(" | ")}</Section>
|
size={{
|
||||||
|
'@sm': 'small',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Section>{active.join(' | ')}</Section>
|
||||||
<Section>| {log}</Section>
|
<Section>| {log}</Section>
|
||||||
<Section title="Renders | Time">
|
{/* <Section
|
||||||
{count} | {time.toString().padStart(3, "0")}
|
title="Renders | Time"
|
||||||
</Section>
|
>
|
||||||
|
{count} | {time.toString().padStart(3, '0')}
|
||||||
|
</Section> */}
|
||||||
</StatusBarContainer>
|
</StatusBarContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const StatusBarContainer = styled("div", {
|
const StatusBarContainer = styled('div', {
|
||||||
position: "absolute",
|
position: 'absolute',
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
width: "100%",
|
width: '100%',
|
||||||
height: 40,
|
height: 40,
|
||||||
userSelect: "none",
|
userSelect: 'none',
|
||||||
borderTop: "1px solid black",
|
borderTop: '1px solid black',
|
||||||
gridArea: "status",
|
gridArea: 'status',
|
||||||
display: "grid",
|
display: 'grid',
|
||||||
gridTemplateColumns: "auto 1fr auto",
|
gridTemplateColumns: 'auto 1fr auto',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
backgroundColor: "white",
|
backgroundColor: 'white',
|
||||||
gap: 8,
|
gap: 8,
|
||||||
fontSize: "$1",
|
fontSize: '$0',
|
||||||
padding: "0 16px",
|
padding: '0 16px',
|
||||||
zIndex: 200,
|
zIndex: 200,
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
small: {
|
||||||
|
fontSize: '$1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const Section = styled("div", {
|
const Section = styled('div', {
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: 'nowrap',
|
||||||
overflow: "hidden",
|
overflow: 'hidden',
|
||||||
})
|
})
|
||||||
|
|
||||||
function useRenderCount() {
|
function useRenderCount() {
|
||||||
|
|
|
@ -52,10 +52,11 @@ export default function ToolsPanel() {
|
||||||
return (
|
return (
|
||||||
<OuterContainer>
|
<OuterContainer>
|
||||||
<Zoom />
|
<Zoom />
|
||||||
|
<Flex>
|
||||||
<Container>
|
<Container>
|
||||||
<IconButton
|
<IconButton
|
||||||
name="select"
|
name="select"
|
||||||
size="large"
|
size={{ '@sm': 'small', '@md': 'large' }}
|
||||||
onClick={selectSelectTool}
|
onClick={selectSelectTool}
|
||||||
isActive={activeTool === 'select'}
|
isActive={activeTool === 'select'}
|
||||||
>
|
>
|
||||||
|
@ -65,7 +66,7 @@ export default function ToolsPanel() {
|
||||||
<Container>
|
<Container>
|
||||||
<IconButton
|
<IconButton
|
||||||
name={ShapeType.Draw}
|
name={ShapeType.Draw}
|
||||||
size="large"
|
size={{ '@sm': 'small', '@md': 'large' }}
|
||||||
onClick={selectDrawTool}
|
onClick={selectDrawTool}
|
||||||
isActive={activeTool === ShapeType.Draw}
|
isActive={activeTool === ShapeType.Draw}
|
||||||
>
|
>
|
||||||
|
@ -73,7 +74,7 @@ export default function ToolsPanel() {
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
name={ShapeType.Rectangle}
|
name={ShapeType.Rectangle}
|
||||||
size="large"
|
size={{ '@sm': 'small', '@md': 'large' }}
|
||||||
onClick={selectRectangleTool}
|
onClick={selectRectangleTool}
|
||||||
isActive={activeTool === ShapeType.Rectangle}
|
isActive={activeTool === ShapeType.Rectangle}
|
||||||
>
|
>
|
||||||
|
@ -81,7 +82,7 @@ export default function ToolsPanel() {
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
name={ShapeType.Circle}
|
name={ShapeType.Circle}
|
||||||
size="large"
|
size={{ '@sm': 'small', '@md': 'large' }}
|
||||||
onClick={selectCircleTool}
|
onClick={selectCircleTool}
|
||||||
isActive={activeTool === ShapeType.Circle}
|
isActive={activeTool === ShapeType.Circle}
|
||||||
>
|
>
|
||||||
|
@ -89,7 +90,7 @@ export default function ToolsPanel() {
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
name={ShapeType.Ellipse}
|
name={ShapeType.Ellipse}
|
||||||
size="large"
|
size={{ '@sm': 'small', '@md': 'large' }}
|
||||||
onClick={selectEllipseTool}
|
onClick={selectEllipseTool}
|
||||||
isActive={activeTool === ShapeType.Ellipse}
|
isActive={activeTool === ShapeType.Ellipse}
|
||||||
>
|
>
|
||||||
|
@ -97,7 +98,7 @@ export default function ToolsPanel() {
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
name={ShapeType.Line}
|
name={ShapeType.Line}
|
||||||
size="large"
|
size={{ '@sm': 'small', '@md': 'large' }}
|
||||||
onClick={selectLineTool}
|
onClick={selectLineTool}
|
||||||
isActive={activeTool === ShapeType.Line}
|
isActive={activeTool === ShapeType.Line}
|
||||||
>
|
>
|
||||||
|
@ -105,7 +106,7 @@ export default function ToolsPanel() {
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
name={ShapeType.Ray}
|
name={ShapeType.Ray}
|
||||||
size="large"
|
size={{ '@sm': 'small', '@md': 'large' }}
|
||||||
onClick={selectRayTool}
|
onClick={selectRayTool}
|
||||||
isActive={activeTool === ShapeType.Ray}
|
isActive={activeTool === ShapeType.Ray}
|
||||||
>
|
>
|
||||||
|
@ -113,7 +114,7 @@ export default function ToolsPanel() {
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
name={ShapeType.Dot}
|
name={ShapeType.Dot}
|
||||||
size="large"
|
size={{ '@sm': 'small', '@md': 'large' }}
|
||||||
onClick={selectDotTool}
|
onClick={selectDotTool}
|
||||||
isActive={activeTool === ShapeType.Dot}
|
isActive={activeTool === ShapeType.Dot}
|
||||||
>
|
>
|
||||||
|
@ -121,32 +122,47 @@ export default function ToolsPanel() {
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Container>
|
</Container>
|
||||||
<Container>
|
<Container>
|
||||||
<IconButton size="medium" onClick={selectToolLock}>
|
<IconButton
|
||||||
|
size={{ '@sm': 'small', '@md': 'large' }}
|
||||||
|
onClick={selectToolLock}
|
||||||
|
>
|
||||||
{isToolLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
|
{isToolLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
{isPenLocked && (
|
{isPenLocked && (
|
||||||
<IconButton size="medium" onClick={selectToolLock}>
|
<IconButton
|
||||||
|
size={{ '@sm': 'small', '@md': 'large' }}
|
||||||
|
onClick={selectToolLock}
|
||||||
|
>
|
||||||
<Pencil2Icon />
|
<Pencil2Icon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
|
</Flex>
|
||||||
<UndoRedo />
|
<UndoRedo />
|
||||||
</OuterContainer>
|
</OuterContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Spacer = styled('div', { flexGrow: 2 })
|
|
||||||
|
|
||||||
const OuterContainer = styled('div', {
|
const OuterContainer = styled('div', {
|
||||||
position: 'relative',
|
position: 'fixed',
|
||||||
gridArea: 'tools',
|
bottom: 40,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
padding: '0 8px 12px 8px',
|
padding: '0 8px 12px 8px',
|
||||||
height: '100%',
|
|
||||||
width: '100%',
|
width: '100%',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
flexWrap: 'wrap',
|
||||||
gap: 16,
|
gap: 16,
|
||||||
|
zIndex: 200,
|
||||||
|
})
|
||||||
|
|
||||||
|
const Flex = styled('div', {
|
||||||
|
display: 'flex',
|
||||||
|
'& > *:nth-child(n+2)': {
|
||||||
|
marginLeft: 16,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const Container = styled('div', {
|
const Container = styled('div', {
|
||||||
|
@ -157,8 +173,6 @@ const Container = styled('div', {
|
||||||
border: '1px solid $border',
|
border: '1px solid $border',
|
||||||
pointerEvents: 'all',
|
pointerEvents: 'all',
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
zIndex: 200,
|
|
||||||
boxShadow: '0px 2px 25px rgba(0,0,0,.16)',
|
|
||||||
height: '100%',
|
height: '100%',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
padding: 4,
|
padding: 4,
|
||||||
|
|
|
@ -9,7 +9,7 @@ const clear = () => state.send('CLEARED_PAGE')
|
||||||
|
|
||||||
export default function UndoRedo() {
|
export default function UndoRedo() {
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container size={{ '@sm': 'small' }}>
|
||||||
<IconButton onClick={undo}>
|
<IconButton onClick={undo}>
|
||||||
<RotateCcw />
|
<RotateCcw />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -25,7 +25,7 @@ export default function UndoRedo() {
|
||||||
|
|
||||||
const Container = styled('div', {
|
const Container = styled('div', {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: 12,
|
bottom: 64,
|
||||||
right: 12,
|
right: 12,
|
||||||
backgroundColor: '$panel',
|
backgroundColor: '$panel',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
|
@ -43,4 +43,12 @@ const Container = styled('div', {
|
||||||
height: 13,
|
height: 13,
|
||||||
width: 13,
|
width: 13,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
small: {
|
||||||
|
bottom: 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -10,7 +10,7 @@ const zoomToActual = () => state.send('ZOOMED_TO_ACTUAL')
|
||||||
|
|
||||||
export default function Zoom() {
|
export default function Zoom() {
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container size={{ '@sm': 'small' }}>
|
||||||
<IconButton onClick={zoomOut}>
|
<IconButton onClick={zoomOut}>
|
||||||
<ZoomOutIcon />
|
<ZoomOutIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -33,8 +33,8 @@ function ZoomCounter() {
|
||||||
|
|
||||||
const Container = styled('div', {
|
const Container = styled('div', {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: 12,
|
|
||||||
left: 12,
|
left: 12,
|
||||||
|
bottom: 64,
|
||||||
backgroundColor: '$panel',
|
backgroundColor: '$panel',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
|
@ -50,6 +50,14 @@ const Container = styled('div', {
|
||||||
'& svg': {
|
'& svg': {
|
||||||
strokeWidth: 0,
|
strokeWidth: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
small: {
|
||||||
|
bottom: 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const ZoomButton = styled(IconButton, {
|
const ZoomButton = styled(IconButton, {
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
import { useCallback, useRef } from "react"
|
import { useCallback, useRef } from 'react'
|
||||||
import inputs from "state/inputs"
|
import inputs from 'state/inputs'
|
||||||
import { Edge, Corner } from "types"
|
import { Edge, Corner } from 'types'
|
||||||
|
|
||||||
import state from "../state"
|
import state from '../state'
|
||||||
|
|
||||||
export default function useBoundsHandleEvents(
|
export default function useBoundsHandleEvents(
|
||||||
handle: Edge | Corner | "rotate"
|
handle: Edge | Corner | 'rotate'
|
||||||
) {
|
) {
|
||||||
const onPointerDown = useCallback(
|
const onPointerDown = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
if (e.buttons !== 1) return
|
if (e.buttons !== 1) return
|
||||||
|
if (!inputs.canAccept(e.pointerId)) return
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
e.currentTarget.setPointerCapture(e.pointerId)
|
e.currentTarget.setPointerCapture(e.pointerId)
|
||||||
state.send("POINTED_BOUNDS_HANDLE", inputs.pointerDown(e, handle))
|
state.send('POINTED_BOUNDS_HANDLE', inputs.pointerDown(e, handle))
|
||||||
},
|
},
|
||||||
[handle]
|
[handle]
|
||||||
)
|
)
|
||||||
|
@ -20,18 +21,20 @@ export default function useBoundsHandleEvents(
|
||||||
const onPointerMove = useCallback(
|
const onPointerMove = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
if (e.buttons !== 1) return
|
if (e.buttons !== 1) return
|
||||||
|
if (!inputs.canAccept(e.pointerId)) return
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
state.send("MOVED_POINTER", inputs.pointerMove(e))
|
state.send('MOVED_POINTER', inputs.pointerMove(e))
|
||||||
},
|
},
|
||||||
[handle]
|
[handle]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onPointerUp = useCallback((e) => {
|
const onPointerUp = useCallback((e) => {
|
||||||
if (e.buttons !== 1) return
|
if (e.buttons !== 1) return
|
||||||
|
if (!inputs.canAccept(e.pointerId)) return
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
e.currentTarget.releasePointerCapture(e.pointerId)
|
e.currentTarget.releasePointerCapture(e.pointerId)
|
||||||
e.currentTarget.replaceWith(e.currentTarget)
|
e.currentTarget.replaceWith(e.currentTarget)
|
||||||
state.send("STOPPED_POINTING", inputs.pointerUp(e))
|
state.send('STOPPED_POINTING', inputs.pointerUp(e))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return { onPointerDown, onPointerMove, onPointerUp }
|
return { onPointerDown, onPointerMove, onPointerUp }
|
||||||
|
|
|
@ -8,7 +8,8 @@ export default function useShapeEvents(
|
||||||
) {
|
) {
|
||||||
const handlePointerDown = useCallback(
|
const handlePointerDown = useCallback(
|
||||||
(e: React.PointerEvent) => {
|
(e: React.PointerEvent) => {
|
||||||
e.stopPropagation()
|
if (!inputs.canAccept(e.pointerId)) return
|
||||||
|
// e.stopPropagation()
|
||||||
rGroup.current.setPointerCapture(e.pointerId)
|
rGroup.current.setPointerCapture(e.pointerId)
|
||||||
state.send('POINTED_SHAPE', inputs.pointerDown(e, id))
|
state.send('POINTED_SHAPE', inputs.pointerDown(e, id))
|
||||||
},
|
},
|
||||||
|
@ -17,7 +18,8 @@ export default function useShapeEvents(
|
||||||
|
|
||||||
const handlePointerUp = useCallback(
|
const handlePointerUp = useCallback(
|
||||||
(e: React.PointerEvent) => {
|
(e: React.PointerEvent) => {
|
||||||
e.stopPropagation()
|
if (!inputs.canAccept(e.pointerId)) return
|
||||||
|
// e.stopPropagation()
|
||||||
rGroup.current.releasePointerCapture(e.pointerId)
|
rGroup.current.releasePointerCapture(e.pointerId)
|
||||||
state.send('STOPPED_POINTING', inputs.pointerUp(e))
|
state.send('STOPPED_POINTING', inputs.pointerUp(e))
|
||||||
},
|
},
|
||||||
|
@ -26,6 +28,7 @@ export default function useShapeEvents(
|
||||||
|
|
||||||
const handlePointerEnter = useCallback(
|
const handlePointerEnter = useCallback(
|
||||||
(e: React.PointerEvent) => {
|
(e: React.PointerEvent) => {
|
||||||
|
if (!inputs.canAccept(e.pointerId)) return
|
||||||
state.send('HOVERED_SHAPE', inputs.pointerEnter(e, id))
|
state.send('HOVERED_SHAPE', inputs.pointerEnter(e, id))
|
||||||
},
|
},
|
||||||
[id]
|
[id]
|
||||||
|
@ -33,13 +36,17 @@ export default function useShapeEvents(
|
||||||
|
|
||||||
const handlePointerMove = useCallback(
|
const handlePointerMove = useCallback(
|
||||||
(e: React.PointerEvent) => {
|
(e: React.PointerEvent) => {
|
||||||
|
if (!inputs.canAccept(e.pointerId)) return
|
||||||
state.send('MOVED_OVER_SHAPE', inputs.pointerEnter(e, id))
|
state.send('MOVED_OVER_SHAPE', inputs.pointerEnter(e, id))
|
||||||
},
|
},
|
||||||
[id]
|
[id]
|
||||||
)
|
)
|
||||||
|
|
||||||
const handlePointerLeave = useCallback(
|
const handlePointerLeave = useCallback(
|
||||||
() => state.send('UNHOVERED_SHAPE', { target: id }),
|
(e: React.PointerEvent) => {
|
||||||
|
if (!inputs.canAccept(e.pointerId)) return
|
||||||
|
state.send('UNHOVERED_SHAPE', { target: id })
|
||||||
|
},
|
||||||
[id]
|
[id]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ 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 { useGesture } 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.
|
||||||
|
@ -12,66 +12,26 @@ import { usePinch } from 'react-use-gesture'
|
||||||
export default function useZoomEvents(
|
export default function useZoomEvents(
|
||||||
ref: React.MutableRefObject<SVGSVGElement>
|
ref: React.MutableRefObject<SVGSVGElement>
|
||||||
) {
|
) {
|
||||||
const rTouchDist = useRef(0)
|
const rPinchDa = useRef<number[] | undefined>(undefined)
|
||||||
|
const rPinchPoint = useRef<number[] | undefined>(undefined)
|
||||||
|
|
||||||
useEffect(() => {
|
const bind = useGesture(
|
||||||
const element = ref.current
|
{
|
||||||
|
onWheel: ({ event, delta }) => {
|
||||||
if (!element) return
|
if (event.ctrlKey) {
|
||||||
|
|
||||||
function handleWheel(e: WheelEvent) {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
|
|
||||||
if (e.ctrlKey) {
|
|
||||||
state.send('ZOOMED_CAMERA', {
|
state.send('ZOOMED_CAMERA', {
|
||||||
delta: e.deltaY,
|
delta: delta[1],
|
||||||
...inputs.wheel(e),
|
...inputs.wheel(event as WheelEvent),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
state.send('PANNED_CAMERA', {
|
state.send('PANNED_CAMERA', {
|
||||||
delta: [e.deltaX, e.deltaY],
|
delta,
|
||||||
...inputs.wheel(e),
|
...inputs.wheel(event as WheelEvent),
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
|
onPinch: ({ pinching, da, origin }) => {
|
||||||
function handleTouchMove(e: TouchEvent) {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
|
|
||||||
if (e.touches.length === 2) {
|
|
||||||
const { clientX: x0, clientY: y0 } = e.touches[0]
|
|
||||||
const { clientX: x1, clientY: y1 } = e.touches[1]
|
|
||||||
|
|
||||||
const dist = vec.dist([x0, y0], [x1, y1])
|
|
||||||
const point = vec.med([x0, y0], [x1, y1])
|
|
||||||
|
|
||||||
state.send('WHEELED', {
|
|
||||||
delta: dist - rTouchDist.current,
|
|
||||||
point,
|
|
||||||
})
|
|
||||||
|
|
||||||
rTouchDist.current = dist
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
element.addEventListener('wheel', handleWheel, { passive: false })
|
|
||||||
element.addEventListener('touchstart', handleTouchMove, { passive: false })
|
|
||||||
element.addEventListener('touchmove', handleTouchMove, { passive: false })
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
element.removeEventListener('wheel', handleWheel)
|
|
||||||
element.removeEventListener('touchstart', handleTouchMove)
|
|
||||||
element.removeEventListener('touchmove', handleTouchMove)
|
|
||||||
}
|
|
||||||
}, [ref])
|
|
||||||
|
|
||||||
const rPinchDa = useRef<number[] | undefined>(undefined)
|
|
||||||
const rPinchPoint = useRef<number[] | undefined>(undefined)
|
|
||||||
|
|
||||||
const bind = usePinch(({ pinching, da, origin }) => {
|
|
||||||
if (!pinching) {
|
if (!pinching) {
|
||||||
state.send('STOPPED_PINCHING')
|
state.send('STOPPED_PINCHING')
|
||||||
rPinchDa.current = undefined
|
rPinchDa.current = undefined
|
||||||
|
@ -96,7 +56,13 @@ export default function useZoomEvents(
|
||||||
|
|
||||||
rPinchDa.current = da
|
rPinchDa.current = da
|
||||||
rPinchPoint.current = origin
|
rPinchPoint.current = origin
|
||||||
})
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
domTarget: document.body,
|
||||||
|
eventOptions: { passive: false },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return { ...bind() }
|
return { ...bind() }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Data } from "types"
|
import { Data } from 'types'
|
||||||
import { BaseCommand } from "./commands/command"
|
import { BaseCommand } from './commands/command'
|
||||||
import state from "./state"
|
import state from './state'
|
||||||
|
|
||||||
// A singleton to manage history changes.
|
// A singleton to manage history changes.
|
||||||
|
|
||||||
|
@ -11,10 +11,11 @@ class BaseHistory<T> {
|
||||||
private _enabled = true
|
private _enabled = true
|
||||||
|
|
||||||
execute = (data: T, command: BaseCommand<T>) => {
|
execute = (data: T, command: BaseCommand<T>) => {
|
||||||
|
command.redo(data, true)
|
||||||
|
|
||||||
if (this.disabled) return
|
if (this.disabled) return
|
||||||
this.stack = this.stack.slice(0, this.pointer + 1)
|
this.stack = this.stack.slice(0, this.pointer + 1)
|
||||||
this.stack.push(command)
|
this.stack.push(command)
|
||||||
command.redo(data, true)
|
|
||||||
this.pointer++
|
this.pointer++
|
||||||
|
|
||||||
if (this.stack.length > this.maxLength) {
|
if (this.stack.length > this.maxLength) {
|
||||||
|
@ -26,26 +27,26 @@ class BaseHistory<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
undo = (data: T) => {
|
undo = (data: T) => {
|
||||||
if (this.disabled) return
|
|
||||||
if (this.pointer === -1) return
|
if (this.pointer === -1) return
|
||||||
const command = this.stack[this.pointer]
|
const command = this.stack[this.pointer]
|
||||||
command.undo(data)
|
command.undo(data)
|
||||||
|
if (this.disabled) return
|
||||||
this.pointer--
|
this.pointer--
|
||||||
this.save(data)
|
this.save(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
redo = (data: T) => {
|
redo = (data: T) => {
|
||||||
if (this.disabled) return
|
|
||||||
if (this.pointer === this.stack.length - 1) return
|
if (this.pointer === this.stack.length - 1) return
|
||||||
const command = this.stack[this.pointer + 1]
|
const command = this.stack[this.pointer + 1]
|
||||||
command.redo(data, false)
|
command.redo(data, false)
|
||||||
|
if (this.disabled) return
|
||||||
this.pointer++
|
this.pointer++
|
||||||
this.save(data)
|
this.save(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
load(data: T, id = "code_slate_0.0.1") {
|
load(data: T, id = 'code_slate_0.0.1') {
|
||||||
if (typeof window === "undefined") return
|
if (typeof window === 'undefined') return
|
||||||
if (typeof localStorage === "undefined") return
|
if (typeof localStorage === 'undefined') return
|
||||||
|
|
||||||
const savedData = localStorage.getItem(id)
|
const savedData = localStorage.getItem(id)
|
||||||
|
|
||||||
|
@ -54,9 +55,9 @@ class BaseHistory<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
save = (data: T, id = "code_slate_0.0.1") => {
|
save = (data: T, id = 'code_slate_0.0.1') => {
|
||||||
if (typeof window === "undefined") return
|
if (typeof window === 'undefined') return
|
||||||
if (typeof localStorage === "undefined") return
|
if (typeof localStorage === 'undefined') return
|
||||||
|
|
||||||
localStorage.setItem(id, JSON.stringify(this.prepareDataForSave(data)))
|
localStorage.setItem(id, JSON.stringify(this.prepareDataForSave(data)))
|
||||||
}
|
}
|
||||||
|
@ -110,14 +111,14 @@ class History extends BaseHistory<Data> {
|
||||||
restoredData.selectedIds = new Set(restoredData.selectedIds)
|
restoredData.selectedIds = new Set(restoredData.selectedIds)
|
||||||
|
|
||||||
// Also restore camera position, which is saved separately in this app
|
// Also restore camera position, which is saved separately in this app
|
||||||
const cameraInfo = localStorage.getItem("code_slate_camera")
|
const cameraInfo = localStorage.getItem('code_slate_camera')
|
||||||
|
|
||||||
if (cameraInfo !== null) {
|
if (cameraInfo !== null) {
|
||||||
Object.assign(restoredData.camera, JSON.parse(cameraInfo))
|
Object.assign(restoredData.camera, JSON.parse(cameraInfo))
|
||||||
|
|
||||||
// And update the CSS property
|
// And update the CSS property
|
||||||
document.documentElement.style.setProperty(
|
document.documentElement.style.setProperty(
|
||||||
"--camera-zoom",
|
'--camera-zoom',
|
||||||
restoredData.camera.zoom.toString()
|
restoredData.camera.zoom.toString()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { PointerInfo } from "types"
|
import { PointerInfo } from 'types'
|
||||||
import { isDarwin } from "utils/utils"
|
import { isDarwin } from 'utils/utils'
|
||||||
|
|
||||||
class Inputs {
|
class Inputs {
|
||||||
|
activePointerId?: number
|
||||||
points: Record<string, PointerInfo> = {}
|
points: Record<string, PointerInfo> = {}
|
||||||
|
|
||||||
pointerDown(e: PointerEvent | React.PointerEvent, target: string) {
|
pointerDown(e: PointerEvent | React.PointerEvent, target: string) {
|
||||||
|
@ -19,6 +20,7 @@ class Inputs {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.points[e.pointerId] = info
|
this.points[e.pointerId] = info
|
||||||
|
this.activePointerId = e.pointerId
|
||||||
|
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
@ -78,6 +80,7 @@ class Inputs {
|
||||||
}
|
}
|
||||||
|
|
||||||
delete this.points[e.pointerId]
|
delete this.points[e.pointerId]
|
||||||
|
delete this.activePointerId
|
||||||
|
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
@ -87,6 +90,12 @@ class Inputs {
|
||||||
return { point: [e.clientX, e.clientY], shiftKey, ctrlKey, metaKey, altKey }
|
return { point: [e.clientX, e.clientY], shiftKey, ctrlKey, metaKey, altKey }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canAccept(pointerId: PointerEvent['pointerId']) {
|
||||||
|
return (
|
||||||
|
this.activePointerId === undefined || this.activePointerId === pointerId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
get pointer() {
|
get pointer() {
|
||||||
return this.points[Object.keys(this.points)[0]]
|
return this.points[Object.keys(this.points)[0]]
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,11 @@ export default class RotateSession extends BaseSession {
|
||||||
for (let { id, center, offset, rotation } of initialShapes) {
|
for (let { id, center, offset, rotation } of initialShapes) {
|
||||||
const shape = page.shapes[id]
|
const shape = page.shapes[id]
|
||||||
|
|
||||||
|
// const rotationOffset = vec.sub(
|
||||||
|
// getBoundsCenter(getShapeBounds(shape)),
|
||||||
|
// getBoundsCenter(getRotatedBounds(shape))
|
||||||
|
// )
|
||||||
|
|
||||||
const nextRotation = isLocked
|
const nextRotation = isLocked
|
||||||
? clampToRotationToSegments(rotation + rot, 24)
|
? clampToRotationToSegments(rotation + rot, 24)
|
||||||
: rotation + rot
|
: rotation + rot
|
||||||
|
@ -100,11 +105,17 @@ export function getRotateSnapshot(data: Data) {
|
||||||
const center = getBoundsCenter(bounds)
|
const center = getBoundsCenter(bounds)
|
||||||
const offset = vec.sub(center, shape.point)
|
const offset = vec.sub(center, shape.point)
|
||||||
|
|
||||||
|
const rotationOffset = vec.sub(
|
||||||
|
center,
|
||||||
|
getBoundsCenter(getRotatedBounds(shape))
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: shape.id,
|
id: shape.id,
|
||||||
point: shape.point,
|
point: shape.point,
|
||||||
rotation: shape.rotation,
|
rotation: shape.rotation,
|
||||||
offset,
|
offset,
|
||||||
|
rotationOffset,
|
||||||
center,
|
center,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -68,6 +68,28 @@ const initialData: Data = {
|
||||||
|
|
||||||
const state = createState({
|
const state = createState({
|
||||||
data: initialData,
|
data: initialData,
|
||||||
|
on: {
|
||||||
|
UNMOUNTED: [{ unless: 'isReadOnly', do: 'forceSave' }, { to: 'loading' }],
|
||||||
|
},
|
||||||
|
initial: 'loading',
|
||||||
|
states: {
|
||||||
|
loading: {
|
||||||
|
on: {
|
||||||
|
MOUNTED: [
|
||||||
|
'restoreSavedData',
|
||||||
|
{
|
||||||
|
to: 'ready',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ready: {
|
||||||
|
onEnter: {
|
||||||
|
wait: 0.01,
|
||||||
|
if: 'hasSelection',
|
||||||
|
do: 'zoomCameraToSelectionActual',
|
||||||
|
else: ['zoomCameraToFit', 'zoomCameraToActual'],
|
||||||
|
},
|
||||||
on: {
|
on: {
|
||||||
ZOOMED_CAMERA: {
|
ZOOMED_CAMERA: {
|
||||||
do: 'zoomCamera',
|
do: 'zoomCamera',
|
||||||
|
@ -110,31 +132,7 @@ const state = createState({
|
||||||
NUDGED: { do: 'nudgeSelection' },
|
NUDGED: { do: 'nudgeSelection' },
|
||||||
USED_PEN_DEVICE: 'enablePenLock',
|
USED_PEN_DEVICE: 'enablePenLock',
|
||||||
DISABLED_PEN_LOCK: 'disablePenLock',
|
DISABLED_PEN_LOCK: 'disablePenLock',
|
||||||
},
|
CLEARED_PAGE: ['selectAll', 'deleteSelection'],
|
||||||
initial: 'loading',
|
|
||||||
states: {
|
|
||||||
loading: {
|
|
||||||
on: {
|
|
||||||
MOUNTED: [
|
|
||||||
'restoreSavedData',
|
|
||||||
{
|
|
||||||
to: 'ready',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ready: {
|
|
||||||
onEnter: {
|
|
||||||
wait: 0.01,
|
|
||||||
if: 'hasSelection',
|
|
||||||
do: 'zoomCameraToSelectionActual',
|
|
||||||
else: ['zoomCameraToFit', 'zoomCameraToActual'],
|
|
||||||
},
|
|
||||||
on: {
|
|
||||||
UNMOUNTED: [
|
|
||||||
{ unless: 'isReadOnly', do: 'forceSave' },
|
|
||||||
{ to: 'loading' },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
initial: 'selecting',
|
initial: 'selecting',
|
||||||
states: {
|
states: {
|
||||||
|
@ -143,10 +141,8 @@ const state = createState({
|
||||||
SAVED: 'forceSave',
|
SAVED: 'forceSave',
|
||||||
UNDO: 'undo',
|
UNDO: 'undo',
|
||||||
REDO: 'redo',
|
REDO: 'redo',
|
||||||
CLEARED_PAGE: ['selectAll', 'deleteSelection'],
|
|
||||||
SAVED_CODE: 'saveCode',
|
SAVED_CODE: 'saveCode',
|
||||||
DELETED: 'deleteSelection',
|
DELETED: 'deleteSelection',
|
||||||
STARTED_PINCHING: { to: 'pinching' },
|
|
||||||
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',
|
||||||
|
@ -164,6 +160,7 @@ const state = createState({
|
||||||
notPointing: {
|
notPointing: {
|
||||||
on: {
|
on: {
|
||||||
CANCELLED: 'clearSelectedIds',
|
CANCELLED: 'clearSelectedIds',
|
||||||
|
STARTED_PINCHING: { to: 'pinching' },
|
||||||
POINTED_CANVAS: { to: 'brushSelecting' },
|
POINTED_CANVAS: { to: 'brushSelecting' },
|
||||||
POINTED_BOUNDS: { to: 'pointingBounds' },
|
POINTED_BOUNDS: { to: 'pointingBounds' },
|
||||||
POINTED_BOUNDS_HANDLE: {
|
POINTED_BOUNDS_HANDLE: {
|
||||||
|
@ -269,7 +266,7 @@ const state = createState({
|
||||||
'startBrushSession',
|
'startBrushSession',
|
||||||
],
|
],
|
||||||
on: {
|
on: {
|
||||||
STARTED_PINCHING: { to: 'pinching' },
|
STARTED_PINCHING: { do: 'completeSession', to: 'pinching' },
|
||||||
MOVED_POINTER: 'updateBrushSession',
|
MOVED_POINTER: 'updateBrushSession',
|
||||||
PANNED_CAMERA: 'updateBrushSession',
|
PANNED_CAMERA: 'updateBrushSession',
|
||||||
STOPPED_POINTING: { do: 'completeSession', to: 'selecting' },
|
STOPPED_POINTING: { do: 'completeSession', to: 'selecting' },
|
||||||
|
@ -280,14 +277,30 @@ const state = createState({
|
||||||
},
|
},
|
||||||
pinching: {
|
pinching: {
|
||||||
on: {
|
on: {
|
||||||
STOPPED_PINCHING: { to: 'selecting' },
|
|
||||||
PINCHED: { do: 'pinchCamera' },
|
PINCHED: { do: 'pinchCamera' },
|
||||||
},
|
},
|
||||||
|
initial: 'selectPinching',
|
||||||
|
states: {
|
||||||
|
selectPinching: {
|
||||||
|
on: {
|
||||||
|
STOPPED_PINCHING: { to: 'selecting' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
toolPinching: {
|
||||||
|
on: {
|
||||||
|
STOPPED_PINCHING: { to: 'usingTool.previous' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
usingTool: {
|
usingTool: {
|
||||||
initial: 'draw',
|
initial: 'draw',
|
||||||
onEnter: 'clearSelectedIds',
|
onEnter: 'clearSelectedIds',
|
||||||
on: {
|
on: {
|
||||||
|
STARTED_PINCHING: {
|
||||||
|
do: 'breakSession',
|
||||||
|
to: 'pinching.toolPinching',
|
||||||
|
},
|
||||||
TOGGLED_TOOL_LOCK: 'toggleToolLock',
|
TOGGLED_TOOL_LOCK: 'toggleToolLock',
|
||||||
},
|
},
|
||||||
states: {
|
states: {
|
||||||
|
@ -319,7 +332,7 @@ const state = createState({
|
||||||
to: 'draw.creating',
|
to: 'draw.creating',
|
||||||
},
|
},
|
||||||
CANCELLED: {
|
CANCELLED: {
|
||||||
do: ['cancelSession', 'deleteSelection'],
|
do: 'breakSession',
|
||||||
to: 'selecting',
|
to: 'selecting',
|
||||||
},
|
},
|
||||||
MOVED_POINTER: 'updateDrawSession',
|
MOVED_POINTER: 'updateDrawSession',
|
||||||
|
@ -359,7 +372,7 @@ const state = createState({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
CANCELLED: {
|
CANCELLED: {
|
||||||
do: ['cancelSession', 'deleteSelection'],
|
do: 'breakSession',
|
||||||
to: 'selecting',
|
to: 'selecting',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -545,7 +558,7 @@ const state = createState({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
CANCELLED: {
|
CANCELLED: {
|
||||||
do: ['cancelSession', 'deleteSelection'],
|
do: 'breakSession',
|
||||||
to: 'selecting',
|
to: 'selecting',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -662,6 +675,13 @@ const state = createState({
|
||||||
/* -------------------- Sessions -------------------- */
|
/* -------------------- Sessions -------------------- */
|
||||||
|
|
||||||
// Shared
|
// Shared
|
||||||
|
breakSession(data) {
|
||||||
|
session?.cancel(data)
|
||||||
|
session = undefined
|
||||||
|
history.disable()
|
||||||
|
commands.deleteSelected(data)
|
||||||
|
history.enable()
|
||||||
|
},
|
||||||
cancelSession(data) {
|
cancelSession(data) {
|
||||||
session?.cancel(data)
|
session?.cancel(data)
|
||||||
session = undefined
|
session = undefined
|
||||||
|
|
|
@ -42,6 +42,10 @@ const { styled, global, css, theme, getCssString } = createCss({
|
||||||
zIndices: {},
|
zIndices: {},
|
||||||
transitions: {},
|
transitions: {},
|
||||||
},
|
},
|
||||||
|
media: {
|
||||||
|
sm: '(min-width: 640px)',
|
||||||
|
md: '(min-width: 768px)',
|
||||||
|
},
|
||||||
utils: {
|
utils: {
|
||||||
zDash: () => (value: number) => {
|
zDash: () => (value: number) => {
|
||||||
return {
|
return {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue