瀏覽代碼

Improves undo/redo, fixes pinching and multitouch

main
Steve Ruiz 3 年之前
父節點
當前提交
76a4ccdfcb

+ 12
- 10
components/canvas/bounds/bounds-bg.tsx 查看文件

1
-import { useCallback, useRef } from "react"
2
-import state, { useSelector } from "state"
3
-import inputs from "state/inputs"
4
-import styled from "styles"
5
-import { getPage } from "utils/utils"
1
+import { useCallback, useRef } from 'react'
2
+import state, { useSelector } from 'state'
3
+import inputs from 'state/inputs'
4
+import styled from 'styles'
5
+import { getPage } from 'utils/utils'
6
 
6
 
7
 function handlePointerDown(e: React.PointerEvent<SVGRectElement>) {
7
 function handlePointerDown(e: React.PointerEvent<SVGRectElement>) {
8
   if (e.buttons !== 1) return
8
   if (e.buttons !== 1) return
9
+  if (!inputs.canAccept(e.pointerId)) return
9
   e.stopPropagation()
10
   e.stopPropagation()
10
   e.currentTarget.setPointerCapture(e.pointerId)
11
   e.currentTarget.setPointerCapture(e.pointerId)
11
-  state.send("POINTED_BOUNDS", inputs.pointerDown(e, "bounds"))
12
+  state.send('POINTED_BOUNDS', inputs.pointerDown(e, 'bounds'))
12
 }
13
 }
13
 
14
 
14
 function handlePointerUp(e: React.PointerEvent<SVGRectElement>) {
15
 function handlePointerUp(e: React.PointerEvent<SVGRectElement>) {
15
   if (e.buttons !== 1) return
16
   if (e.buttons !== 1) return
17
+  if (!inputs.canAccept(e.pointerId)) return
16
   e.stopPropagation()
18
   e.stopPropagation()
17
   e.currentTarget.releasePointerCapture(e.pointerId)
19
   e.currentTarget.releasePointerCapture(e.pointerId)
18
-  state.send("STOPPED_POINTING", inputs.pointerUp(e))
20
+  state.send('STOPPED_POINTING', inputs.pointerUp(e))
19
 }
21
 }
20
 
22
 
21
 export default function BoundsBg() {
23
 export default function BoundsBg() {
22
   const rBounds = useRef<SVGRectElement>(null)
24
   const rBounds = useRef<SVGRectElement>(null)
23
   const bounds = useSelector((state) => state.values.selectedBounds)
25
   const bounds = useSelector((state) => state.values.selectedBounds)
24
-  const isSelecting = useSelector((s) => s.isIn("selecting"))
26
+  const isSelecting = useSelector((s) => s.isIn('selecting'))
25
   const rotation = useSelector((s) => {
27
   const rotation = useSelector((s) => {
26
     if (s.data.selectedIds.size === 1) {
28
     if (s.data.selectedIds.size === 1) {
27
       const { shapes } = getPage(s.data)
29
       const { shapes } = getPage(s.data)
53
   )
55
   )
54
 }
56
 }
55
 
57
 
56
-const StyledBoundsBg = styled("rect", {
57
-  fill: "$boundsBg",
58
+const StyledBoundsBg = styled('rect', {
59
+  fill: '$boundsBg',
58
 })
60
 })

+ 14
- 2
components/canvas/canvas.tsx 查看文件

21
   const isReady = useSelector((s) => s.isIn('ready'))
21
   const isReady = useSelector((s) => s.isIn('ready'))
22
 
22
 
23
   const handlePointerDown = useCallback((e: React.PointerEvent) => {
23
   const handlePointerDown = useCallback((e: React.PointerEvent) => {
24
+    if (!inputs.canAccept(e.pointerId)) return
24
     rCanvas.current.setPointerCapture(e.pointerId)
25
     rCanvas.current.setPointerCapture(e.pointerId)
25
     state.send('POINTED_CANVAS', inputs.pointerDown(e, 'canvas'))
26
     state.send('POINTED_CANVAS', inputs.pointerDown(e, 'canvas'))
26
   }, [])
27
   }, [])
27
 
28
 
29
+  const handleTouchStart = useCallback((e: React.TouchEvent) => {
30
+    if (e.touches.length === 2) {
31
+      state.send('TOUCH_UNDO')
32
+    }
33
+  }, [])
34
+
28
   const handlePointerMove = useCallback((e: React.PointerEvent) => {
35
   const handlePointerMove = useCallback((e: React.PointerEvent) => {
29
-    state.send('MOVED_POINTER', inputs.pointerMove(e))
36
+    if (!inputs.canAccept(e.pointerId)) return
37
+    if (inputs.canAccept(e.pointerId)) {
38
+      state.send('MOVED_POINTER', inputs.pointerMove(e))
39
+    }
30
   }, [])
40
   }, [])
31
 
41
 
32
   const handlePointerUp = useCallback((e: React.PointerEvent) => {
42
   const handlePointerUp = useCallback((e: React.PointerEvent) => {
43
+    if (!inputs.canAccept(e.pointerId)) return
33
     rCanvas.current.releasePointerCapture(e.pointerId)
44
     rCanvas.current.releasePointerCapture(e.pointerId)
34
     state.send('STOPPED_POINTING', { id: 'canvas', ...inputs.pointerUp(e) })
45
     state.send('STOPPED_POINTING', { id: 'canvas', ...inputs.pointerUp(e) })
35
   }, [])
46
   }, [])
41
       onPointerDown={handlePointerDown}
52
       onPointerDown={handlePointerDown}
42
       onPointerMove={handlePointerMove}
53
       onPointerMove={handlePointerMove}
43
       onPointerUp={handlePointerUp}
54
       onPointerUp={handlePointerUp}
55
+      onTouchStart={handleTouchStart}
44
     >
56
     >
45
       <Defs />
57
       <Defs />
46
       {isReady && (
58
       {isReady && (
47
         <g ref={rGroup}>
59
         <g ref={rGroup}>
48
           <BoundsBg />
60
           <BoundsBg />
49
           <Page />
61
           <Page />
50
-          <Bounds />
51
           <Selected />
62
           <Selected />
63
+          <Bounds />
52
           <Brush />
64
           <Brush />
53
         </g>
65
         </g>
54
       )}
66
       )}

+ 1
- 2
components/shared.tsx 查看文件

26
   '& > svg': {
26
   '& > svg': {
27
     height: '16px',
27
     height: '16px',
28
     width: '16px',
28
     width: '16px',
29
-    // strokeWidth: '2px',
30
-    // stroke: '$text',
31
   },
29
   },
32
 
30
 
33
   variants: {
31
   variants: {
34
     size: {
32
     size: {
33
+      small: {},
35
       medium: {
34
       medium: {
36
         height: 44,
35
         height: 44,
37
         width: 44,
36
         width: 44,

+ 39
- 25
components/status-bar.tsx 查看文件

1
-import { useStateDesigner } from "@state-designer/react"
2
-import state from "state"
3
-import styled from "styles"
4
-import { useRef } from "react"
1
+import { useStateDesigner } from '@state-designer/react'
2
+import state from 'state'
3
+import styled from 'styles'
4
+import { useRef } from 'react'
5
 
5
 
6
 export default function StatusBar() {
6
 export default function StatusBar() {
7
   const local = useStateDesigner(state)
7
   const local = useStateDesigner(state)
8
   const { count, time } = useRenderCount()
8
   const { count, time } = useRenderCount()
9
 
9
 
10
-  const active = local.active.slice(1).map((s) => s.split("root.")[1])
10
+  const active = local.active.slice(1).map((s) => s.split('root.')[1])
11
   const log = local.log[0]
11
   const log = local.log[0]
12
 
12
 
13
   return (
13
   return (
14
-    <StatusBarContainer>
15
-      <Section>{active.join(" | ")}</Section>
14
+    <StatusBarContainer
15
+      size={{
16
+        '@sm': 'small',
17
+      }}
18
+    >
19
+      <Section>{active.join(' | ')}</Section>
16
       <Section>| {log}</Section>
20
       <Section>| {log}</Section>
17
-      <Section title="Renders | Time">
18
-        {count} | {time.toString().padStart(3, "0")}
19
-      </Section>
21
+      {/* <Section
22
+        title="Renders | Time"
23
+      >
24
+        {count} | {time.toString().padStart(3, '0')}
25
+      </Section> */}
20
     </StatusBarContainer>
26
     </StatusBarContainer>
21
   )
27
   )
22
 }
28
 }
23
 
29
 
24
-const StatusBarContainer = styled("div", {
25
-  position: "absolute",
30
+const StatusBarContainer = styled('div', {
31
+  position: 'absolute',
26
   bottom: 0,
32
   bottom: 0,
27
   left: 0,
33
   left: 0,
28
-  width: "100%",
34
+  width: '100%',
29
   height: 40,
35
   height: 40,
30
-  userSelect: "none",
31
-  borderTop: "1px solid black",
32
-  gridArea: "status",
33
-  display: "grid",
34
-  gridTemplateColumns: "auto 1fr auto",
35
-  alignItems: "center",
36
-  backgroundColor: "white",
36
+  userSelect: 'none',
37
+  borderTop: '1px solid black',
38
+  gridArea: 'status',
39
+  display: 'grid',
40
+  gridTemplateColumns: 'auto 1fr auto',
41
+  alignItems: 'center',
42
+  backgroundColor: 'white',
37
   gap: 8,
43
   gap: 8,
38
-  fontSize: "$1",
39
-  padding: "0 16px",
44
+  fontSize: '$0',
45
+  padding: '0 16px',
40
   zIndex: 200,
46
   zIndex: 200,
47
+
48
+  variants: {
49
+    size: {
50
+      small: {
51
+        fontSize: '$1',
52
+      },
53
+    },
54
+  },
41
 })
55
 })
42
 
56
 
43
-const Section = styled("div", {
44
-  whiteSpace: "nowrap",
45
-  overflow: "hidden",
57
+const Section = styled('div', {
58
+  whiteSpace: 'nowrap',
59
+  overflow: 'hidden',
46
 })
60
 })
47
 
61
 
48
 function useRenderCount() {
62
 function useRenderCount() {

+ 98
- 84
components/tools-panel/tools-panel.tsx 查看文件

52
   return (
52
   return (
53
     <OuterContainer>
53
     <OuterContainer>
54
       <Zoom />
54
       <Zoom />
55
-      <Container>
56
-        <IconButton
57
-          name="select"
58
-          size="large"
59
-          onClick={selectSelectTool}
60
-          isActive={activeTool === 'select'}
61
-        >
62
-          <CursorArrowIcon />
63
-        </IconButton>
64
-      </Container>
65
-      <Container>
66
-        <IconButton
67
-          name={ShapeType.Draw}
68
-          size="large"
69
-          onClick={selectDrawTool}
70
-          isActive={activeTool === ShapeType.Draw}
71
-        >
72
-          <Pencil1Icon />
73
-        </IconButton>
74
-        <IconButton
75
-          name={ShapeType.Rectangle}
76
-          size="large"
77
-          onClick={selectRectangleTool}
78
-          isActive={activeTool === ShapeType.Rectangle}
79
-        >
80
-          <SquareIcon />
81
-        </IconButton>
82
-        <IconButton
83
-          name={ShapeType.Circle}
84
-          size="large"
85
-          onClick={selectCircleTool}
86
-          isActive={activeTool === ShapeType.Circle}
87
-        >
88
-          <CircleIcon />
89
-        </IconButton>
90
-        <IconButton
91
-          name={ShapeType.Ellipse}
92
-          size="large"
93
-          onClick={selectEllipseTool}
94
-          isActive={activeTool === ShapeType.Ellipse}
95
-        >
96
-          <CircleIcon transform="rotate(-45) scale(1, .8)" />
97
-        </IconButton>
98
-        <IconButton
99
-          name={ShapeType.Line}
100
-          size="large"
101
-          onClick={selectLineTool}
102
-          isActive={activeTool === ShapeType.Line}
103
-        >
104
-          <DividerHorizontalIcon transform="rotate(-45)" />
105
-        </IconButton>
106
-        <IconButton
107
-          name={ShapeType.Ray}
108
-          size="large"
109
-          onClick={selectRayTool}
110
-          isActive={activeTool === ShapeType.Ray}
111
-        >
112
-          <SewingPinIcon transform="rotate(-135)" />
113
-        </IconButton>
114
-        <IconButton
115
-          name={ShapeType.Dot}
116
-          size="large"
117
-          onClick={selectDotTool}
118
-          isActive={activeTool === ShapeType.Dot}
119
-        >
120
-          <DotIcon />
121
-        </IconButton>
122
-      </Container>
123
-      <Container>
124
-        <IconButton size="medium" onClick={selectToolLock}>
125
-          {isToolLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
126
-        </IconButton>
127
-        {isPenLocked && (
128
-          <IconButton size="medium" onClick={selectToolLock}>
129
-            <Pencil2Icon />
55
+      <Flex>
56
+        <Container>
57
+          <IconButton
58
+            name="select"
59
+            size={{ '@sm': 'small', '@md': 'large' }}
60
+            onClick={selectSelectTool}
61
+            isActive={activeTool === 'select'}
62
+          >
63
+            <CursorArrowIcon />
130
           </IconButton>
64
           </IconButton>
131
-        )}
132
-      </Container>
65
+        </Container>
66
+        <Container>
67
+          <IconButton
68
+            name={ShapeType.Draw}
69
+            size={{ '@sm': 'small', '@md': 'large' }}
70
+            onClick={selectDrawTool}
71
+            isActive={activeTool === ShapeType.Draw}
72
+          >
73
+            <Pencil1Icon />
74
+          </IconButton>
75
+          <IconButton
76
+            name={ShapeType.Rectangle}
77
+            size={{ '@sm': 'small', '@md': 'large' }}
78
+            onClick={selectRectangleTool}
79
+            isActive={activeTool === ShapeType.Rectangle}
80
+          >
81
+            <SquareIcon />
82
+          </IconButton>
83
+          <IconButton
84
+            name={ShapeType.Circle}
85
+            size={{ '@sm': 'small', '@md': 'large' }}
86
+            onClick={selectCircleTool}
87
+            isActive={activeTool === ShapeType.Circle}
88
+          >
89
+            <CircleIcon />
90
+          </IconButton>
91
+          <IconButton
92
+            name={ShapeType.Ellipse}
93
+            size={{ '@sm': 'small', '@md': 'large' }}
94
+            onClick={selectEllipseTool}
95
+            isActive={activeTool === ShapeType.Ellipse}
96
+          >
97
+            <CircleIcon transform="rotate(-45) scale(1, .8)" />
98
+          </IconButton>
99
+          <IconButton
100
+            name={ShapeType.Line}
101
+            size={{ '@sm': 'small', '@md': 'large' }}
102
+            onClick={selectLineTool}
103
+            isActive={activeTool === ShapeType.Line}
104
+          >
105
+            <DividerHorizontalIcon transform="rotate(-45)" />
106
+          </IconButton>
107
+          <IconButton
108
+            name={ShapeType.Ray}
109
+            size={{ '@sm': 'small', '@md': 'large' }}
110
+            onClick={selectRayTool}
111
+            isActive={activeTool === ShapeType.Ray}
112
+          >
113
+            <SewingPinIcon transform="rotate(-135)" />
114
+          </IconButton>
115
+          <IconButton
116
+            name={ShapeType.Dot}
117
+            size={{ '@sm': 'small', '@md': 'large' }}
118
+            onClick={selectDotTool}
119
+            isActive={activeTool === ShapeType.Dot}
120
+          >
121
+            <DotIcon />
122
+          </IconButton>
123
+        </Container>
124
+        <Container>
125
+          <IconButton
126
+            size={{ '@sm': 'small', '@md': 'large' }}
127
+            onClick={selectToolLock}
128
+          >
129
+            {isToolLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
130
+          </IconButton>
131
+          {isPenLocked && (
132
+            <IconButton
133
+              size={{ '@sm': 'small', '@md': 'large' }}
134
+              onClick={selectToolLock}
135
+            >
136
+              <Pencil2Icon />
137
+            </IconButton>
138
+          )}
139
+        </Container>
140
+      </Flex>
133
       <UndoRedo />
141
       <UndoRedo />
134
     </OuterContainer>
142
     </OuterContainer>
135
   )
143
   )
136
 }
144
 }
137
 
145
 
138
-const Spacer = styled('div', { flexGrow: 2 })
139
-
140
 const OuterContainer = styled('div', {
146
 const OuterContainer = styled('div', {
141
-  position: 'relative',
142
-  gridArea: 'tools',
147
+  position: 'fixed',
148
+  bottom: 40,
149
+  left: 0,
150
+  right: 0,
143
   padding: '0 8px 12px 8px',
151
   padding: '0 8px 12px 8px',
144
-  height: '100%',
145
   width: '100%',
152
   width: '100%',
146
   display: 'flex',
153
   display: 'flex',
147
   alignItems: 'center',
154
   alignItems: 'center',
148
   justifyContent: 'center',
155
   justifyContent: 'center',
156
+  flexWrap: 'wrap',
149
   gap: 16,
157
   gap: 16,
158
+  zIndex: 200,
159
+})
160
+
161
+const Flex = styled('div', {
162
+  display: 'flex',
163
+  '& > *:nth-child(n+2)': {
164
+    marginLeft: 16,
165
+  },
150
 })
166
 })
151
 
167
 
152
 const Container = styled('div', {
168
 const Container = styled('div', {
157
   border: '1px solid $border',
173
   border: '1px solid $border',
158
   pointerEvents: 'all',
174
   pointerEvents: 'all',
159
   userSelect: 'none',
175
   userSelect: 'none',
160
-  zIndex: 200,
161
-  boxShadow: '0px 2px 25px rgba(0,0,0,.16)',
162
   height: '100%',
176
   height: '100%',
163
   display: 'flex',
177
   display: 'flex',
164
   padding: 4,
178
   padding: 4,

+ 10
- 2
components/tools-panel/undo-redo.tsx 查看文件

9
 
9
 
10
 export default function UndoRedo() {
10
 export default function UndoRedo() {
11
   return (
11
   return (
12
-    <Container>
12
+    <Container size={{ '@sm': 'small' }}>
13
       <IconButton onClick={undo}>
13
       <IconButton onClick={undo}>
14
         <RotateCcw />
14
         <RotateCcw />
15
       </IconButton>
15
       </IconButton>
25
 
25
 
26
 const Container = styled('div', {
26
 const Container = styled('div', {
27
   position: 'absolute',
27
   position: 'absolute',
28
-  bottom: 12,
28
+  bottom: 64,
29
   right: 12,
29
   right: 12,
30
   backgroundColor: '$panel',
30
   backgroundColor: '$panel',
31
   borderRadius: '4px',
31
   borderRadius: '4px',
43
     height: 13,
43
     height: 13,
44
     width: 13,
44
     width: 13,
45
   },
45
   },
46
+
47
+  variants: {
48
+    size: {
49
+      small: {
50
+        bottom: 12,
51
+      },
52
+    },
53
+  },
46
 })
54
 })

+ 10
- 2
components/tools-panel/zoom.tsx 查看文件

10
 
10
 
11
 export default function Zoom() {
11
 export default function Zoom() {
12
   return (
12
   return (
13
-    <Container>
13
+    <Container size={{ '@sm': 'small' }}>
14
       <IconButton onClick={zoomOut}>
14
       <IconButton onClick={zoomOut}>
15
         <ZoomOutIcon />
15
         <ZoomOutIcon />
16
       </IconButton>
16
       </IconButton>
33
 
33
 
34
 const Container = styled('div', {
34
 const Container = styled('div', {
35
   position: 'absolute',
35
   position: 'absolute',
36
-  bottom: 12,
37
   left: 12,
36
   left: 12,
37
+  bottom: 64,
38
   backgroundColor: '$panel',
38
   backgroundColor: '$panel',
39
   borderRadius: '4px',
39
   borderRadius: '4px',
40
   overflow: 'hidden',
40
   overflow: 'hidden',
50
   '& svg': {
50
   '& svg': {
51
     strokeWidth: 0,
51
     strokeWidth: 0,
52
   },
52
   },
53
+
54
+  variants: {
55
+    size: {
56
+      small: {
57
+        bottom: 12,
58
+      },
59
+    },
60
+  },
53
 })
61
 })
54
 
62
 
55
 const ZoomButton = styled(IconButton, {
63
 const ZoomButton = styled(IconButton, {

+ 11
- 8
hooks/useBoundsHandleEvents.ts 查看文件

1
-import { useCallback, useRef } from "react"
2
-import inputs from "state/inputs"
3
-import { Edge, Corner } from "types"
1
+import { useCallback, useRef } from 'react'
2
+import inputs from 'state/inputs'
3
+import { Edge, Corner } from 'types'
4
 
4
 
5
-import state from "../state"
5
+import state from '../state'
6
 
6
 
7
 export default function useBoundsHandleEvents(
7
 export default function useBoundsHandleEvents(
8
-  handle: Edge | Corner | "rotate"
8
+  handle: Edge | Corner | 'rotate'
9
 ) {
9
 ) {
10
   const onPointerDown = useCallback(
10
   const onPointerDown = useCallback(
11
     (e) => {
11
     (e) => {
12
       if (e.buttons !== 1) return
12
       if (e.buttons !== 1) return
13
+      if (!inputs.canAccept(e.pointerId)) return
13
       e.stopPropagation()
14
       e.stopPropagation()
14
       e.currentTarget.setPointerCapture(e.pointerId)
15
       e.currentTarget.setPointerCapture(e.pointerId)
15
-      state.send("POINTED_BOUNDS_HANDLE", inputs.pointerDown(e, handle))
16
+      state.send('POINTED_BOUNDS_HANDLE', inputs.pointerDown(e, handle))
16
     },
17
     },
17
     [handle]
18
     [handle]
18
   )
19
   )
20
   const onPointerMove = useCallback(
21
   const onPointerMove = useCallback(
21
     (e) => {
22
     (e) => {
22
       if (e.buttons !== 1) return
23
       if (e.buttons !== 1) return
24
+      if (!inputs.canAccept(e.pointerId)) return
23
       e.stopPropagation()
25
       e.stopPropagation()
24
-      state.send("MOVED_POINTER", inputs.pointerMove(e))
26
+      state.send('MOVED_POINTER', inputs.pointerMove(e))
25
     },
27
     },
26
     [handle]
28
     [handle]
27
   )
29
   )
28
 
30
 
29
   const onPointerUp = useCallback((e) => {
31
   const onPointerUp = useCallback((e) => {
30
     if (e.buttons !== 1) return
32
     if (e.buttons !== 1) return
33
+    if (!inputs.canAccept(e.pointerId)) return
31
     e.stopPropagation()
34
     e.stopPropagation()
32
     e.currentTarget.releasePointerCapture(e.pointerId)
35
     e.currentTarget.releasePointerCapture(e.pointerId)
33
     e.currentTarget.replaceWith(e.currentTarget)
36
     e.currentTarget.replaceWith(e.currentTarget)
34
-    state.send("STOPPED_POINTING", inputs.pointerUp(e))
37
+    state.send('STOPPED_POINTING', inputs.pointerUp(e))
35
   }, [])
38
   }, [])
36
 
39
 
37
   return { onPointerDown, onPointerMove, onPointerUp }
40
   return { onPointerDown, onPointerMove, onPointerUp }

+ 10
- 3
hooks/useShapeEvents.ts 查看文件

8
 ) {
8
 ) {
9
   const handlePointerDown = useCallback(
9
   const handlePointerDown = useCallback(
10
     (e: React.PointerEvent) => {
10
     (e: React.PointerEvent) => {
11
-      e.stopPropagation()
11
+      if (!inputs.canAccept(e.pointerId)) return
12
+      // e.stopPropagation()
12
       rGroup.current.setPointerCapture(e.pointerId)
13
       rGroup.current.setPointerCapture(e.pointerId)
13
       state.send('POINTED_SHAPE', inputs.pointerDown(e, id))
14
       state.send('POINTED_SHAPE', inputs.pointerDown(e, id))
14
     },
15
     },
17
 
18
 
18
   const handlePointerUp = useCallback(
19
   const handlePointerUp = useCallback(
19
     (e: React.PointerEvent) => {
20
     (e: React.PointerEvent) => {
20
-      e.stopPropagation()
21
+      if (!inputs.canAccept(e.pointerId)) return
22
+      // e.stopPropagation()
21
       rGroup.current.releasePointerCapture(e.pointerId)
23
       rGroup.current.releasePointerCapture(e.pointerId)
22
       state.send('STOPPED_POINTING', inputs.pointerUp(e))
24
       state.send('STOPPED_POINTING', inputs.pointerUp(e))
23
     },
25
     },
26
 
28
 
27
   const handlePointerEnter = useCallback(
29
   const handlePointerEnter = useCallback(
28
     (e: React.PointerEvent) => {
30
     (e: React.PointerEvent) => {
31
+      if (!inputs.canAccept(e.pointerId)) return
29
       state.send('HOVERED_SHAPE', inputs.pointerEnter(e, id))
32
       state.send('HOVERED_SHAPE', inputs.pointerEnter(e, id))
30
     },
33
     },
31
     [id]
34
     [id]
33
 
36
 
34
   const handlePointerMove = useCallback(
37
   const handlePointerMove = useCallback(
35
     (e: React.PointerEvent) => {
38
     (e: React.PointerEvent) => {
39
+      if (!inputs.canAccept(e.pointerId)) return
36
       state.send('MOVED_OVER_SHAPE', inputs.pointerEnter(e, id))
40
       state.send('MOVED_OVER_SHAPE', inputs.pointerEnter(e, id))
37
     },
41
     },
38
     [id]
42
     [id]
39
   )
43
   )
40
 
44
 
41
   const handlePointerLeave = useCallback(
45
   const handlePointerLeave = useCallback(
42
-    () => state.send('UNHOVERED_SHAPE', { target: id }),
46
+    (e: React.PointerEvent) => {
47
+      if (!inputs.canAccept(e.pointerId)) return
48
+      state.send('UNHOVERED_SHAPE', { target: id })
49
+    },
43
     [id]
50
     [id]
44
   )
51
   )
45
 
52
 

+ 47
- 81
hooks/useZoomEvents.ts 查看文件

2
 import state from 'state'
2
 import state from 'state'
3
 import inputs from 'state/inputs'
3
 import inputs from 'state/inputs'
4
 import * as vec from 'utils/vec'
4
 import * as vec from 'utils/vec'
5
-import { usePinch } from 'react-use-gesture'
5
+import { useGesture } from 'react-use-gesture'
6
 
6
 
7
 /**
7
 /**
8
  * Capture zoom gestures (pinches, wheels and pans) and send to the state.
8
  * Capture zoom gestures (pinches, wheels and pans) and send to the state.
12
 export default function useZoomEvents(
12
 export default function useZoomEvents(
13
   ref: React.MutableRefObject<SVGSVGElement>
13
   ref: React.MutableRefObject<SVGSVGElement>
14
 ) {
14
 ) {
15
-  const rTouchDist = useRef(0)
16
-
17
-  useEffect(() => {
18
-    const element = ref.current
19
-
20
-    if (!element) return
21
-
22
-    function handleWheel(e: WheelEvent) {
23
-      e.preventDefault()
24
-      e.stopPropagation()
25
-
26
-      if (e.ctrlKey) {
27
-        state.send('ZOOMED_CAMERA', {
28
-          delta: e.deltaY,
29
-          ...inputs.wheel(e),
30
-        })
31
-        return
32
-      }
33
-
34
-      state.send('PANNED_CAMERA', {
35
-        delta: [e.deltaX, e.deltaY],
36
-        ...inputs.wheel(e),
37
-      })
38
-    }
39
-
40
-    function handleTouchMove(e: TouchEvent) {
41
-      e.preventDefault()
42
-      e.stopPropagation()
43
-
44
-      if (e.touches.length === 2) {
45
-        const { clientX: x0, clientY: y0 } = e.touches[0]
46
-        const { clientX: x1, clientY: y1 } = e.touches[1]
47
-
48
-        const dist = vec.dist([x0, y0], [x1, y1])
49
-        const point = vec.med([x0, y0], [x1, y1])
50
-
51
-        state.send('WHEELED', {
52
-          delta: dist - rTouchDist.current,
53
-          point,
54
-        })
55
-
56
-        rTouchDist.current = dist
57
-      }
58
-    }
59
-
60
-    element.addEventListener('wheel', handleWheel, { passive: false })
61
-    element.addEventListener('touchstart', handleTouchMove, { passive: false })
62
-    element.addEventListener('touchmove', handleTouchMove, { passive: false })
63
-
64
-    return () => {
65
-      element.removeEventListener('wheel', handleWheel)
66
-      element.removeEventListener('touchstart', handleTouchMove)
67
-      element.removeEventListener('touchmove', handleTouchMove)
68
-    }
69
-  }, [ref])
70
-
71
   const rPinchDa = useRef<number[] | undefined>(undefined)
15
   const rPinchDa = useRef<number[] | undefined>(undefined)
72
   const rPinchPoint = useRef<number[] | undefined>(undefined)
16
   const rPinchPoint = useRef<number[] | undefined>(undefined)
73
 
17
 
74
-  const bind = usePinch(({ pinching, da, origin }) => {
75
-    if (!pinching) {
76
-      state.send('STOPPED_PINCHING')
77
-      rPinchDa.current = undefined
78
-      rPinchPoint.current = undefined
79
-      return
80
-    }
18
+  const bind = useGesture(
19
+    {
20
+      onWheel: ({ event, delta }) => {
21
+        if (event.ctrlKey) {
22
+          state.send('ZOOMED_CAMERA', {
23
+            delta: delta[1],
24
+            ...inputs.wheel(event as WheelEvent),
25
+          })
26
+          return
27
+        }
28
+
29
+        state.send('PANNED_CAMERA', {
30
+          delta,
31
+          ...inputs.wheel(event as WheelEvent),
32
+        })
33
+      },
34
+      onPinch: ({ pinching, da, origin }) => {
35
+        if (!pinching) {
36
+          state.send('STOPPED_PINCHING')
37
+          rPinchDa.current = undefined
38
+          rPinchPoint.current = undefined
39
+          return
40
+        }
41
+
42
+        if (rPinchPoint.current === undefined) {
43
+          state.send('STARTED_PINCHING')
44
+          rPinchDa.current = da
45
+          rPinchPoint.current = origin
46
+        }
47
+
48
+        const [distanceDelta, angleDelta] = vec.sub(rPinchDa.current, da)
49
+
50
+        state.send('PINCHED', {
51
+          delta: vec.sub(rPinchPoint.current, origin),
52
+          point: origin,
53
+          distanceDelta,
54
+          angleDelta,
55
+        })
81
 
56
 
82
-    if (rPinchPoint.current === undefined) {
83
-      state.send('STARTED_PINCHING')
84
-      rPinchDa.current = da
85
-      rPinchPoint.current = origin
57
+        rPinchDa.current = da
58
+        rPinchPoint.current = origin
59
+      },
60
+    },
61
+    {
62
+      domTarget: document.body,
63
+      eventOptions: { passive: false },
86
     }
64
     }
87
-
88
-    const [distanceDelta, angleDelta] = vec.sub(rPinchDa.current, da)
89
-
90
-    state.send('PINCHED', {
91
-      delta: vec.sub(rPinchPoint.current, origin),
92
-      point: origin,
93
-      distanceDelta,
94
-      angleDelta,
95
-    })
96
-
97
-    rPinchDa.current = da
98
-    rPinchPoint.current = origin
99
-  })
65
+  )
100
 
66
 
101
   return { ...bind() }
67
   return { ...bind() }
102
 }
68
 }

+ 15
- 14
state/history.ts 查看文件

1
-import { Data } from "types"
2
-import { BaseCommand } from "./commands/command"
3
-import state from "./state"
1
+import { Data } from 'types'
2
+import { BaseCommand } from './commands/command'
3
+import state from './state'
4
 
4
 
5
 // A singleton to manage history changes.
5
 // A singleton to manage history changes.
6
 
6
 
11
   private _enabled = true
11
   private _enabled = true
12
 
12
 
13
   execute = (data: T, command: BaseCommand<T>) => {
13
   execute = (data: T, command: BaseCommand<T>) => {
14
+    command.redo(data, true)
15
+
14
     if (this.disabled) return
16
     if (this.disabled) return
15
     this.stack = this.stack.slice(0, this.pointer + 1)
17
     this.stack = this.stack.slice(0, this.pointer + 1)
16
     this.stack.push(command)
18
     this.stack.push(command)
17
-    command.redo(data, true)
18
     this.pointer++
19
     this.pointer++
19
 
20
 
20
     if (this.stack.length > this.maxLength) {
21
     if (this.stack.length > this.maxLength) {
26
   }
27
   }
27
 
28
 
28
   undo = (data: T) => {
29
   undo = (data: T) => {
29
-    if (this.disabled) return
30
     if (this.pointer === -1) return
30
     if (this.pointer === -1) return
31
     const command = this.stack[this.pointer]
31
     const command = this.stack[this.pointer]
32
     command.undo(data)
32
     command.undo(data)
33
+    if (this.disabled) return
33
     this.pointer--
34
     this.pointer--
34
     this.save(data)
35
     this.save(data)
35
   }
36
   }
36
 
37
 
37
   redo = (data: T) => {
38
   redo = (data: T) => {
38
-    if (this.disabled) return
39
     if (this.pointer === this.stack.length - 1) return
39
     if (this.pointer === this.stack.length - 1) return
40
     const command = this.stack[this.pointer + 1]
40
     const command = this.stack[this.pointer + 1]
41
     command.redo(data, false)
41
     command.redo(data, false)
42
+    if (this.disabled) return
42
     this.pointer++
43
     this.pointer++
43
     this.save(data)
44
     this.save(data)
44
   }
45
   }
45
 
46
 
46
-  load(data: T, id = "code_slate_0.0.1") {
47
-    if (typeof window === "undefined") return
48
-    if (typeof localStorage === "undefined") return
47
+  load(data: T, id = 'code_slate_0.0.1') {
48
+    if (typeof window === 'undefined') return
49
+    if (typeof localStorage === 'undefined') return
49
 
50
 
50
     const savedData = localStorage.getItem(id)
51
     const savedData = localStorage.getItem(id)
51
 
52
 
54
     }
55
     }
55
   }
56
   }
56
 
57
 
57
-  save = (data: T, id = "code_slate_0.0.1") => {
58
-    if (typeof window === "undefined") return
59
-    if (typeof localStorage === "undefined") return
58
+  save = (data: T, id = 'code_slate_0.0.1') => {
59
+    if (typeof window === 'undefined') return
60
+    if (typeof localStorage === 'undefined') return
60
 
61
 
61
     localStorage.setItem(id, JSON.stringify(this.prepareDataForSave(data)))
62
     localStorage.setItem(id, JSON.stringify(this.prepareDataForSave(data)))
62
   }
63
   }
110
     restoredData.selectedIds = new Set(restoredData.selectedIds)
111
     restoredData.selectedIds = new Set(restoredData.selectedIds)
111
 
112
 
112
     // Also restore camera position, which is saved separately in this app
113
     // Also restore camera position, which is saved separately in this app
113
-    const cameraInfo = localStorage.getItem("code_slate_camera")
114
+    const cameraInfo = localStorage.getItem('code_slate_camera')
114
 
115
 
115
     if (cameraInfo !== null) {
116
     if (cameraInfo !== null) {
116
       Object.assign(restoredData.camera, JSON.parse(cameraInfo))
117
       Object.assign(restoredData.camera, JSON.parse(cameraInfo))
117
 
118
 
118
       // And update the CSS property
119
       // And update the CSS property
119
       document.documentElement.style.setProperty(
120
       document.documentElement.style.setProperty(
120
-        "--camera-zoom",
121
+        '--camera-zoom',
121
         restoredData.camera.zoom.toString()
122
         restoredData.camera.zoom.toString()
122
       )
123
       )
123
     }
124
     }

+ 11
- 2
state/inputs.tsx 查看文件

1
-import { PointerInfo } from "types"
2
-import { isDarwin } from "utils/utils"
1
+import { PointerInfo } from 'types'
2
+import { isDarwin } from 'utils/utils'
3
 
3
 
4
 class Inputs {
4
 class Inputs {
5
+  activePointerId?: number
5
   points: Record<string, PointerInfo> = {}
6
   points: Record<string, PointerInfo> = {}
6
 
7
 
7
   pointerDown(e: PointerEvent | React.PointerEvent, target: string) {
8
   pointerDown(e: PointerEvent | React.PointerEvent, target: string) {
19
     }
20
     }
20
 
21
 
21
     this.points[e.pointerId] = info
22
     this.points[e.pointerId] = info
23
+    this.activePointerId = e.pointerId
22
 
24
 
23
     return info
25
     return info
24
   }
26
   }
78
     }
80
     }
79
 
81
 
80
     delete this.points[e.pointerId]
82
     delete this.points[e.pointerId]
83
+    delete this.activePointerId
81
 
84
 
82
     return info
85
     return info
83
   }
86
   }
87
     return { point: [e.clientX, e.clientY], shiftKey, ctrlKey, metaKey, altKey }
90
     return { point: [e.clientX, e.clientY], shiftKey, ctrlKey, metaKey, altKey }
88
   }
91
   }
89
 
92
 
93
+  canAccept(pointerId: PointerEvent['pointerId']) {
94
+    return (
95
+      this.activePointerId === undefined || this.activePointerId === pointerId
96
+    )
97
+  }
98
+
90
   get pointer() {
99
   get pointer() {
91
     return this.points[Object.keys(this.points)[0]]
100
     return this.points[Object.keys(this.points)[0]]
92
   }
101
   }

+ 11
- 0
state/sessions/rotate-session.ts 查看文件

45
     for (let { id, center, offset, rotation } of initialShapes) {
45
     for (let { id, center, offset, rotation } of initialShapes) {
46
       const shape = page.shapes[id]
46
       const shape = page.shapes[id]
47
 
47
 
48
+      // const rotationOffset = vec.sub(
49
+      //   getBoundsCenter(getShapeBounds(shape)),
50
+      //   getBoundsCenter(getRotatedBounds(shape))
51
+      // )
52
+
48
       const nextRotation = isLocked
53
       const nextRotation = isLocked
49
         ? clampToRotationToSegments(rotation + rot, 24)
54
         ? clampToRotationToSegments(rotation + rot, 24)
50
         : rotation + rot
55
         : rotation + rot
100
       const center = getBoundsCenter(bounds)
105
       const center = getBoundsCenter(bounds)
101
       const offset = vec.sub(center, shape.point)
106
       const offset = vec.sub(center, shape.point)
102
 
107
 
108
+      const rotationOffset = vec.sub(
109
+        center,
110
+        getBoundsCenter(getRotatedBounds(shape))
111
+      )
112
+
103
       return {
113
       return {
104
         id: shape.id,
114
         id: shape.id,
105
         point: shape.point,
115
         point: shape.point,
106
         rotation: shape.rotation,
116
         rotation: shape.rotation,
107
         offset,
117
         offset,
118
+        rotationOffset,
108
         center,
119
         center,
109
       }
120
       }
110
     }),
121
     }),

+ 72
- 52
state/state.ts 查看文件

69
 const state = createState({
69
 const state = createState({
70
   data: initialData,
70
   data: initialData,
71
   on: {
71
   on: {
72
-    ZOOMED_CAMERA: {
73
-      do: 'zoomCamera',
74
-    },
75
-    PANNED_CAMERA: {
76
-      do: 'panCamera',
77
-    },
78
-    ZOOMED_TO_ACTUAL: {
79
-      if: 'hasSelection',
80
-      do: 'zoomCameraToSelectionActual',
81
-      else: 'zoomCameraToActual',
82
-    },
83
-    ZOOMED_TO_SELECTION: {
84
-      if: 'hasSelection',
85
-      do: 'zoomCameraToSelection',
86
-    },
87
-    ZOOMED_TO_FIT: ['zoomCameraToFit', 'zoomCameraToActual'],
88
-    ZOOMED_IN: 'zoomIn',
89
-    ZOOMED_OUT: 'zoomOut',
90
-    RESET_CAMERA: 'resetCamera',
91
-    TOGGLED_SHAPE_LOCK: { if: 'hasSelection', do: 'lockSelection' },
92
-    TOGGLED_SHAPE_HIDE: { if: 'hasSelection', do: 'hideSelection' },
93
-    TOGGLED_SHAPE_ASPECT_LOCK: {
94
-      if: 'hasSelection',
95
-      do: 'aspectLockSelection',
96
-    },
97
-    SELECTED_SELECT_TOOL: { to: 'selecting' },
98
-    SELECTED_DRAW_TOOL: { unless: 'isReadOnly', to: 'draw' },
99
-    SELECTED_DOT_TOOL: { unless: 'isReadOnly', to: 'dot' },
100
-    SELECTED_CIRCLE_TOOL: { unless: 'isReadOnly', to: 'circle' },
101
-    SELECTED_ELLIPSE_TOOL: { unless: 'isReadOnly', to: 'ellipse' },
102
-    SELECTED_RAY_TOOL: { unless: 'isReadOnly', to: 'ray' },
103
-    SELECTED_LINE_TOOL: { unless: 'isReadOnly', to: 'line' },
104
-    SELECTED_POLYLINE_TOOL: { unless: 'isReadOnly', to: 'polyline' },
105
-    SELECTED_RECTANGLE_TOOL: { unless: 'isReadOnly', to: 'rectangle' },
106
-    TOGGLED_CODE_PANEL_OPEN: 'toggleCodePanel',
107
-    TOGGLED_STYLE_PANEL_OPEN: 'toggleStylePanel',
108
-    CHANGED_STYLE: ['updateStyles', 'applyStylesToSelection'],
109
-    SELECTED_ALL: { to: 'selecting', do: 'selectAll' },
110
-    NUDGED: { do: 'nudgeSelection' },
111
-    USED_PEN_DEVICE: 'enablePenLock',
112
-    DISABLED_PEN_LOCK: 'disablePenLock',
72
+    UNMOUNTED: [{ unless: 'isReadOnly', do: 'forceSave' }, { to: 'loading' }],
113
   },
73
   },
114
   initial: 'loading',
74
   initial: 'loading',
115
   states: {
75
   states: {
131
         else: ['zoomCameraToFit', 'zoomCameraToActual'],
91
         else: ['zoomCameraToFit', 'zoomCameraToActual'],
132
       },
92
       },
133
       on: {
93
       on: {
134
-        UNMOUNTED: [
135
-          { unless: 'isReadOnly', do: 'forceSave' },
136
-          { to: 'loading' },
137
-        ],
94
+        ZOOMED_CAMERA: {
95
+          do: 'zoomCamera',
96
+        },
97
+        PANNED_CAMERA: {
98
+          do: 'panCamera',
99
+        },
100
+        ZOOMED_TO_ACTUAL: {
101
+          if: 'hasSelection',
102
+          do: 'zoomCameraToSelectionActual',
103
+          else: 'zoomCameraToActual',
104
+        },
105
+        ZOOMED_TO_SELECTION: {
106
+          if: 'hasSelection',
107
+          do: 'zoomCameraToSelection',
108
+        },
109
+        ZOOMED_TO_FIT: ['zoomCameraToFit', 'zoomCameraToActual'],
110
+        ZOOMED_IN: 'zoomIn',
111
+        ZOOMED_OUT: 'zoomOut',
112
+        RESET_CAMERA: 'resetCamera',
113
+        TOGGLED_SHAPE_LOCK: { if: 'hasSelection', do: 'lockSelection' },
114
+        TOGGLED_SHAPE_HIDE: { if: 'hasSelection', do: 'hideSelection' },
115
+        TOGGLED_SHAPE_ASPECT_LOCK: {
116
+          if: 'hasSelection',
117
+          do: 'aspectLockSelection',
118
+        },
119
+        SELECTED_SELECT_TOOL: { to: 'selecting' },
120
+        SELECTED_DRAW_TOOL: { unless: 'isReadOnly', to: 'draw' },
121
+        SELECTED_DOT_TOOL: { unless: 'isReadOnly', to: 'dot' },
122
+        SELECTED_CIRCLE_TOOL: { unless: 'isReadOnly', to: 'circle' },
123
+        SELECTED_ELLIPSE_TOOL: { unless: 'isReadOnly', to: 'ellipse' },
124
+        SELECTED_RAY_TOOL: { unless: 'isReadOnly', to: 'ray' },
125
+        SELECTED_LINE_TOOL: { unless: 'isReadOnly', to: 'line' },
126
+        SELECTED_POLYLINE_TOOL: { unless: 'isReadOnly', to: 'polyline' },
127
+        SELECTED_RECTANGLE_TOOL: { unless: 'isReadOnly', to: 'rectangle' },
128
+        TOGGLED_CODE_PANEL_OPEN: 'toggleCodePanel',
129
+        TOGGLED_STYLE_PANEL_OPEN: 'toggleStylePanel',
130
+        CHANGED_STYLE: ['updateStyles', 'applyStylesToSelection'],
131
+        SELECTED_ALL: { to: 'selecting', do: 'selectAll' },
132
+        NUDGED: { do: 'nudgeSelection' },
133
+        USED_PEN_DEVICE: 'enablePenLock',
134
+        DISABLED_PEN_LOCK: 'disablePenLock',
135
+        CLEARED_PAGE: ['selectAll', 'deleteSelection'],
138
       },
136
       },
139
       initial: 'selecting',
137
       initial: 'selecting',
140
       states: {
138
       states: {
143
             SAVED: 'forceSave',
141
             SAVED: 'forceSave',
144
             UNDO: 'undo',
142
             UNDO: 'undo',
145
             REDO: 'redo',
143
             REDO: 'redo',
146
-            CLEARED_PAGE: ['selectAll', 'deleteSelection'],
147
             SAVED_CODE: 'saveCode',
144
             SAVED_CODE: 'saveCode',
148
             DELETED: 'deleteSelection',
145
             DELETED: 'deleteSelection',
149
-            STARTED_PINCHING: { to: 'pinching' },
150
             INCREASED_CODE_FONT_SIZE: 'increaseCodeFontSize',
146
             INCREASED_CODE_FONT_SIZE: 'increaseCodeFontSize',
151
             DECREASED_CODE_FONT_SIZE: 'decreaseCodeFontSize',
147
             DECREASED_CODE_FONT_SIZE: 'decreaseCodeFontSize',
152
             CHANGED_CODE_CONTROL: 'updateControls',
148
             CHANGED_CODE_CONTROL: 'updateControls',
164
             notPointing: {
160
             notPointing: {
165
               on: {
161
               on: {
166
                 CANCELLED: 'clearSelectedIds',
162
                 CANCELLED: 'clearSelectedIds',
163
+                STARTED_PINCHING: { to: 'pinching' },
167
                 POINTED_CANVAS: { to: 'brushSelecting' },
164
                 POINTED_CANVAS: { to: 'brushSelecting' },
168
                 POINTED_BOUNDS: { to: 'pointingBounds' },
165
                 POINTED_BOUNDS: { to: 'pointingBounds' },
169
                 POINTED_BOUNDS_HANDLE: {
166
                 POINTED_BOUNDS_HANDLE: {
269
                 'startBrushSession',
266
                 'startBrushSession',
270
               ],
267
               ],
271
               on: {
268
               on: {
272
-                STARTED_PINCHING: { to: 'pinching' },
269
+                STARTED_PINCHING: { do: 'completeSession', to: 'pinching' },
273
                 MOVED_POINTER: 'updateBrushSession',
270
                 MOVED_POINTER: 'updateBrushSession',
274
                 PANNED_CAMERA: 'updateBrushSession',
271
                 PANNED_CAMERA: 'updateBrushSession',
275
                 STOPPED_POINTING: { do: 'completeSession', to: 'selecting' },
272
                 STOPPED_POINTING: { do: 'completeSession', to: 'selecting' },
280
         },
277
         },
281
         pinching: {
278
         pinching: {
282
           on: {
279
           on: {
283
-            STOPPED_PINCHING: { to: 'selecting' },
284
             PINCHED: { do: 'pinchCamera' },
280
             PINCHED: { do: 'pinchCamera' },
285
           },
281
           },
282
+          initial: 'selectPinching',
283
+          states: {
284
+            selectPinching: {
285
+              on: {
286
+                STOPPED_PINCHING: { to: 'selecting' },
287
+              },
288
+            },
289
+            toolPinching: {
290
+              on: {
291
+                STOPPED_PINCHING: { to: 'usingTool.previous' },
292
+              },
293
+            },
294
+          },
286
         },
295
         },
287
         usingTool: {
296
         usingTool: {
288
           initial: 'draw',
297
           initial: 'draw',
289
           onEnter: 'clearSelectedIds',
298
           onEnter: 'clearSelectedIds',
290
           on: {
299
           on: {
300
+            STARTED_PINCHING: {
301
+              do: 'breakSession',
302
+              to: 'pinching.toolPinching',
303
+            },
291
             TOGGLED_TOOL_LOCK: 'toggleToolLock',
304
             TOGGLED_TOOL_LOCK: 'toggleToolLock',
292
           },
305
           },
293
           states: {
306
           states: {
319
                       to: 'draw.creating',
332
                       to: 'draw.creating',
320
                     },
333
                     },
321
                     CANCELLED: {
334
                     CANCELLED: {
322
-                      do: ['cancelSession', 'deleteSelection'],
335
+                      do: 'breakSession',
323
                       to: 'selecting',
336
                       to: 'selecting',
324
                     },
337
                     },
325
                     MOVED_POINTER: 'updateDrawSession',
338
                     MOVED_POINTER: 'updateDrawSession',
359
                       },
372
                       },
360
                     ],
373
                     ],
361
                     CANCELLED: {
374
                     CANCELLED: {
362
-                      do: ['cancelSession', 'deleteSelection'],
375
+                      do: 'breakSession',
363
                       to: 'selecting',
376
                       to: 'selecting',
364
                     },
377
                     },
365
                   },
378
                   },
545
               },
558
               },
546
             ],
559
             ],
547
             CANCELLED: {
560
             CANCELLED: {
548
-              do: ['cancelSession', 'deleteSelection'],
561
+              do: 'breakSession',
549
               to: 'selecting',
562
               to: 'selecting',
550
             },
563
             },
551
           },
564
           },
662
     /* -------------------- Sessions -------------------- */
675
     /* -------------------- Sessions -------------------- */
663
 
676
 
664
     // Shared
677
     // Shared
678
+    breakSession(data) {
679
+      session?.cancel(data)
680
+      session = undefined
681
+      history.disable()
682
+      commands.deleteSelected(data)
683
+      history.enable()
684
+    },
665
     cancelSession(data) {
685
     cancelSession(data) {
666
       session?.cancel(data)
686
       session?.cancel(data)
667
       session = undefined
687
       session = undefined

+ 4
- 0
styles/stitches.config.ts 查看文件

42
     zIndices: {},
42
     zIndices: {},
43
     transitions: {},
43
     transitions: {},
44
   },
44
   },
45
+  media: {
46
+    sm: '(min-width: 640px)',
47
+    md: '(min-width: 768px)',
48
+  },
45
   utils: {
49
   utils: {
46
     zDash: () => (value: number) => {
50
     zDash: () => (value: number) => {
47
       return {
51
       return {

Loading…
取消
儲存